]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'jc/1.7.0-push-safety'
authorJunio C Hamano <gitster@pobox.com>
Sat, 26 Dec 2009 22:03:17 +0000 (14:03 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sat, 26 Dec 2009 22:03:17 +0000 (14:03 -0800)
* jc/1.7.0-push-safety:
  Refuse deleting the current branch via push
  Refuse updating the current branch in a non-bare repository via push

593 files changed:
.gitignore
.mailmap
Documentation/.gitignore
Documentation/Makefile
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.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.txt
Documentation/RelNotes-1.6.6.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/diff-options.txt
Documentation/fetch-options.txt
Documentation/git-add.txt
Documentation/git-am.txt
Documentation/git-apply.txt
Documentation/git-archive.txt
Documentation/git-bisect-lk2009.txt [new file with mode: 0644]
Documentation/git-bisect.txt
Documentation/git-branch.txt
Documentation/git-bundle.txt
Documentation/git-check-ref-format.txt
Documentation/git-checkout.txt
Documentation/git-clean.txt
Documentation/git-clone.txt
Documentation/git-commit.txt
Documentation/git-cvsserver.txt
Documentation/git-describe.txt
Documentation/git-difftool.txt
Documentation/git-fast-export.txt
Documentation/git-fast-import.txt
Documentation/git-fetch.txt
Documentation/git-filter-branch.txt
Documentation/git-fmt-merge-msg.txt
Documentation/git-format-patch.txt
Documentation/git-fsck.txt
Documentation/git-gc.txt
Documentation/git-grep.txt
Documentation/git-http-backend.txt [new file with mode: 0644]
Documentation/git-http-push.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-mailinfo.txt
Documentation/git-merge-base.txt
Documentation/git-merge.txt
Documentation/git-mergetool.txt
Documentation/git-mv.txt
Documentation/git-notes.txt [new file with mode: 0644]
Documentation/git-pack-objects.txt
Documentation/git-prune-packed.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-remote-helpers.txt [new file with mode: 0644]
Documentation/git-remote.txt
Documentation/git-replace.txt [new file with mode: 0644]
Documentation/git-reset.txt
Documentation/git-rev-list.txt
Documentation/git-send-email.txt
Documentation/git-send-pack.txt
Documentation/git-show-branch.txt
Documentation/git-show-ref.txt
Documentation/git-stash.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git-symbolic-ref.txt
Documentation/git-tag.txt
Documentation/git-update-index.txt
Documentation/git-var.txt
Documentation/git-verify-pack.txt
Documentation/git-write-tree.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitcli.txt
Documentation/gitcore-tutorial.txt
Documentation/githooks.txt
Documentation/gitworkflows.txt
Documentation/glossary-content.txt
Documentation/howto/maintain-git.txt
Documentation/howto/revert-branch-rebase.txt
Documentation/howto/update-hook-example.txt
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/pt_BR/gittutorial.txt [new file with mode: 0644]
Documentation/pull-fetch-param.txt
Documentation/rev-list-options.txt
Documentation/technical/api-hash.txt
Documentation/technical/api-history-graph.txt
Documentation/technical/api-run-command.txt
Documentation/technical/api-strbuf.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.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
INSTALL
Makefile
README
RelNotes
abspath.c
advice.c [new file with mode: 0644]
advice.h [new file with mode: 0644]
archive.c
arm/sha1.c [deleted file]
arm/sha1.h [deleted file]
arm/sha1_arm.S [deleted file]
bisect.c
block-sha1/sha1.c [new file with mode: 0644]
block-sha1/sha1.h [new file with mode: 0644]
builtin-add.c
builtin-apply.c
builtin-archive.c
builtin-blame.c
builtin-branch.c
builtin-bundle.c
builtin-check-ref-format.c
builtin-checkout.c
builtin-clean.c
builtin-clone.c
builtin-commit-tree.c
builtin-commit.c
builtin-describe.c
builtin-diff-tree.c
builtin-diff.c
builtin-fast-export.c
builtin-fetch-pack.c
builtin-fetch.c
builtin-for-each-ref.c
builtin-fsck.c
builtin-gc.c
builtin-grep.c
builtin-help.c
builtin-init-db.c
builtin-log.c
builtin-ls-files.c
builtin-ls-remote.c
builtin-ls-tree.c
builtin-mailinfo.c
builtin-mailsplit.c
builtin-merge-base.c
builtin-merge-ours.c
builtin-merge-recursive.c
builtin-merge.c
builtin-mv.c
builtin-pack-objects.c
builtin-prune-packed.c
builtin-prune.c
builtin-push.c
builtin-read-tree.c
builtin-receive-pack.c
builtin-reflog.c
builtin-remote.c
builtin-replace.c [new file with mode: 0644]
builtin-rerere.c
builtin-reset.c
builtin-rev-list.c
builtin-rev-parse.c
builtin-send-pack.c
builtin-shortlog.c
builtin-show-branch.c
builtin-show-ref.c
builtin-stripspace.c
builtin-tag.c
builtin-tar-tree.c
builtin-unpack-objects.c
builtin-update-index.c
builtin-update-server-info.c [new file with mode: 0644]
builtin-upload-archive.c
builtin-verify-pack.c
builtin-verify-tag.c
builtin-write-tree.c
builtin.h
bundle.c
cache.h
color.h
combine-diff.c
command-list.txt
commit.c
commit.h
compat/bswap.h [new file with mode: 0644]
compat/mingw.c
compat/mingw.h
compat/msvc.c [new file with mode: 0644]
compat/msvc.h [new file with mode: 0644]
compat/regex/regex.c
compat/snprintf.c
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/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/win32mmap.c
compat/winansi.c
config.c
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/completion/git-completion.bash
contrib/emacs/git-blame.el
contrib/emacs/git.el
contrib/examples/builtin-fetch--tool.c [moved from builtin-fetch--tool.c with 98% similarity]
contrib/examples/git-merge.sh
contrib/examples/git-resolve.sh
contrib/fast-import/git-p4
contrib/fast-import/import-directories.perl [new file with mode: 0755]
contrib/fast-import/import-tars.perl
contrib/hg-to-git/hg-to-git.py
contrib/hooks/post-receive-email
convert.c
copy.c
daemon.c
date.c
diff-delta.c
diff-lib.c
diff-no-index.c
diff.c
diff.h
diffcore-break.c
diffcore-delta.c
diffcore-rename.c
dir.c
dir.h
editor.c
entry.c
environment.c
fast-import.c
fetch-pack.h
fsck.h
git-add--interactive.perl
git-am.sh
git-bisect.sh
git-compat-util.h
git-cvsimport.perl
git-cvsserver.perl
git-filter-branch.sh
git-gui/git-gui.sh
git-gui/lib/blame.tcl
git-gui/lib/database.tcl
git-gui/lib/diff.tcl
git-gui/lib/remote_branch_delete.tcl
git-gui/po/el.po [new file with mode: 0644]
git-gui/po/git-gui.pot
git-gui/po/glossary/el.po [new file with mode: 0644]
git-gui/po/ru.po
git-instaweb.sh
git-merge-octopus.sh
git-merge-one-file.sh
git-mergetool--lib.sh
git-notes.sh [new file with mode: 0755]
git-pull.sh
git-rebase--interactive.sh
git-rebase.sh
git-request-pull.sh
git-send-email.perl
git-sh-setup.sh
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/git.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/ja.po [new file with mode: 0644]
gitk-git/po/sv.po
gitweb/INSTALL
gitweb/README
gitweb/git-favicon.png
gitweb/git-logo.png
gitweb/gitweb.css
gitweb/gitweb.js [new file with mode: 0644]
gitweb/gitweb.perl
graph.c
graph.h
grep.c
grep.h
help.c
http-backend.c [new file with mode: 0644]
http-fetch.c [moved from builtin-http-fetch.c with 82% similarity]
http-push.c
http.c
http.h
ident.c
imap-send.c
index-pack.c
ll-merge.c
lockfile.c
log-tree.c
log-tree.h
merge-recursive.c
merge-recursive.h
mktag.c
mozilla-sha1/sha1.c [deleted file]
mozilla-sha1/sha1.h [deleted file]
notes.c [new file with mode: 0644]
notes.h [new file with mode: 0644]
object.c
pack-redundant.c
pager.c
parse-options.c
parse-options.h
patch-delta.c
path.c
perl/Makefile
perl/Makefile.PL
pkt-line.c
pkt-line.h
pretty.c
progress.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
revision.c
revision.h
run-command.c
run-command.h
send-pack.h
setup.c
sha1_file.c
show-index.c
sideband.c
strbuf.c
strbuf.h
submodule.c [new file with mode: 0644]
submodule.h [new file with mode: 0644]
symlinks.c
t/Makefile
t/README
t/gitweb-lib.sh [new file with mode: 0644]
t/lib-cvs.sh [new file with mode: 0644]
t/lib-git-svn.sh
t/lib-httpd/apache.conf
t/lib-patch-mode.sh [new file with mode: 0755]
t/lib-rebase.sh
t/t0001-init.sh
t/t0006-date.sh [new file with mode: 0755]
t/t0040-parse-options.sh
t/t1001-read-tree-m-2way.sh
t/t1009-read-tree-new-index.sh [new file with mode: 0755]
t/t1200-tutorial.sh
t/t1300-repo-config.sh
t/t1402-check-ref-format.sh [new file with mode: 0755]
t/t1501-worktree.sh
t/t2000-checkout-cache-clash.sh
t/t2015-checkout-unborn.sh [new file with mode: 0755]
t/t2016-checkout-patch.sh [new file with mode: 0755]
t/t2300-cd-to-toplevel.sh
t/t3003-ls-files-exclude.sh [new file with mode: 0755]
t/t3101-ls-tree-dirname.sh
t/t3202-show-branch-octopus.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/t3400-rebase.sh
t/t3404-rebase-interactive.sh
t/t3409-rebase-preserve-merges.sh
t/t3411-rebase-preserve-around-merges.sh
t/t3414-rebase-preserve-onto.sh
t/t3701-add-interactive.sh
t/t3903-stash.sh
t/t3904-stash-patch.sh [new file with mode: 0755]
t/t4013-diff-various.sh
t/t4013/diff.log_--decorate=full_--all [new file with mode: 0644]
t/t4013/diff.log_--decorate_--all
t/t4014-format-patch.sh
t/t4015-diff-whitespace.sh
t/t4019-diff-wserror.sh
t/t4020-diff-external.sh
t/t4026-color.sh
t/t4034-diff-words.sh
t/t4039-diff-assume-unchanged.sh [new file with mode: 0755]
t/t4041-diff-submodule.sh [new file with mode: 0755]
t/t4107-apply-ignore-whitespace.sh [new file with mode: 0755]
t/t4124-apply-ws-rule.sh
t/t4128-apply-root.sh
t/t4132-apply-removal.sh [new file with mode: 0755]
t/t4150-am.sh
t/t4201-shortlog.sh
t/t4202-log.sh
t/t5000-tar-tree.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/sample.mbox
t/t5303-pack-corruption-resilience.sh
t/t5304-prune.sh
t/t5500-fetch-pack.sh
t/t5505-remote.sh
t/t5506-remote-groups.sh
t/t5510-fetch.sh
t/t5514-fetch-multiple.sh [new file with mode: 0755]
t/t5516-fetch-push.sh
t/t5518-fetch-exit-status.sh
t/t5520-pull.sh
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.sh [new file with mode: 0755]
t/t5601-clone.sh
t/t5706-clone-branch.sh [new file with mode: 0755]
t/t6006-rev-list-format.sh
t/t6010-merge-base.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/t6020-merge-df.sh
t/t6028-merge-up-to-date.sh
t/t6030-bisect-porcelain.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/t6050-replace.sh [new file with mode: 0755]
t/t6120-describe.sh
t/t7002-grep.sh
t/t7003-filter-branch.sh
t/t7005-editor.sh
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/t7300-clean.sh
t/t7400-submodule-basic.sh
t/t7401-submodule-summary.sh
t/t7406-submodule-update.sh
t/t7407-submodule-foreach.sh [new file with mode: 0755]
t/t7408-submodule-reference.sh [moved from t/t7406-submodule-reference.sh with 100% similarity]
t/t7501-commit.sh
t/t7502-commit.sh
t/t7509-commit.sh [new file with mode: 0755]
t/t7600-merge.sh
t/t7604-merge-custom-message.sh
t/t7608-merge-messages.sh [new file with mode: 0755]
t/t7800-difftool.sh
t/t8003-blame.sh
t/t8005/iso8859-5.txt [deleted file]
t/t9001-send-email.sh
t/t9101-git-svn-props.sh
t/t9104-git-svn-follow-parent.sh
t/t9107-git-svn-migrate.sh
t/t9115-git-svn-dcommit-funky-renames.sh
t/t9120-git-svn-clone-with-percent-escapes.sh
t/t9130-git-svn-authors-file.sh
t/t9135-git-svn-moved-branch-empty-file.sh
t/t9138-git-svn-authors-prog.sh
t/t9143-git-svn-gc.sh
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/t9300-fast-import.sh
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
templates/Makefile
templates/hooks--post-receive.sample
templates/hooks--pre-commit.sample
test-date.c
test-delta.c
test-genrandom.c
test-parse-options.c
thread-utils.c
transport-helper.c [new file with mode: 0644]
transport.c
transport.h
unpack-file.c
unpack-trees.c
unpack-trees.h
update-server-info.c [deleted file]
upload-pack.c
usage.c
utf8.c
utf8.h
var.c
ws.c
wt-status.c
wt-status.h
xdiff/xutils.c

index 41c0b20a76ce0fd47c7cafc51777336ce99ce1b0..ac02a580daf07150b03194a5a0de522ac1e3415d 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
+/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-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.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
 *.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*
+*+
+/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..975e6758efa85c674207ffd6b400e3bbab2576a2 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -41,6 +41,7 @@ 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>
index d8edd904065fbc4bd06365ce378f57d4cd8f9f0d..1c3a9fead579a9b52037f1bbe245998db8a2f40b 100644 (file)
@@ -8,3 +8,4 @@ gitman.info
 howto-index.txt
 doc.dep
 cmds-*.txt
+manpage-base-url.xsl
index 06b0c57b95057b3df4f215f3191fd8e9fc473e6f..4797b2dc3522ccd2050ddefe990268fddb6b8a47 100644 (file)
@@ -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)))
@@ -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))
@@ -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 $<
 
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..4f29bab
--- /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
+  presense 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.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..e42f8b2
--- /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 overrided 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..5b49ea5
--- /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 typoes
+  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.
index 856047d16fd8d9936a6e5b954dc5bd084301b386..ee141c19add21c463d076e994ee68704eb13ba55 100644 (file)
@@ -1,8 +1,9 @@
 GIT v1.6.5 Release Notes
 ========================
 
-In git 1.7.0, which is planned to be the release after 1.6.5, "git push"
-into a branch that is currently checked out will be refused by default.
+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
@@ -31,21 +32,138 @@ 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)
 
-(developers)
+ * Human writable date format to various options, e.g. --since=yesterday,
+   master@{2000.09.17}, are taught to infer some omitted input properly.
 
-Fixes since v1.6.4
-------------------
+ * 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.
 
-# All of the fixes in v1.6.4.X maintenance series are included in this
-# release, unless otherwise noted.
+ * "git commit --dry-run $args" is a new recommended way to ask "what would
+   happen if I try to commit with these arguments."
 
-# Here are fixes that this release has, but have not been backported to
-# v1.6.4.X series.
+ * "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.txt b/Documentation/RelNotes-1.6.6.txt
new file mode 100644 (file)
index 0000000..04e205c
--- /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 transtion 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
+   ammount 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.
index c6f09f801a8dbb2f03e73ab5dd11c1139332fa5f..76173dbc5deaf1fdbff316b42e0a42ce61f4f32d 100644 (file)
@@ -113,10 +113,33 @@ 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 overwritting local changes.
+               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,
@@ -129,6 +152,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
@@ -154,9 +189,10 @@ core.autocrlf::
        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.
+       `LF` at the end of lines.  A file is considered
+       "text" (i.e. be subjected to the autocrlf mechanism) based on
+       the file's `crlf` attribute, or if `crlf` is unspecified,
+       based on the file's contents.  See linkgit:gitattributes[5].
 
 core.safecrlf::
        If true, makes git check if converting `CRLF` as controlled by
@@ -208,7 +244,11 @@ core.symlinks::
        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
@@ -365,16 +405,15 @@ Common unit suffixes of 'k', 'm', or 'g' are supported.
 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.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
@@ -401,13 +440,17 @@ core.whitespace::
        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).
+* `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
@@ -439,6 +482,19 @@ 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.  This ref is expected to contain files named
+       after the full SHA-1 of the commit they annotate.
++
+If such a file exists in the given ref, the referenced blob is read, and
+appended to the commit message, separated by a "Notes:" line.  If the
+given ref itself does not exist, it is not an error, but means that no
+notes should be printed.
++
+This setting defaults to "refs/notes/commits", and can be overridden by
+the `GIT_NOTES_REF` environment variable.
+
 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'
@@ -461,6 +517,14 @@ it will be treated as a shell command.  For example, defining
 executed from the top-level directory of a repository, which may
 not necessarily be the current directory.
 
+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
        as the '--whitespace' option. See linkgit:git-apply[1].
@@ -516,7 +580,7 @@ branch.<name>.merge::
 
 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.
 
@@ -571,10 +635,10 @@ 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.grep::
        When set to `always`, always highlight matches.  When `false` (or
@@ -605,7 +669,7 @@ color.interactive.<slot>::
        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::
@@ -643,6 +707,8 @@ color.ui::
 
 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
@@ -1066,6 +1132,14 @@ 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.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.
@@ -1113,7 +1187,7 @@ instaweb.port::
        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
@@ -1218,12 +1292,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
@@ -1289,6 +1371,11 @@ rebase.stat::
        Whether to show a diffstat of what changed upstream since the last
        rebase. False 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
@@ -1320,10 +1407,14 @@ receive.denyCurrentBranch::
 
 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].
@@ -1350,7 +1441,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
@@ -1364,6 +1461,10 @@ remote.<name>.tagopt::
        Setting this value to \--no-tags disables automatic tag following when
        fetching from remote <name>
 
+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
        <group>".  See linkgit:git-remote[1].
@@ -1484,6 +1585,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
index 9276faeb11aec2650393c56fe5edf2b571692dbd..8707d0e7404543d0565d72566a8db5946d9467ee 100644 (file)
@@ -14,7 +14,8 @@ 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[]
@@ -27,33 +28,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 +69,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` or `--numstat` 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,6 +110,13 @@ 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.
 
+--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::
        Show colored diff.
 
@@ -110,16 +140,19 @@ The regex can also be set via a diff driver or configuration option, see
 linkgit:gitattributes[1] or linkgit:git-config[1].  Giving it explicitly
 overrides any diff driver or configuration setting.  Diff drivers
 override configuration settings.
+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,16 +160,16 @@ 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.
@@ -147,6 +180,7 @@ override configuration settings.
 -C::
        Detect copies as well as renames.  See also `--find-copies-harder`.
 
+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 +192,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 +204,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 +218,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 +243,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 +268,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
index d313795fdbc420e3395adc42aebe82fabda037d4..ab6419fe6e44c7008cbff4e1fae48a70cfb82ac0 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::
        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.
+
+--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[]
@@ -47,10 +59,6 @@ endif::git-pull[]
        flag lets all tags and their associated objects be
        downloaded.
 
--k::
---keep::
-       Keep downloaded pack.
-
 -u::
 --update-head-ok::
        By default 'git-fetch' refuses to update the head which
@@ -60,7 +68,19 @@ endif::git-pull[]
        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.
+
+-v::
+--verbose::
+       Be verbose.
+endif::git-pull[]
index ab1943c71243df245f84ede7a69e4c666a8d8834..e93e606f458356275964d0d7b438ca772abba47c 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 [verse]
 'git add' [-n] [-v] [--force | -f] [--interactive | -i] [--patch | -p]
          [--edit | -e] [--all | [--update | -u]] [--intent-to-add | -N]
-         [--refresh] [--ignore-errors] [--] <filepattern>...
+         [--refresh] [--ignore-errors] [--] [<filepattern>...]
 
 DESCRIPTION
 -----------
@@ -72,9 +72,14 @@ 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
index 32e689b2bf184464b0923c3bb88a209b9b28a022..67ad5da9cc0e766882300e90ed114306cb3c9fa7 100644 (file)
@@ -11,9 +11,9 @@ SYNOPSIS
 [verse]
 'git am' [--signoff] [--keep] [--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] [-q | --quiet]
+        [--reject] [-q | --quiet] [--scissors | --no-scissors]
         [<mbox> | <Maildir>...]
 'git am' (--skip | --resolved | --abort)
 
@@ -39,6 +39,14 @@ OPTIONS
 --keep::
        Pass `-k` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]).
 
+-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.
@@ -65,6 +73,9 @@ 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>::
@@ -125,10 +136,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 735374d7df7e8d0e967dce7750f41aabff9a095f..c2528a76545a154eb42a430b59caf26e7aad3bef 100644 (file)
@@ -3,7 +3,7 @@ git-apply(1)
 
 NAME
 ----
-git-apply - Apply a patch on a git index file and/or a working tree
+git-apply - Apply a patch to files and/or to the index
 
 
 SYNOPSIS
@@ -13,14 +13,18 @@ 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.
 
 OPTIONS
 -------
@@ -33,7 +37,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,22 +51,22 @@ 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'
@@ -86,11 +90,13 @@ the information is read from the current index instead.
        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
@@ -106,8 +112,8 @@ the information is read from the current index instead.
        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.
@@ -143,12 +149,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,6 +219,10 @@ 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.
@@ -214,13 +232,13 @@ Submodules
 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.
 
index bc132c87e1a5b9f42ea1f2bbd4e3c46f67fc3852..e57979198ba880379056189860275724eec7fd3e 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...]
 
@@ -34,8 +34,11 @@ 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
 ---------------------
@@ -129,6 +134,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..6b7b2e5
--- /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's 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 commiter.
+
+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 63e7a42cb353e2cd0c8bbb6e83927bd1863a32cb..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
 ~~~~~~~~~~~~~~~~
@@ -319,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 ae201deb7af44c61e793ab408e916c5b6520800d..0e836809c20c83b8798fc77fa373f17fbf5f1eb6 100644 (file)
@@ -30,10 +30,8 @@ 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
@@ -76,6 +74,7 @@ OPTIONS
        based sha1 expressions such as "<branchname>@\{yesterday}".
 
 -f::
+--force::
        Reset <branchname> to <startpoint> if <branchname> exists
        already. Without `-f` 'git-branch' refuses to change an existing branch.
 
@@ -133,11 +132,13 @@ start-point is either a local or remote branch.
 --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.
@@ -146,9 +147,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.
@@ -209,6 +210,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..c3a066e60cb40d893287f813a4a007c4db42b1bb 100644 (file)
@@ -24,7 +24,7 @@ ssh, rsync, http) cannot be used.  This command provides support for
 'git-fetch' and 'git-pull' to operate by packaging objects and references
 in an archive at the originating machine, then importing those into
 another repository using 'git-fetch' and 'git-pull'
-after moving the archive by some means (i.e., by sneakernet).  As no
+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
index 0b7982ea76633e45b8d3073cd275ea74a757c0eb..0aeef24780f07e5d5fa021f6ebdaa7090a3dcdb7 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
 -----------
@@ -63,16 +64,31 @@ reference name expressions (see linkgit:git-rev-parse[1]):
 
 . 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 ad4b31e89218e857001fce69f32acbe85fc7539c..37c1810e3fc8424868333a22094107e99764fc37 100644 (file)
@@ -11,6 +11,7 @@ SYNOPSIS
 'git checkout' [-q] [-f] [-m] [<branch>]
 'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>]
 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
+'git checkout' --patch [<tree-ish>] [--] [<paths>...]
 
 DESCRIPTION
 -----------
@@ -25,7 +26,7 @@ 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
+When <paths> or --patch 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
@@ -45,9 +46,11 @@ file can be discarded to recreate 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.
@@ -113,6 +116,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
index be894af39ff6559affbf0617e4c5d8fe68c33fe8..9d291bdd266b49fe01b017dfce8e73dd288ad785 100644 (file)
@@ -27,8 +27,12 @@ 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::
+--force::
        If the git configuration specifies clean.requireForce as true,
        'git-clean' will refuse to run unless given -f or -n.
 
index b14de6c407b8bd0bc001c608ca4f26fc619abf3e..7ccd742a87db1541184868b794fa4ef597d04de8 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
@@ -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,22 +69,30 @@ 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::
@@ -92,7 +101,7 @@ then the cloned repository will become corrupt.
 
 --verbose::
 -v::
-       Display the progressbar, even in case the standard output is not
+       Display the progress bar, even in case the standard output is not
        a terminal.
 
 --no-checkout::
@@ -112,12 +121,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>::
@@ -139,6 +155,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,8 +171,8 @@ 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
+       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
index b5d81be7ecd60daa1a1d476441ca58e6b732d9ef..d227cec9ba566caa098c36e8c91b19a8a27fcae5 100644 (file)
@@ -8,8 +8,8 @@ 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>]
+'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run]
+          [(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author]
           [--allow-empty] [--no-verify] [-e] [--author=<author>]
           [--cleanup=<mode>] [--] [[-i | -o ]<file>...]
 
@@ -42,10 +42,9 @@ The content to be added can be specified in several ways:
    by one which files should be part of the commit, before finalizing the
    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'.
@@ -70,6 +69,11 @@ 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.
+
 -F <file>::
 --file=<file>::
        Take the commit message from the given file.  Use '-' to
@@ -198,6 +202,11 @@ 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.
+
 \--::
        Do not interpret any more arguments as options.
 
@@ -319,7 +328,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 785779e22122156bdc8c58a94d36edb66a8ee266..99a7c14700ffa06090534682283320fef5001815 100644 (file)
@@ -182,10 +182,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 +203,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:
 
index b231dbb947791bb4fc5cde552e8c736b3558ca0a..78b9808aa3a2f5fe2435ea188ac4e73508c2720a 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
@@ -108,7 +117,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,6 +126,13 @@ 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
 ---------------
 
index 96a6c51a4b83207fa23fc8f09cb02f57b1085ea4..8e9aed67d7f68c0234ee47dc147736bf3b8e5c4f 100644 (file)
@@ -31,7 +31,7 @@ OPTIONS
        Use the diff tool specified by <tool>.
        Valid merge tools are:
        kdiff3, kompare, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff,
-       ecmerge, diffuse, opendiff and araxis.
+       ecmerge, diffuse, opendiff, p4merge and araxis.
 +
 If a diff tool is not specified, 'git-difftool'
 will use the configuration variable `diff.tool`.  If the
index af2328d401cef30311996cf0376ad17c83c6c516..75b06f33e73497fbb8916ef02d70ca59f4aab2f6 100644 (file)
@@ -82,6 +82,14 @@ 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.
+
 [git-rev-list-args...]::
        A list of arguments, acceptable to 'git-rev-parse' and
        'git-rev-list', that specifies the specific objects and references
index c2f483a8d2aed8dc017f3172e2d5fff4bed2c450..288032c7b88f2f43a2965d5bdbffc8472f0ca281 100644 (file)
@@ -316,7 +316,7 @@ change to the project.
        data
        ('from' SP <committish> LF)?
        ('merge' SP <committish> LF)?
-       (filemodify | filedelete | filecopy | filerename | filedeleteall)*
+       (filemodify | filedelete | filecopy | filerename | filedeleteall | notemodify)*
        LF?
 ....
 
@@ -339,14 +339,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).
 
@@ -595,6 +594,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
index d3164c5c88db6b9e02a4186c398e19c425bc204b..9b9e5686e4d4e1ca41bba1c5a50715463975cad1 100644 (file)
@@ -10,11 +10,17 @@ SYNOPSIS
 --------
 '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
@@ -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..394a77a35f2019f5c86f9e90e94b88cf56da04ae 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>...]
 
@@ -158,7 +159,18 @@ 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.
+
+--remap-to-ancestor::
+       Rewrite refs to the nearest rewritten ancestor instead of
+       ignoring them.
++
+Normally, positive refs on the command line are only changed if the
+commit they point to was rewritten.  However, you can limit the extent
+of this rewriting by using linkgit:rev-list[1] arguments, e.g., path
+limiters.  Refs pointing to such excluded commits would then normally
+be ignored.  With this option, they are instead rewritten to point at
+the nearest ancestor that was not excluded.
 
 --prune-empty::
        Some kind of filters will generate empty commits, that left the tree
@@ -305,6 +317,16 @@ range in addition to the new branch name.  The new branch name will
 point to the top-most revision that a 'git-rev-list' of this range
 will print.
 
+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
index 1c24796d66d5aeaeeccfd152c69cddba1953fd6c..a586950b48bac6eda630383714c0ccd1b28820a8 100644 (file)
@@ -18,8 +18,8 @@ Takes the list of merged objects on stdin and produces a suitable
 commit message to be used for the merge commit, usually to be
 passed as the '<merge-message>' argument of 'git-merge'.
 
-This script is intended mostly for internal use by scripts
-automatically invoking 'git-merge'.
+This command is intended mostly for internal use by scripts
+automatically invoking 'git merge'.
 
 OPTIONS
 -------
index 687e66759817bc2f349e8a5a89aa2c6731237b23..f1fd0df08ae3067f8f5b41797e5575d465a859af 100644 (file)
@@ -43,28 +43,28 @@ There are two ways to specify which commits to operate on.
 
 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 +112,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,13 +121,13 @@ 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>]::
 --no-thread::
-       Controls addition of In-Reply-To and References headers to
+       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
+       first.  Also controls generation of the `Message-Id` header to
        reference.
 +
 The optional <style> argument can be either `shallow` or `deep`.
@@ -136,16 +136,16 @@ 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.
 +
-The default is --no-thread, unless the 'format.thread' configuration
-is set.  If --thread is specified without a style, it defaults to the
+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 hreading, you
-will want to ensure that threading is disabled for 'git send-email'.
+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.
 
@@ -160,16 +160,16 @@ will want to ensure that threading is disabled for 'git send-email'.
        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.
 
 --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
index 287c4fc5e07ea753c2a3d93bf6480f41aac8c9af..6fe9484da325fa1f72809937df263574ffe6fb9b 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
 -----------
@@ -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
index b292e9843aa9da86cd44bd07d3ce35053be32177..4cd9cdf9056fe78b3acbbf62aa080b1e47907b0a 100644 (file)
@@ -61,7 +61,7 @@ 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::
@@ -106,7 +106,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
@@ -120,7 +120,7 @@ Notes
 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
+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
index b753c9d76fde51198eb7ef34b9ff298095fb1f72..8c700200f55e1a92545cfb8218c8c1ba2cedee68 100644 (file)
@@ -17,6 +17,7 @@ SYNOPSIS
           [-l | --files-with-matches] [-L | --files-without-match]
           [-z | --null]
           [-c | --count] [--all-match]
+          [--max-depth <depth>]
           [--color | --no-color]
           [-A <post-context>] [-B <pre-context>] [-C <context>]
           [-f <file>] [-e] <pattern>
@@ -47,6 +48,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
diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt
new file mode 100644 (file)
index 0000000..67aec06
--- /dev/null
@@ -0,0 +1,178 @@
+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 protcol
+and the backwards-compatible dumb HTTP protocol, as well as clients
+pushing using the smart HTTP protocol.
+
+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 older Git clients which 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
+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 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 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 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..f081b24d9d8233677388d437b8f7e6d9402b261b 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
@@ -74,6 +74,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).
+
 --
 
 
index 22da21a54f625c434216945889127ec283d3d09f..0771f254436c9024b935ef745c3bcd384bc18465 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 and webrick are supported.
        (Default: lighttpd)
 
 -m::
index 34cf4e5811d1a6f46fcbd333a2ff48c200eadff8..3d79de11ec36d0212610a4bec68789bdeec6c1fe 100644 (file)
@@ -37,8 +37,12 @@ include::diff-options.txt[]
        and <until>, see "SPECIFYING REVISIONS" section in
        linkgit:git-rev-parse[1].
 
---decorate::
-       Print out the ref names of any commits that are shown.
+--decorate[=short|full]::
+       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
index 057a021eb50899ed5fe1ee5a7b6c59e7fc27becf..625723e41fa072523fa850e37534677cfe8af631 100644 (file)
@@ -44,12 +44,14 @@ OPTIONS
 
 -o::
 --others::
-       Show other files in the output
+       Show other (i.e. untracked) files in the output
 
 -i::
 --ignored::
-       Show ignored files in the output.
-       Note that this also reverses any exclude list present.
+       Show only ignored files in the output. When showing files in the
+       index, print only those matched by an exclude pattern. When
+       showing "other" files, show only those matched by an exclude
+       pattern.
 
 -s::
 --stage::
index 8d95aaa30441c36a019e9d3d78ec451fbd40fdaf..b81ac98cf0d95ab961b228e514c8fdabc152a21e 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
@@ -32,6 +32,11 @@ OPTIONS
        munging, and is most useful when used to read back
        '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
@@ -49,6 +54,25 @@ conversion, even with this flag.
 -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 767486c770afd385d118ee5f9a6f9cd3ad0a2d73..ce5b369985c254ec5d986aa3dd250828cf3cc4cb 100644 (file)
@@ -8,12 +8,12 @@ git-merge-base - Find as good common ancestors as possible for a merge
 
 SYNOPSIS
 --------
-'git merge-base' [--all] <commit> <commit>...
+'git merge-base' [-a|--all] <commit> <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
@@ -27,8 +27,13 @@ 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.
 
index c04ae739edd409307d286fe31e6f87e41f977eac..e886c2ef543501deea84909b2e88fb163a1c9d2b 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git merge' [-n] [--stat] [--no-commit] [--squash] [-s <strategy>]...
-       [-m <msg>] <remote> <remote>...
+       [-m <msg>] <remote>...
 'git merge' <msg> HEAD <remote>...
 
 DESCRIPTION
@@ -28,9 +28,10 @@ OPTIONS
 include::merge-options.txt[]
 
 -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.
+       Set the commit message to be used for the merge commit (in
+       case one is created). The 'git fmt-merge-msg' command can be
+       used to give a good default for automated 'git merge'
+       invocations.
 
 <remote>...::
        Other branch heads to merge into our branch.  You need at
@@ -49,8 +50,8 @@ include::merge-config.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.
+       supported options are the same as those of 'git merge', but option
+       values containing whitespace characters are currently not supported.
 
 HOW MERGE WORKS
 ---------------
@@ -211,6 +212,39 @@ You can work through the conflict with a number of tools:
    common ancestor, 'git show :2:filename' shows the HEAD
    version and 'git show :3:filename' shows the remote 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.
+
+
 SEE ALSO
 --------
 linkgit:git-fmt-merge-msg[1], linkgit:git-pull[1],
index 68ed6c095664b8f06635ba7eaa85b85086b24280..4a6f7f3a2d189c40e4abfe4f74178f79d1c563fd 100644 (file)
@@ -27,7 +27,7 @@ OPTIONS
        Use the merge resolution program specified by <tool>.
        Valid merge tools are:
        kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge,
-       diffuse, tortoisemerge, opendiff and araxis.
+       diffuse, tortoisemerge, opendiff, p4merge and araxis.
 +
 If a merge resolution program is not specified, 'git-mergetool'
 will use the configuration variable `merge.tool`.  If the
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
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
new file mode 100644 (file)
index 0000000..94cceb1
--- /dev/null
@@ -0,0 +1,60 @@
+git-notes(1)
+============
+
+NAME
+----
+git-notes - Add/inspect commit notes
+
+SYNOPSIS
+--------
+[verse]
+'git-notes' (edit [-F <file> | -m <msg>] | show) [commit]
+
+DESCRIPTION
+-----------
+This command allows you to add notes to commit messages, without
+changing the commit.  To discern these notes from the message stored
+in the commit object, the notes are indented like the message, after
+an unindented line saying "Notes:".
+
+To disable commit notes, you have to set the config variable
+core.notesRef to the empty string.  Alternatively, you can set it
+to a different ref, something like "refs/notes/bugzilla".  This setting
+can be overridden by the environment variable "GIT_NOTES_REF".
+
+
+SUBCOMMANDS
+-----------
+
+edit::
+       Edit the notes for a given commit (defaults to HEAD).
+
+show::
+       Show the notes for a given commit (defaults to HEAD).
+
+
+OPTIONS
+-------
+-m <msg>::
+       Use the given note message (instead of prompting).
+       If multiple `-m` (or `-F`) options are given, their
+       values are concatenated as separate paragraphs.
+
+-F <file>::
+       Take the note message from the given file.  Use '-' to
+       read the note message from the standard input.
+       If multiple `-F` (or `-m`) options are given, their
+       values are concatenated as separate paragraphs.
+
+
+Author
+------
+Written by Johannes Schindelin <johannes.schindelin@gmx.de>
+
+Documentation
+-------------
+Documentation by Johannes Schindelin
+
+GIT
+---
+Part of the linkgit:git[7] suite
index 2e4992970e84a1789cb97d35969976d12e5a30e1..f54d433d36b50184bd83f217c310935d1bbb541f 100644 (file)
@@ -9,8 +9,9 @@ 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]
+'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
 
@@ -137,7 +138,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
@@ -146,6 +147,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.
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 7578623edba9e2ddc5232f1a981bcb297182638d..b93201158fa6fda914b6093af5911b7884328838 100644 (file)
@@ -26,6 +26,10 @@ Also note that options meant for 'git-pull' itself and underlying
 
 OPTIONS
 -------
+
+Options related to merging
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
 include::merge-options.txt[]
 
 :git-pull: 1
@@ -47,6 +51,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,54 +138,13 @@ $ 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
index 2653388fd8f5fac9292ece83a116accdf0978902..52c0538df528ecbb5b755a2e75fc1715762bfc00 100644 (file)
@@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects
 SYNOPSIS
 --------
 [verse]
-'git push' [--all | --mirror | --tags] [--dry-run] [--receive-pack=<git-receive-pack>]
+'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
           [--repo=<repository>] [-f | --force] [-v | --verbose]
           [<repository> <refspec>...]
 
@@ -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
@@ -82,6 +82,7 @@ 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.
 
@@ -137,6 +138,11 @@ useful if you write an alias or script around 'git-push'.
 --verbose::
        Run verbosely.
 
+-q::
+--quiet::
+       Suppress all output, including the listing of updated refs,
+       unless an error occurs.
+
 include::urls-remotes.txt[]
 
 OUTPUT
@@ -170,10 +176,10 @@ 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
+       `<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
+       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
@@ -195,6 +201,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
 --------
 
@@ -255,9 +347,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..a10ce4ba40cc509a0f3a8f39551310fdd9231e71 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>]
+               <tree-ish1> [<tree-ish2> [<tree-ish3>]]
 
 
 DESCRIPTION
@@ -141,7 +144,7 @@ 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'
 the following:
index db1b71d24846e4561d99bed095345eb9580b0632..ca5e1e8653be7a1d2b0911ec572ff0a85300ebb9 100644 (file)
@@ -228,13 +228,23 @@ 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.
-       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.
+       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.
 
 -q::
 --quiet::
@@ -268,8 +278,9 @@ OPTIONS
        exit with the message "Current branch is up to date" in such a
        situation.
 
+--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.
 
@@ -367,14 +378,17 @@ By replacing the command "pick" with the command "edit", you can tell
 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.
 
-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
index 514f03c97903aa0be41a4a8f0df236ccb68280b0..cb5f4052806d81b04d4a096d3697e0230d13e8ed 100644 (file)
@@ -20,7 +20,7 @@ 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?)
diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt
new file mode 100644 (file)
index 0000000..5cfdc0c
--- /dev/null
@@ -0,0 +1,174 @@
+git-remote-helpers(1)
+=====================
+
+NAME
+----
+git-remote-helpers - Helper programs for interoperation with remote git
+
+SYNOPSIS
+--------
+'git remote-<transport>' <remote>
+
+DESCRIPTION
+-----------
+
+These programs are normally not used directly by end users, but are
+invoked by various git programs that interact with remote repositories
+when the repository they would operate on will be accessed using
+transport code not linked into the main git binary. Various particular
+helper programs will behave as documented here.
+
+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.
+
+'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. After the
+       complete list, outputs 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>::
+       Set 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 correct
+       for it).  Options should be set before other commands,
+       and may how those commands behave.
++
+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, and the batch is 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 <src> commit or branch locally 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.
++
+Supported if the helper has the "import" 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'::
+       This helper supports the 'fetch' command.
+
+'option'::
+       This helper supports the option command.
+
+'push'::
+       This helper supports the 'push' command.
+
+'import'::
+       This helper supports the 'import' command.
+
+'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>::
+       Change the level of messages displayed by the helper.
+       When N is 0 the end-user has asked the process to be
+       quiet, and the helper should produce only error output.
+       N of 1 is the default level of verbosity, higher values
+       of N correspond to the number of -v flags passed on the
+       command line.
+
+'option progress' \{'true'|'false'\}::
+       Enable (or disable) progress messages displayed by the
+       transport helper during a command.
+
+'option depth' <depth>::
+       Deepen 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.
+
+Documentation
+-------------
+Documentation by Daniel Barkalow.
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 9e2b4eaa385db66ffe0c547f0452d29e9e3dc484..c272c92d4bef4d451505634a412eaf3a10762d95 100644 (file)
@@ -13,10 +13,10 @@ SYNOPSIS
 'git remote add' [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>
 'git remote rename' <old> <new>
 'git remote rm' <name>
-'git remote set-head' <name> [-a | -d | <branch>]
-'git remote show' [-n] <name>
+'git remote set-head' <name> (-a | -d | <branch>)
+'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 +30,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
@@ -114,14 +115,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]).
diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt
new file mode 100644 (file)
index 0000000..65a0da5
--- /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 abb25d1c00c97144b1f3709e408fe9cad613e623..2d27e405a39c77f1a7d7507db2f063a4819bec47 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 [verse]
 'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]
 'git reset' [-q] [<commit>] [--] <paths>...
+'git reset' --patch [<commit>] [--] [<paths>...]
 
 DESCRIPTION
 -----------
@@ -23,8 +24,9 @@ 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.
 
-The second form with 'paths' is used to revert selected paths in
-the index from a given commit, without moving HEAD.
+The second and third forms with 'paths' and/or --patch are used to
+revert selected paths in the index from a given commit, without moving
+HEAD.
 
 
 OPTIONS
@@ -50,6 +52,15 @@ OPTIONS
        and updates the files that are different between the named commit
        and the current commit in the working tree.
 
+-p::
+--patch::
+       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]).
+
 -q::
        Be quiet, only report errors.
 
@@ -139,7 +150,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 +161,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
index a765cfa4d208ed42a9539c3f26192b2ba4ec05a5..3341d1b62f34fa99afcdab91da1191ceacc2c24f 100644 (file)
@@ -51,20 +51,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
@@ -84,7 +90,7 @@ 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
index d6b192b7b9d6c7d67703b5e2984a9fed7ffcea3b..ced35b2f532dde3580f162a0c23b642002a0e508 100644 (file)
@@ -60,8 +60,8 @@ 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.
 
 --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
@@ -84,7 +84,7 @@ See the CONFIGURATION section for 'sendemail.multiedit'.
 --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.
 
@@ -108,9 +108,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>::
@@ -142,8 +143,9 @@ 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); symbolic port names (e.g. "submission" instead of 465)
+       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.
 
@@ -170,8 +172,8 @@ Automating
        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
index 399821832c2a5cd6a718a7dc37a87e6b5bc0b213..5a04c6eaf78adb8c6ccff573d873392e19640fdd 100644 (file)
@@ -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 89ec5364ecd447f6a7836d9c7f582dbd1d864807..734336119c6b1f7ea8241f0404eaa3ba2ae10f69 100644 (file)
@@ -8,11 +8,12 @@ git-show-branch - Show branches and their commits
 SYNOPSIS
 --------
 [verse]
-'git show-branch' [--all] [--remotes] [--topo-order | --date-order]
-               [--current] [--color | --no-color]
+'git show-branch' [-a|--all] [-r|--remotes] [--topo-order | --date-order]
+               [--current] [--color | --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>]
 
 DESCRIPTION
@@ -81,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
index f4429bdc6863ce1d115b47282f2c04a4d9093d29..70f400b2664df422160195a9615d566152a1b877 100644 (file)
@@ -8,7 +8,7 @@ git-show-ref - List references in a local repository
 SYNOPSIS
 --------
 [verse]
-'git show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference]
+'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
@@ -30,7 +30,6 @@ the `.git` directory.
 OPTIONS
 -------
 
--h::
 --head::
 
        Show the HEAD reference.
index 1c64a02fe576a6bdad2afcbc96391ce0c2d541c4..3f14b727b899fbf3b4f7cb6513bd1b14e5938160 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
 'git stash' drop [-q|--quiet] [<stash>]
 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
 'git stash' branch <branchname> [<stash>]
-'git stash' [save [--keep-index] [-q|--quiet] [<message>]]
+'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
 'git stash' clear
 'git stash' create
 
@@ -42,15 +42,27 @@ is also possible).
 OPTIONS
 -------
 
-save [--keep-index] [-q|--quiet] [<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>]::
 
@@ -114,7 +126,8 @@ 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 [-q|--quiet] [<stash>]::
 
@@ -217,6 +230,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 683ba1a1ebe080e6e093d27686068823448e459c..4ef70c42ebf512ee6dc946ca2ceee284b3211b54 100644 (file)
@@ -10,13 +10,13 @@ SYNOPSIS
 --------
 [verse]
 'git submodule' [--quiet] add [-b branch]
-             [--reference <repository>] [--] <repository> <path>
-'git submodule' [--quiet] status [--cached] [--] [<path>...]
+             [--reference <repository>] [--] <repository> [<path>]
+'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
 'git submodule' [--quiet] init [--] [<path>...]
 'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase]
-             [--reference <repository>] [--] [<path>...]
-'git submodule' [--quiet] summary [--summary-limit <n>] [commit] [--] [<path>...]
-'git submodule' [--quiet] foreach <command>
+             [--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>...]
 
 
@@ -69,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 ./
@@ -100,6 +104,9 @@ status::
        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'.
++
+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
@@ -122,21 +129,31 @@ update::
 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 and $sha1:
+       $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.
        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.
@@ -169,6 +186,11 @@ OPTIONS
        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.
@@ -209,6 +231,12 @@ OPTIONS
 *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 22a0389f1ed12a79f0b5784c30e0b2be9716c170..4cdca0d87435b4995e5c051750a6dd02720006f8 100644 (file)
@@ -102,9 +102,6 @@ COMMANDS
        Store Git commit times in the local timezone instead of UTC.  This
        makes 'git log' (even without --date=local) show the same times
        that `svn log` would in the local timezone.
-
---parent;;
-       Fetch only from the SVN parent of the current HEAD.
 +
 This doesn't interfere with interoperating with the Subversion
 repository you cloned from, but if you wish for your local Git
@@ -112,6 +109,9 @@ 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.
@@ -320,6 +320,13 @@ 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
@@ -735,6 +742,16 @@ 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.
 
+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
index 210fde03a12cd757769f81754e789a2a5934f02c..63925388073b17c3087d32813979513580adfa2a 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
index fa733214ab0259dec1c866e9cb629dd0e7b69f3a..299b04f726b2e7662fb7db64169726eec82a5c14 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,6 +87,12 @@ 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
@@ -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 25e0bbea86caf1234da1746d4a2082cfb80129bd..6052484ab9e28d5565067885b1c4ed5667e8e90f 100644 (file)
@@ -99,6 +99,10 @@ 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.
+
 -g::
 --again::
        Runs 'git-update-index' itself on the paths whose index
@@ -308,7 +312,7 @@ 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
index e2f4c0901bcb4bcc5361e400ff40d70062c77ae6..ef6aa81872eb9e67eaf8183cfd5359d97e6c772b 100644 (file)
@@ -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..97f7f9165eca2311e4806e9cab8ef18c8c94f7d5 100644 (file)
@@ -8,7 +8,7 @@ git-verify-pack - Validate packed git archive files
 
 SYNOPSIS
 --------
-'git verify-pack' [-v] [--] <pack>.idx ...
+'git verify-pack' [-v|--verbose] [--] <pack>.idx ...
 
 
 DESCRIPTION
@@ -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 26d3850e7317c22dcf0999e0c4a6afe9a5ea2e03..c8899d528adfe37459b9301e16faab90e7a7e63d 100644 (file)
@@ -12,7 +12,8 @@ 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.
 
index 5fd5953e29d1514388421c0d729f46f8a54d3dba..352c23019f7dad59c735f88c0606cf056c84a578 100644 (file)
@@ -10,7 +10,7 @@ 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]
     [--help] COMMAND [ARGS]
 
@@ -43,9 +43,30 @@ 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.4/git.html[documentation for release 1.6.4]
+* link:v1.6.6/git.html[documentation for release 1.6.6]
 
 * release notes for
+  link:RelNotes-1.6.6.txt[1.6.6].
+
+* link:v1.6.5.7/git.html[documentation for release 1.6.5.7]
+
+* release notes for
+  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]
@@ -228,6 +249,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
 ---------------------
@@ -327,7 +352,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[]
index aaa073efc80522a649f17d60127aae8cc85b0b3b..5a45e51890b1f4b13db3d8082da83731dc76ceac 100644 (file)
@@ -197,6 +197,25 @@ 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
+------------------------
+
 
 Interaction between checkin/checkout attributes
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -404,7 +423,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::
 
@@ -560,6 +579,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
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
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..f762dca440d264cc8c9ccf34ad217e174a9bdf77 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
@@ -185,7 +185,7 @@ 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
@@ -602,7 +602,7 @@ $ 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
@@ -993,7 +993,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 +1003,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.
@@ -1186,9 +1186,9 @@ $ 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.
@@ -1204,11 +1204,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
 ------------
 
@@ -1237,8 +1237,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,8 +1253,8 @@ 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
 ------------
 
@@ -1283,8 +1283,8 @@ 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
 ------------
 
@@ -1328,7 +1328,7 @@ into it later. Obviously, this repository creation needs to be
 done only once.
 
 [NOTE]
-'git-push' uses a pair of programs,
+'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.
index 1c736738ccc91b6929226a27e02716221ec3ef05..29eeae77ca8716a9b07362f164104e3afee4f192 100644 (file)
@@ -26,8 +26,11 @@ 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
 parameter, the name of the file that holds the proposed commit
@@ -43,7 +46,7 @@ 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
 invoked after the patch is applied, but before a commit is made.
@@ -58,7 +61,7 @@ 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,
 and is invoked after the patch is applied and a commit is made.
@@ -67,7 +70,7 @@ This hook is meant primarily for notification, and cannot affect
 the outcome of 'git-am'.
 
 pre-commit
-----------
+~~~~~~~~~~
 
 This hook is invoked by 'git-commit', and can be bypassed
 with `\--no-verify` option.  It takes no parameter, and is
@@ -84,7 +87,7 @@ 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
 default log message, and before the editor is started.
@@ -109,7 +112,7 @@ 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
 with `\--no-verify` option.  It takes a single parameter, the
@@ -126,7 +129,7 @@ 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
 parameter, and is invoked after a commit is made.
@@ -135,14 +138,14 @@ This hook is meant primarily for notification, and cannot affect
 the outcome of 'git-commit'.
 
 pre-rebase
-----------
+~~~~~~~~~~
 
 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
 worktree.  The hook is given three parameters: the ref of the previous HEAD,
@@ -160,7 +163,7 @@ 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'
 is done on a local repository.  The hook takes a single parameter, a status
@@ -175,7 +178,7 @@ 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.
@@ -204,7 +207,7 @@ 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.
@@ -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
@@ -242,12 +245,12 @@ Both standard output and standard error output are forwarded to
 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.
@@ -277,7 +280,7 @@ 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.
@@ -308,7 +311,7 @@ Both standard output and standard error output are forwarded to
 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'
index 2b021e3c155013965a383a2188fd1c380d379b62..065441df644b6efc2883bec87fee4941788312ef 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
 ---------------------
 
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 e70d8a31e7b05e8efc70c6a56f476324065d57a6..8c32da6deb05b5da700a5bd0a4281bf862b23f2c 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:
 
 ------------------------------------------------
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" ;;
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 c0f96e70708cab74a13ec8696db003289f99b455..a403155052299dd0aaafd6bdfe0fec92d0d0ac7c 100644 (file)
@@ -23,7 +23,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", "araxis", 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 adadf8e4bf309a2fd2ec8efbeff09b410ca7b041..fec33943058ea3e139aff0b898ef55a677d4067e 100644 (file)
@@ -1,43 +1,42 @@
--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 (except for the merge information),
        but do not actually make a commit or
        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>::
        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).
+
+--summary::
+--no-summary::
+       Synonyms to --stat and --no-stat; these are deprecated and will be
+       removed in the future.
+
+-q::
+--quiet::
+       Operate quietly.
+
+-v::
+--verbose::
+       Be verbose.
index 4365b7e8420fa96d6cbfa14a5aa49d956ba2de16..42910a3d5e0229f471c5bceb0c36f184d17ebb08 100644 (file)
@@ -29,8 +29,9 @@ 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.
 
index 2a845b1e57590a6f18f05fd3efa858b736c1071a..53a9168ba7d8959e65c2442f6a078155d6f24c71 100644 (file)
@@ -123,6 +123,10 @@ The placeholders are:
 - '%s': subject
 - '%f': sanitized subject line, suitable for a filename
 - '%b': 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
@@ -131,6 +135,22 @@ The placeholders are:
 - '%m': left, right or boundary mark
 - '%n': newline
 - '%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.
 
 * 'tformat:'
 +
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..0551ebdfafb3f41dd0e31d3c74ce42fff33e38e1 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]
index bf66116d61af4f16cff59ed1563f38736a14ecce..1f57aed337e87e8bd2b175ef3fc807288a8256bf 100644 (file)
@@ -243,12 +243,23 @@ endif::git-rev-list[]
        Pretend as if all the refs in `$GIT_DIR/refs/remotes` are listed
        on the command line as '<commit>'.
 
-ifdef::git-rev-list[]
+ifndef::git-rev-list[]
+--bisect::
+
+       Pretend as if the bad bisection ref `$GIT_DIR/refs/bisect/bad`
+       was listed and as if it was followed by `--not` and the good
+       bisection refs `$GIT_DIR/refs/bisect/good-*` on the command
+       line.
+endif::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
@@ -536,7 +547,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
+`$GIT_DIR/refs/bisect/bad` is added to the included commits (if it
+exists) and the good bisection refs `$GIT_DIR/refs/bisect/good-*` are
+added to the excluded commits (if they exist). Thus, supposing there
+are no refs in `$GIT_DIR/refs/bisect/`, if
 
 -----------------------------------------------------------------------
        $ git rev-list --bisect foo ^bar ^baz
@@ -556,22 +571,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
+`$GIT_DIR/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 `$GIT_DIR/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
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
index 2efe7a40be641bc2532c139637fe02e534ea1152..b26c28133c143b23acf28fc1a3a06d16c10c65f8 100644 (file)
@@ -35,12 +35,32 @@ 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 diagnistic 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
@@ -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`
 
index 7438149249364ca8837811771b072b20990b3a5d..a0e0f850f83fe164dd7c1ca87d001fe485ba2ec2 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.
 
@@ -253,3 +253,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 e3ddf912841298d6317a682a29cbaf628e86f156..55b728632cb7fa18de0d1dc070f5007acef053e0 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.
+
+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..7950eeeda4447808c937b6ddcaa9ae1686e2ed5c 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 paramater, 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 its 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, its 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 it's objects), then the client will send
+a 'done' command.  The 'done' command signals to the server that the client
+is ready to receive it's 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 it's 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: 003aACK 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: 000aunpack ok\n
+   S: 0014ok refs/heads/debug\n
+   S: 0026ng 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..1892d3e
--- /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 its 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 refering 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 an 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 5355ebc0f39114823f830e0651078a99f0ac2e70..d813ceb7239bc2d22eb0af4ad33a6e1be8408787 100644 (file)
@@ -67,3 +67,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 0b88a51d0b192a3dbc2ec0fe73a32860247377a1..b169836684a56aae176875effa5cc4e6a619de80 100644 (file)
@@ -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 independant 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.
 
@@ -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
@@ -4131,7 +4147,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
@@ -4275,7 +4291,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 d7d9a9a063f6c7dbab5c9a14008b9a45e7f3379b..1628d986fe44aabf33d7b9e88082b52cb2640986 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.6.4.GIT
+DEF_VER=v1.6.6
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index ae7f7508f8e8cffeb930c820e068ba70dabff7bd..be504c95e1f092edaf524064d49ba30adc57dc9b 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
 
@@ -48,32 +52,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).
+
+       - "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).
 
-       - "ssh" is used to push and pull over the net
+       - "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
index daf4296706c4c9c524b6ec4434cadcf95881ea25..fd4919c0ad86d13617b73eddc31d7447f2f74485 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,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://
@@ -84,18 +84,16 @@ 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 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_CRYPTO_WITH_SSL if you need -lcrypto when using -lssl (Darwin).
 #
-# 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).
 #
@@ -155,13 +153,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
@@ -198,6 +206,21 @@ all::
 # 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 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'
 
 GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -210,6 +233,12 @@ 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
@@ -250,6 +279,9 @@ lib = lib
 # DESTDIR=
 pathsep = :
 
+# JavaScript minifier invocation that can function as filter
+JSMIN =
+
 # default configuration for gitweb
 GITWEB_CONFIG = gitweb_config.perl
 GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf
@@ -265,6 +297,11 @@ GITWEB_HOMETEXT = indextext.html
 GITWEB_CSS = gitweb.css
 GITWEB_LOGO = git-logo.png
 GITWEB_FAVICON = git-favicon.png
+ifdef JSMIN
+GITWEB_JS = gitweb.min.js
+else
+GITWEB_JS = gitweb.js
+endif
 GITWEB_SITE_HEADER =
 GITWEB_SITE_FOOTER =
 
@@ -306,6 +343,7 @@ LIB_H =
 LIB_OBJS =
 PROGRAMS =
 SCRIPT_PERL =
+SCRIPT_PYTHON =
 SCRIPT_SH =
 TEST_PROGRAMS =
 
@@ -319,6 +357,7 @@ 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-notes.sh
 SCRIPT_SH += git-parse-remote.sh
 SCRIPT_SH += git-pull.sh
 SCRIPT_SH += git-quiltimport.sh
@@ -343,6 +382,7 @@ SCRIPT_PERL += git-svn.perl
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
+         $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
          git-instaweb
 
 # Empty...
@@ -352,6 +392,7 @@ EXTRA_PROGRAMS =
 PROGRAMS += $(EXTRA_PROGRAMS)
 PROGRAMS += git-fast-import$X
 PROGRAMS += git-hash-object$X
+PROGRAMS += git-imap-send$X
 PROGRAMS += git-index-pack$X
 PROGRAMS += git-merge-index$X
 PROGRAMS += git-merge-tree$X
@@ -361,9 +402,9 @@ 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
+PROGRAMS += git-http-backend$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.
@@ -383,7 +424,8 @@ 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
@@ -396,12 +438,17 @@ 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
 
+LIB_H += advice.h
 LIB_H += archive.h
 LIB_H += attr.h
 LIB_H += blob.h
@@ -409,6 +456,7 @@ LIB_H += builtin.h
 LIB_H += cache.h
 LIB_H += cache-tree.h
 LIB_H += commit.h
+LIB_H += compat/bswap.h
 LIB_H += compat/cygwin.h
 LIB_H += compat/mingw.h
 LIB_H += csum-file.h
@@ -429,6 +477,7 @@ 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 += object.h
 LIB_H += pack.h
 LIB_H += pack-refs.h
@@ -449,6 +498,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
@@ -459,6 +509,7 @@ LIB_H += utf8.h
 LIB_H += wt-status.h
 
 LIB_OBJS += abspath.o
+LIB_OBJS += advice.o
 LIB_OBJS += alias.o
 LIB_OBJS += alloc.o
 LIB_OBJS += archive.o
@@ -512,6 +563,7 @@ 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 += object.o
 LIB_OBJS += pack-check.o
 LIB_OBJS += pack-refs.o
@@ -532,6 +584,7 @@ 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 += revision.o
 LIB_OBJS += run-command.o
@@ -545,10 +598,12 @@ 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
@@ -588,7 +643,6 @@ 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
@@ -621,6 +675,7 @@ 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
@@ -638,6 +693,7 @@ 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-update-server-info.o
 BUILTIN_OBJS += builtin-upload-archive.o
 BUILTIN_OBJS += builtin-verify-pack.o
 BUILTIN_OBJS += builtin-verify-tag.o
@@ -706,6 +762,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)
@@ -729,6 +786,7 @@ ifeq ($(uname_S),SunOS)
        NO_MKSTEMPS = YesPlease
        NO_REGEX = YesPlease
        NO_EXTERNAL_GREP = YesPlease
+       THREADED_DELTA_SEARCH = YesPlease
        ifeq ($(uname_R),5.7)
                NEEDS_RESOLV = YesPlease
                NO_IPV6 = YesPlease
@@ -751,9 +809,6 @@ ifeq ($(uname_S),SunOS)
                NO_C99_FORMAT = YesPlease
                NO_STRTOUMAX = YesPlease
        endif
-       ifdef NO_IPV6
-               NEEDS_RESOLV = YesPlease
-       endif
        INSTALL = /usr/ucb/install
        TAR = gtar
        BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ -DHAVE_ALLOCA_H
@@ -769,12 +824,15 @@ ifeq ($(uname_O),Cygwin)
        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
@@ -838,11 +896,18 @@ ifeq ($(uname_S),IRIX)
        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_EXTERNAL_GREP = UnfortunatelyYes
        SNPRINTF_RETURNS_BOGUS = YesPlease
        SHELL_PATH = /usr/gnu/bin/bash
        NEEDS_LIBGEN = YesPlease
+       THREADED_DELTA_SEARCH = YesPlease
 endif
 ifeq ($(uname_S),IRIX64)
        NO_SETENV=YesPlease
@@ -851,11 +916,18 @@ ifeq ($(uname_S),IRIX64)
        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_EXTERNAL_GREP = UnfortunatelyYes
        SNPRINTF_RETURNS_BOGUS = YesPlease
        SHELL_PATH=/usr/gnu/bin/bash
        NEEDS_LIBGEN = YesPlease
+       THREADED_DELTA_SEARCH = YesPlease
 endif
 ifeq ($(uname_S),HP-UX)
        NO_IPV6=YesPlease
@@ -870,17 +942,66 @@ ifeq ($(uname_S),HP-UX)
        NO_SYS_SELECT_H = YesPlease
        SNPRINTF_RETURNS_BOGUS = YesPlease
 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_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_PTHREADS = YesPlease
+       BLK_SHA1 = 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_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -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
+       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
+       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
@@ -904,6 +1025,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        UNRELIABLE_FSTAT = UnfortunatelyYes
        OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
        NO_REGEX = YesPlease
+       BLK_SHA1 = YesPlease
        COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o
@@ -922,10 +1044,6 @@ else
        NO_PTHREADS = YesPlease
 endif
 endif
-ifneq (,$(findstring arm,$(uname_M)))
-       ARM_SHA1 = YesPlease
-       NO_MKSTEMPS = YesPlease
-endif
 
 -include config.mak.autogen
 -include config.mak
@@ -979,9 +1097,7 @@ else
        else
                CURL_LIBCURL = -lcurl
        endif
-       BUILTIN_OBJS += builtin-http-fetch.o
-       EXTLIBS += $(CURL_LIBCURL)
-       LIB_OBJS += http.o http-walker.o
+       PROGRAMS += git-remote-curl$X git-http-fetch$X
        curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)
        ifeq "$(curl_check)" "070908"
                ifndef NO_EXPAT
@@ -1006,7 +1122,6 @@ EXTLIBS += -lz
 
 ifndef NO_POSIX_ONLY_PROGRAMS
        PROGRAMS += git-daemon$X
-       PROGRAMS += git-imap-send$X
 endif
 ifndef NO_OPENSSL
        OPENSSL_LIBSSL = -lssl
@@ -1016,9 +1131,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
@@ -1167,23 +1285,18 @@ 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
+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
 else
        SHA1_HEADER = <openssl/sha.h>
        EXTLIBS += $(LIB_4_CRYPTO)
 endif
 endif
-endif
 ifdef NO_PERL_MAKEMAKER
        export NO_PERL_MAKEMAKER
 endif
@@ -1241,6 +1354,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  =
 
@@ -1257,6 +1374,7 @@ ifndef V
        QUIET_LINK     = @echo '   ' LINK $@;
        QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
        QUIET_GEN      = @echo '   ' GEN $@;
+       QUIET_LNCP     = @echo '   ' LN/CP $@;
        QUIET_SUBDIR0  = +@subdir=
        QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
                         $(MAKE) $(PRINT_DIR) -C $$subdir
@@ -1287,6 +1405,7 @@ prefix_SQ = $(subst ','\'',$(prefix))
 
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
@@ -1295,6 +1414,22 @@ 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)
 
@@ -1307,7 +1442,7 @@ SHELL = $(SHELL_PATH)
 
 all:: shell_compatibility_test $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
 ifneq (,$X)
-       $(QUIET_BUILT_IN)$(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::
@@ -1317,6 +1452,9 @@ ifndef NO_TCLTK
 endif
 ifndef NO_PERL
        $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+endif
+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)
 
@@ -1331,7 +1469,7 @@ strip: $(PROGRAMS) git$X
 git.o: git.c common-cmds.h GIT-CFLAGS
        $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
                '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
-               $(ALL_CFLAGS) -c $(filter %.c,$^)
+               $(ALL_CFLAGS) -o $@ -c $(filter %.c,$^)
 
 git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
@@ -1387,8 +1525,13 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
        chmod +x $@+ && \
        mv $@+ $@
 
+ifdef JSMIN
+OTHER_PROGRAMS += gitweb/gitweb.cgi   gitweb/gitweb.min.js
+gitweb/gitweb.cgi: gitweb/gitweb.perl gitweb/gitweb.min.js
+else
 OTHER_PROGRAMS += gitweb/gitweb.cgi
 gitweb/gitweb.cgi: gitweb/gitweb.perl
+endif
        $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
            -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
@@ -1407,13 +1550,14 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl
            -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' \
            $< >$@+ && \
        chmod +x $@+ && \
        mv $@+ $@
 
-git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
+git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css gitweb/gitweb.js
        $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
@@ -1422,6 +1566,8 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
            -e '/@@GITWEB_CGI@@/d' \
            -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \
            -e '/@@GITWEB_CSS@@/d' \
+           -e '/@@GITWEB_JS@@/r gitweb/gitweb.js' \
+           -e '/@@GITWEB_JS@@/d' \
            -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
            $@.sh > $@+ && \
        chmod +x $@+ && \
@@ -1436,6 +1582,41 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh
        mv $@+ $@
 endif # NO_PERL
 
+
+ifdef JSMIN
+gitweb/gitweb.min.js: gitweb/gitweb.js
+       $(QUIET_GEN)$(JSMIN) <$< >$@
+endif # JSMIN
+
+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 '1{' \
+           -e '        s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
+           -e '}' \
+           -e 's|^import sys.*|&; \\\
+                  import os; \\\
+                  sys.path[0] = os.environ.has_key("GITPYTHONLIB") and \\\
+                                os.environ["GITPYTHONLIB"] or \\\
+                                "@@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' \
@@ -1484,12 +1665,21 @@ 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
+http.o http-walker.o http-push.o: http.h
+
+http.o http-walker.o: $(LIB_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)
 
+git-remote-curl$X: 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_OBJS) $(BUILTIN_OBJS): $(LIB_H)
 $(patsubst git-%$X,%.o,$(PROGRAMS)) git.o: $(LIB_H) $(wildcard */*.h)
 builtin-revert.o wt-status.o: wt-status.h
@@ -1549,9 +1739,11 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
 # and the first level quoting from the shell that runs "echo".
 GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
        @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
+       @echo PERL_PATH=\''$(subst ','\'',$(PERL_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)))'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
@@ -1651,6 +1843,9 @@ install: all
 ifndef NO_PERL
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' 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
        $(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install
@@ -1722,7 +1917,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)
@@ -1750,7 +1948,7 @@ distclean: clean
        $(RM) configure
 
 clean:
-       $(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \
+       $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \
                $(LIB_FILE) $(XDIFF_LIB)
        $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X
        $(RM) $(TEST_PROGRAMS)
@@ -1764,6 +1962,9 @@ clean:
 ifndef NO_PERL
        $(RM) gitweb/gitweb.cgi
        $(MAKE) -C perl clean
+endif
+ifndef NO_PYTHON
+       $(MAKE) -C git_remote_helpers clean
 endif
        $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
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 b62449d2e22829f8bcbe404a48ff8f62c47eebd9..5274ceed4e7ecd0210ad8338a0b3570e3648b05e 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.6.5.txt
\ No newline at end of file
+Documentation/RelNotes-1.6.6.txt
\ No newline at end of file
index 4bee0ba1ec6fcf49e88b874f0a415f4220117c0a..b88122cbe73ec0c438e2d375fdebd51e5febf9ae 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;
@@ -50,7 +50,7 @@ const char *make_absolute_path(const char *path)
                        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);
@@ -61,7 +61,7 @@ const char *make_absolute_path(const char *path)
                }
 
                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_errno ("Invalid symlink '%s'", buf);
                        if (PATH_MAX <= len)
diff --git a/advice.c b/advice.c
new file mode 100644 (file)
index 0000000..cb666ac
--- /dev/null
+++ b/advice.c
@@ -0,0 +1,29 @@
+#include "cache.h"
+
+int advice_push_nonfastforward = 1;
+int advice_status_hints = 1;
+int advice_commit_before_merge = 1;
+
+static struct {
+       const char *name;
+       int *preference;
+} advice_config[] = {
+       { "pushnonfastforward", &advice_push_nonfastforward },
+       { "statushints", &advice_status_hints },
+       { "commitbeforemerge", &advice_commit_before_merge },
+};
+
+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;
+}
diff --git a/advice.h b/advice.h
new file mode 100644 (file)
index 0000000..3de5000
--- /dev/null
+++ b/advice.h
@@ -0,0 +1,10 @@
+#ifndef ADVICE_H
+#define ADVICE_H
+
+extern int advice_push_nonfastforward;
+extern int advice_status_hints;
+extern int advice_commit_before_merge;
+
+int git_default_advice_config(const char *var, const char *value);
+
+#endif /* ADVICE_H */
index 0bca9ca4038ace99c32eff2e042290b03b2324b7..55b273246e006ad55c51d3e5cb6bed3153ae8cf4 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -31,6 +31,8 @@ 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;
 
        if (src == buf->buf)
                to_free = strbuf_detach(buf, NULL);
@@ -48,7 +50,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;
        }
@@ -115,6 +117,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 +190,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;
@@ -211,7 +214,7 @@ static const struct archiver *lookup_archiver(const char *name)
 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 = get_pathspec("", pathspec);
 }
 
 static void parse_treeish_arg(const char **argv,
@@ -283,7 +286,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"),
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
index 7f20acb4b9e391bd383597ec554ec58b70979fe5..dc18db8af96b093d7cb16eb88d3f738f98dc30f6 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -991,7 +991,7 @@ int bisect_next_all(const char *prefix)
 
        if (!hashcmp(bisect_rev, current_bad_sha1)) {
                exit_if_skipped_commits(tried, current_bad_sha1);
-               printf("%s is first bad commit\n", bisect_rev_hex);
+               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);
diff --git a/block-sha1/sha1.c b/block-sha1/sha1.c
new file mode 100644 (file)
index 0000000..d893475
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * 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(__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)
+{
+       int lenW = ctx->size & 63;
+
+       ctx->size += len;
+
+       /* Read the data into W and process blocks as they get full */
+       if (lenW) {
+               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(ctx->size >> 29);
+       padlen[1] = htonl(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 581a2a17480cc0ffa47a942ff58da4499aeac41d..cb6e5906fb76f2460f212c54b557e5ae0ce23579 100644 (file)
@@ -105,8 +105,8 @@ static void refresh(int verbose, const char **pathspec)
        for (specs = 0; pathspec[specs];  specs++)
                /* nothing */;
        seen = xcalloc(specs, 1);
-       refresh_index(&the_index, verbose ? REFRESH_SAY_CHANGED : REFRESH_QUIET,
-                     pathspec, seen);
+       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]);
@@ -131,27 +131,27 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p
        return pathspec;
 }
 
-int interactive_add(int argc, const char **argv, const char *prefix)
+int run_add_interactive(const char *revision, const char *patch_mode,
+                       const char **pathspec)
 {
-       int status, ac;
+       int status, ac, pc = 0;
        const char **args;
-       const char **pathspec = NULL;
 
-       if (argc) {
-               pathspec = validate_pathspec(argc, argv, prefix);
-               if (!pathspec)
-                       return -1;
-       }
+       if (pathspec)
+               while (pathspec[pc])
+                       pc++;
 
-       args = xcalloc(sizeof(const char *), (argc + 4));
+       args = xcalloc(sizeof(const char *), (pc + 5));
        ac = 0;
        args[ac++] = "add--interactive";
-       if (patch_interactive)
-               args[ac++] = "--patch";
+       if (patch_mode)
+               args[ac++] = patch_mode;
+       if (revision)
+               args[ac++] = revision;
        args[ac++] = "--";
-       if (argc) {
-               memcpy(&(args[ac]), pathspec, sizeof(const char *) * argc);
-               ac += argc;
+       if (pc) {
+               memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);
+               ac += pc;
        }
        args[ac] = NULL;
 
@@ -160,6 +160,21 @@ int interactive_add(int argc, const char **argv, const char *prefix)
        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"));
@@ -183,7 +198,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
        out = open(file, O_CREAT | O_WRONLY, 0644);
        if (out < 0)
                die ("Could not open '%s' for writing.", file);
-       rev.diffopt.file = fdopen(out, "w");
+       rev.diffopt.file = xfdopen(out, "w");
        rev.diffopt.close_file = 1;
        if (run_diff_files(&rev, 0))
                die ("Could not write patch");
index dc0ff5e08ae73f2832fd4451cce2e849c88be5a8..36e2f9dda5c85c346e31f45afa6d28b107679970 100644 (file)
@@ -61,6 +61,13 @@ static enum ws_error_action {
 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;
@@ -97,6 +104,21 @@ static void parse_whitespace_option(const char *option)
        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)
@@ -131,6 +153,7 @@ struct fragment {
        const char *patch;
        int size;
        int rejected;
+       int linenr;
        struct fragment *next;
 };
 
@@ -214,6 +237,62 @@ static uint32_t hash_line(const char *cp, size_t len)
        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);
@@ -457,6 +536,76 @@ static int guess_p_value(const char *nameline)
        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
  *
@@ -493,7 +642,17 @@ static void parse_traditional_patch(const char *first, const char *second, struc
        } 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 (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);
@@ -664,12 +823,13 @@ static int gitdiff_unrecognized(const char *line, struct patch *patch)
 
 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 == '/')
-                       return line + i;
+               if (ch == '/' && --nslash <= 0)
+                       return &line[i];
        }
        return NULL;
 }
@@ -1069,23 +1229,29 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
        return -1;
 }
 
-static void check_whitespace(const char *line, int len, unsigned ws_rule)
+static void record_ws_error(unsigned result, const char *line, int len, int linenr)
 {
        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);
-       }
+               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);
 }
 
 /*
@@ -1201,6 +1367,7 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
                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);
@@ -1592,10 +1759,17 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
        }
 }
 
+/*
+ * 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 len, size_t postlen)
 {
        int i, ctx;
        char *new, *old, *fixed;
@@ -1614,11 +1788,19 @@ static void update_pre_post_images(struct image *preimage,
        *preimage = fixed_preimage;
 
        /*
-        * Adjust the common context lines in postimage, in place.
-        * This is possible because whitespace fixing does not make
-        * the string grow.
+        * 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.
         */
-       new = old = postimage->buf;
+       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;
@@ -1693,12 +1875,56 @@ static int match_fragment(struct image *img,
            !memcmp(img->buf + try, preimage->buf, preimage->len))
                return 1;
 
+       /*
+        * 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;
+               for (i = 0; i < preimage->nr; 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. Update it and
+                * the common postimage lines to use the same whitespace as the
+                * target. imgoff now holds the true length of the target that
+                * matches the preimage, and we need to update the line lengths
+                * of the preimage to match the target ones.
+                */
+               fixed_buf = xmalloc(imgoff);
+               memcpy(fixed_buf, img->buf + try, imgoff);
+               for (i = 0; i < preimage->nr; i++)
+                       preimage->line[i].len = img->line[try_lno+i].len;
+
+               /*
+                * Update the preimage buffer and the postimage context lines.
+                */
+               update_pre_post_images(preimage, postimage,
+                               fixed_buf, imgoff, 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.
+        * 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.
         */
        fixed_buf = xmalloc(preimage->len + 1);
        buf = fixed_buf;
@@ -1750,7 +1976,7 @@ static int match_fragment(struct image *img,
         * hunk match.  Update the context lines in the postimage.
         */
        update_pre_post_images(preimage, postimage,
-                              fixed_buf, buf - fixed_buf);
+                              fixed_buf, buf - fixed_buf, 0);
        return 1;
 
  unmatch_exit:
@@ -1925,6 +2151,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
                int len = linelen(patch, size);
                int plen, added;
                int added_blank_line = 0;
+               int is_blank_context = 0;
 
                if (!len)
                        break;
@@ -1957,8 +2184,12 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
                        *new++ = '\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,
@@ -1985,7 +2216,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
                                      (first == '+' ? 0 : LINE_COMMON));
                        new += added;
                        if (first == '+' &&
-                           added == 1 && new[-1] == '\n')
+                           (ws_rule & WS_BLANK_AT_EOF) &&
+                           ws_blank_line(patch + 1, plen, ws_rule))
                                added_blank_line = 1;
                        break;
                case '@': case '\\':
@@ -1998,6 +2230,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
                }
                if (added_blank_line)
                        new_blank_lines_at_end++;
+               else if (is_blank_context)
+                       ;
                else
                        new_blank_lines_at_end = 0;
                patch += len;
@@ -2079,17 +2313,24 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
        }
 
        if (applied_pos >= 0) {
-               if (ws_error_action == correct_ws_error &&
-                   new_blank_lines_at_end &&
-                   postimage.nr + applied_pos == img->nr) {
+               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);
+                       }
                        /*
-                        * 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.
+                        * 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
                         */
-                       while (new_blank_lines_at_end--)
-                               remove_last_line(&postimage);
+                       if (ws_error_action == die_on_ws_error)
+                               apply = 0;
                }
 
                /*
@@ -3192,6 +3433,8 @@ 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);
 }
 
@@ -3228,6 +3471,16 @@ static int option_parse_z(const struct option *opt,
        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)
 {
@@ -3304,6 +3557,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
                { 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,
@@ -3328,6 +3587,8 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
        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);
index f9a4bea41e95b9a34f190fc2b696c7394d0e0971..446d6bff30ed47a5cf308310e3f28c5b30648a2c 100644 (file)
@@ -60,6 +60,17 @@ static int run_remote_archiver(int argc, const char **argv,
        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 "zip";
+       return NULL;
+}
+
 #define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH |         \
                             PARSE_OPT_KEEP_ARGV0 |     \
                             PARSE_OPT_KEEP_UNKNOWN |   \
@@ -70,21 +81,43 @@ 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 = NULL;
        struct option local_opts[] = {
-               OPT_STRING(0, "output", &output, "file",
+               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_STRING(0, "format", &format, "fmt", "archive format"),
                OPT_END()
        };
+       char fmt_opt[32];
 
        argc = parse_options(argc, argv, prefix, local_opts, NULL,
                             PARSE_OPT_KEEP_ALL);
 
-       if (output)
+       if (output) {
                create_output_file(output);
+               if (!format)
+                       format = format_from_name(output);
+       }
+
+       if (format) {
+               sprintf(fmt_opt, "--format=%s", format);
+               /*
+                * We have enough room in argv[] to muck it in place,
+                * because either --format and/or --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.
+                * But argv[] may contain "--"; we should make it the
+                * first option.
+                */
+               memmove(argv + 2, argv + 1, sizeof(*argv) * argc);
+               argv[1] = fmt_opt;
+               argv[++argc] = NULL;
+       }
 
        if (remote)
                return run_remote_archiver(argc, argv, remote, exec);
index fd6ca51eebb2234be429b47b572ae2f30592ff4e..6408ec8ee6805fc42eb5e5dc52be6463d62128bf 100644 (file)
@@ -1305,6 +1305,7 @@ static void get_ac_line(const char *inbuf, const char *what,
        error_out:
                /* Ugh */
                *tz = "(unknown)";
+               strcpy(person, *tz);
                strcpy(mail, *tz);
                *time = 0;
                return;
@@ -1314,20 +1315,26 @@ static void get_ac_line(const char *inbuf, const char *what,
        tmp = person;
        tmp += len;
        *tmp = 0;
-       while (*tmp != ' ')
+       while (person < tmp && *tmp != ' ')
                tmp--;
+       if (tmp <= person)
+               goto error_out;
        *tz = tmp+1;
        tzlen = (person+len)-(tmp+1);
 
        *tmp = 0;
-       while (*tmp != ' ')
+       while (person < tmp && *tmp != ' ')
                tmp--;
+       if (tmp <= person)
+               goto error_out;
        *time = strtoul(tmp, NULL, 10);
        timepos = tmp;
 
        *tmp = 0;
-       while (*tmp != ' ')
+       while (person < tmp && *tmp != ' ')
                tmp--;
+       if (tmp <= person)
+               return;
        mailpos = tmp + 1;
        *tmp = 0;
        maillen = timepos - tmp;
@@ -1348,7 +1355,7 @@ static void get_ac_line(const char *inbuf, const char *what,
        /*
         * Now, convert both name and e-mail using mailmap
         */
-       if(map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
+       if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
                /* Add a trailing '>' to email, since map_user returns plain emails
                   Note: It already has '<', since we replace from mail+1 */
                mailpos = memchr(mail, '\0', mail_len);
@@ -1604,6 +1611,9 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
                } 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)
@@ -1667,6 +1677,9 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
                } 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)
@@ -2352,6 +2365,7 @@ parse_done:
                        die_errno("cannot stat path '%s'", path);
        }
 
+       revs.disable_stdin = 1;
        setup_revisions(argc, argv, &revs, NULL);
        memset(&sb, 0, sizeof(sb));
 
index 1a03d5f356009de12364c320577e24853bec7959..c87e63b02dd3bb0e12e5bf14aec22a0f219867b0 100644 (file)
@@ -65,7 +65,7 @@ static int parse_branch_color_slot(const char *var, int ofs)
                return BRANCH_COLOR_LOCAL;
        if (!strcasecmp(var+ofs, "current"))
                return BRANCH_COLOR_CURRENT;
-       die("bad config variable '%s'", var);
+       return -1;
 }
 
 static int git_branch_config(const char *var, const char *value, void *cb)
@@ -76,6 +76,8 @@ static int git_branch_config(const char *var, const char *value, void *cb)
        }
        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]);
@@ -387,8 +389,9 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
 
                commit = item->commit;
                if (commit && !parse_commit(commit)) {
+                       struct pretty_print_context ctx = {0};
                        pretty_print_commit(CMIT_FMT_ONELINE, commit,
-                                           &subject, 0, NULL, NULL, 0, 0);
+                                           &subject, &ctx);
                        sub = subject.buf;
                }
 
@@ -586,7 +589,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                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)"),
+               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",
index 9b58152047baebfdd6f26ffab31d49347e0cb069..2006cc5cd5cc4d381189f5cf7bdd09fcdd66b9ca 100644 (file)
@@ -9,7 +9,11 @@
  * 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]... )";
+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)
 {
@@ -20,7 +24,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
        char buffer[PATH_MAX];
 
        if (argc < 3)
-               usage(bundle_usage);
+               usage(builtin_bundle_usage);
 
        cmd = argv[1];
        bundle_file = argv[2];
@@ -59,5 +63,5 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
                return !!unbundle(&header, bundle_fd) ||
                        list_bundle_refs(&header, argc, argv);
        } else
-               usage(bundle_usage);
+               usage(builtin_bundle_usage);
 }
index f9381e07eaeda673e91ef6250eca4027c5ba0278..b106c65d80dfc8fe794d46e34ea1ff78b04f3056 100644 (file)
@@ -7,8 +7,37 @@
 #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';
+}
+
 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")) {
                struct strbuf sb = STRBUF_INIT;
 
@@ -17,7 +46,16 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
                printf("%s\n", sb.buf + 11);
                exit(0);
        }
+       if (argc == 3 && !strcmp(argv[1], "--print")) {
+               char *refname = xmalloc(strlen(argv[2]) + 1);
+
+               if (check_ref_format(argv[2]))
+                       exit(1);
+               collapse_slashes(refname, argv[2]);
+               printf("%s\n", refname);
+               exit(0);
+       }
        if (argc != 2)
-               usage("git check-ref-format refname");
+               usage(builtin_check_ref_format_usage);
        return !!check_ref_format(argv[1]);
 }
index 8a9a474218c128b7878647045ba471df9902636a..64f3a11ae1d415c466def8eccefe9dbc5b6b3926 100644 (file)
@@ -302,8 +302,9 @@ static void show_local_changes(struct object *head)
 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, 0, NULL, NULL, 0, 0);
+       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);
@@ -402,7 +403,9 @@ static int merge_working_tree(struct checkout_opts *opts,
                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);
+               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);
@@ -541,14 +544,6 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
                parse_commit(new->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;
@@ -572,6 +567,47 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
        return git_xmerge_config(var, value, cb);
 }
 
+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 = { name, NULL, 1 };
+       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;
@@ -580,6 +616,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        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, "new branch", "branch"),
@@ -590,10 +628,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                            2),
                OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
                            3),
-               OPT_BOOLEAN('f', NULL, &opts.force, "force"),
+               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;
@@ -608,6 +650,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix, options, checkout_usage,
                             PARSE_OPT_KEEP_DASHDASH);
 
+       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];
@@ -623,8 +669,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                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);
@@ -648,6 +692,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
         *   With no paths, if <something> is a commit, that is to
         *   switch to the branch or detach HEAD at it.
         *
+        *   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).
@@ -670,7 +719,21 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                if (get_sha1(arg, rev)) {
                        if (has_dash_dash)          /* case (1) */
                                die("invalid reference: %s", arg);
-                       goto no_reference;          /* case (3 -> 2) */
+                       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 */
@@ -708,12 +771,19 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        }
 
 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) {
@@ -729,6 +799,9 @@ no_reference:
                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))
index 2d8c735d4881a005e4aa5006d9781b71631bb0af..28cdcd0274dd5891c7ad3dc3804cb0acead883f2 100644 (file)
@@ -31,6 +31,7 @@ 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;
@@ -40,7 +41,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
        struct option options[] = {
                OPT__QUIET(&quiet),
                OPT__DRY_RUN(&show_only),
-               OPT_BOOLEAN('f', NULL, &force, "force"),
+               OPT_BOOLEAN('f', "force", &force, "force"),
                OPT_BOOLEAN('d', NULL, &remove_directories,
                                "remove whole directories"),
                OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"),
@@ -69,6 +70,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                die("clean.requireForce%s set and -n or -f not given; "
                    "refusing to clean", config_set ? "" : " not");
 
+       if (force > 1)
+               rm_flags = 0;
+
        dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
 
        if (!ignored)
@@ -131,7 +135,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                                   (matches == MATCHED_EXACTLY)) {
                                if (!quiet)
                                        printf("Removing %s\n", qname);
-                               if (remove_dir_recursively(&directory, 0) != 0) {
+                               if (remove_dir_recursively(&directory,
+                                                          rm_flags) != 0) {
                                        warning("failed to remove '%s'", qname);
                                        errors++;
                                }
index 32dea74d78381ca1dffdac7f88e45a89a9825104..5df8b0f72c8bb5dfa7fa7df1edfec90405031f47 100644 (file)
@@ -38,9 +38,10 @@ static const char * const builtin_clone_usage[] = {
 };
 
 static int option_quiet, option_no_checkout, option_bare, option_mirror;
-static int option_local, option_no_hardlinks, option_shared;
+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_verbose;
 
@@ -50,7 +51,9 @@ static struct option builtin_clone_options[] = {
        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"),
+       { 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,
@@ -59,12 +62,16 @@ static struct option builtin_clone_options[] = {
                    "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",
@@ -73,6 +80,10 @@ static struct option builtin_clone_options[] = {
        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", "" };
@@ -260,7 +271,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
                                die_errno("failed to create link '%s'", dest->buf);
                        option_no_hardlinks = 1;
                }
-               if (copy_file(dest->buf, src->buf, 0666))
+               if (copy_file_with_time(dest->buf, src->buf, 0666))
                        die_errno("failed to copy file to '%s'", dest->buf);
        }
        closedir(dir);
@@ -320,24 +331,28 @@ static void remove_junk_on_signal(int signo)
        raise(signo);
 }
 
-static struct ref *write_remote_refs(const struct ref *refs,
-               struct refspec *refspec, const char *reflog)
+static struct ref *wanted_peer_refs(const struct ref *refs,
+               struct refspec *refspec)
 {
        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);
 
+       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();
-
-       return local_refs;
 }
 
 int cmd_clone(int argc, const char **argv, const char *prefix)
@@ -347,7 +362,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        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;
+       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;
@@ -362,8 +380,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        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)
-               die("You must specify a repository to clone.");
+               usage_msg_opt("You must specify a repository to clone.",
+                       builtin_clone_usage, builtin_clone_options);
 
        if (option_mirror)
                option_bare = 1;
@@ -484,9 +507,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        strbuf_reset(&value);
 
-       if (path && !is_bundle)
+       if (path && !is_bundle) {
                refs = clone_local(path, git_dir);
-       else {
+               mapped_refs = wanted_peer_refs(refs, refspec);
+       } else {
                struct remote *remote = remote_get(argv[0]);
                transport = transport_get(remote, remote->url[0]);
 
@@ -509,21 +533,43 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                                             option_upload_pack);
 
                refs = transport_get_remote_refs(transport);
-               if(refs)
-                       transport_fetch_refs(transport, refs);
+               if (refs) {
+                       mapped_refs = wanted_peer_refs(refs, refspec);
+                       transport_fetch_refs(transport, mapped_refs);
+               }
        }
 
        if (refs) {
                clear_extra_refs();
 
-               mapped_refs = write_remote_refs(refs, refspec, reflog_msg.buf);
+               write_remote_refs(mapped_refs);
 
                remote_head = find_ref_by_name(refs, "HEAD");
-               head_points_at = guess_remote_head(remote_head, mapped_refs, 0);
+               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.");
-               head_points_at = NULL;
+               our_head_points_at = NULL;
+               remote_head_points_at = NULL;
                remote_head = NULL;
                option_no_checkout = 1;
                if (!option_bare)
@@ -531,41 +577,35 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                                              "refs/heads/master");
        }
 
-       if (head_points_at) {
-               /* Local default branch link */
-               create_symref("HEAD", head_points_at->name, NULL);
+       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) {
-                       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 */
+                       const char *head = skip_prefix(our_head_points_at->name,
+                                                      "refs/heads/");
                        update_ref(reflog_msg.buf, "HEAD",
-                                  head_points_at->old_sha1,
+                                  our_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);
+                                             our_head_points_at->name);
                }
        } else if (remote_head) {
                /* Source had detached HEAD pointing somewhere. */
-               if (!option_bare)
+               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)
@@ -574,8 +614,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                option_no_checkout = 1;
        }
 
-       if (transport)
+       if (transport) {
                transport_unlock_pack(transport);
+               transport_disconnect(transport);
+       }
 
        if (!option_no_checkout) {
                struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
@@ -597,7 +639,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                opts.src_index = &the_index;
                opts.dst_index = &the_index;
 
-               tree = parse_tree_indirect(remote_head->old_sha1);
+               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);
@@ -607,7 +649,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                        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);
+                               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);
index 64670777312c71d45b10f72a1efe9fceae3e3b89..ddcb7a4bbbcd45dfd7437209f3187fff882e6e62 100644 (file)
@@ -105,7 +105,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 
        git_config(git_default_config, NULL);
 
-       if (argc < 2)
+       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]);
index 4bcce06fbffdf10ec701dfbc0b6b90a11513f89e..f54772f74a14e480ae978b38ce3bcd49ee994411 100644 (file)
@@ -51,7 +51,7 @@ 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 int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static char *untracked_files_arg;
 /*
  * The default commit message cleanup mode will remove the lines
@@ -91,8 +91,9 @@ static struct option builtin_commit_options[] = {
        OPT_FILENAME('F', "file", &logfile, "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', "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"),
@@ -103,6 +104,7 @@ static struct option builtin_commit_options[] = {
        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_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"),
@@ -217,12 +219,15 @@ static void create_base_index(void)
                exit(128); /* We've already reported the error, finish dying */
 }
 
-static char *prepare_index(int argc, const char **argv, const char *prefix)
+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");
@@ -253,7 +258,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
        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);
+               refresh_cache(refresh_flags);
                if (write_cache(fd, active_cache, active_nr) ||
                    close_lock_file(&index_lock))
                        die("unable to write new_index file");
@@ -272,7 +277,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
         */
        if (!pathspec || !*pathspec) {
                fd = hold_locked_index(&index_lock, 1);
-               refresh_cache(REFRESH_QUIET);
+               refresh_cache(refresh_flags);
                if (write_cache(fd, active_cache, active_nr) ||
                    commit_locked_index(&index_lock))
                        die("unable to write new_index file");
@@ -339,27 +344,24 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
        return false_lock.filename;
 }
 
-static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn)
+static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
+                     struct wt_status *s)
 {
-       struct wt_status s;
-
-       wt_status_prepare(&s);
-       if (wt_status_relative_paths)
-               s.prefix = prefix;
+       if (s->relative_paths)
+               s->prefix = prefix;
 
        if (amend) {
-               s.amend = 1;
-               s.reference = "HEAD^1";
+               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;
+       s->verbose = verbose;
+       s->index_file = index_file;
+       s->fp = fp;
+       s->nowarn = nowarn;
 
-       wt_status_print(&s);
+       wt_status_print(s);
 
-       return s.commitable;
+       return s->commitable;
 }
 
 static int is_a_merge(const unsigned char *sha1)
@@ -380,7 +382,7 @@ static void determine_author_info(void)
        email = getenv("GIT_AUTHOR_EMAIL");
        date = getenv("GIT_AUTHOR_DATE");
 
-       if (use_message) {
+       if (use_message && !renew_authorship) {
                const char *a, *lb, *rb, *eol;
 
                a = strstr(use_message_buffer, "\nauthor ");
@@ -413,7 +415,49 @@ static void determine_author_info(void)
        author_date = date;
 }
 
-static int prepare_to_commit(const char *index_file, const char *prefix)
+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;
@@ -487,7 +531,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
                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))
+                       if (!i || !ends_rfc2822_footer(&sb))
                                strbuf_addch(&sb, '\n');
                        strbuf_addbuf(&sb, &sob);
                }
@@ -555,10 +599,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
                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;
+               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";
@@ -579,7 +623,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
 
        if (!commitable && !in_merge && !allow_empty &&
            !(amend && is_a_merge(head_sha1))) {
-               run_status(stdout, index_file, prefix, 0);
+               run_status(stdout, index_file, prefix, 0, s);
                return 0;
        }
 
@@ -682,8 +726,10 @@ static const char *find_author_by_nickname(const char *name)
        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, DATE_NORMAL);
+               format_commit_message(commit, "%an <%ae>", &buf, &ctx);
                return strbuf_detach(&buf, NULL);
        }
        die("No existing author found with '%s'", name);
@@ -691,7 +737,8 @@ static const char *find_author_by_nickname(const char *name)
 
 static int parse_and_validate_options(int argc, const char *argv[],
                                      const char * const usage[],
-                                     const char *prefix)
+                                     const char *prefix,
+                                     struct wt_status *s)
 {
        int f = 0;
 
@@ -701,6 +748,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
        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)
@@ -734,6 +784,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
                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";
@@ -794,11 +846,11 @@ static int parse_and_validate_options(int argc, const char *argv[],
        if (!untracked_files_arg)
                ; /* default already initialized */
        else if (!strcmp(untracked_files_arg, "no"))
-               show_untracked_files = SHOW_NO_UNTRACKED_FILES;
+               s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
        else if (!strcmp(untracked_files_arg, "normal"))
-               show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
+               s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
        else if (!strcmp(untracked_files_arg, "all"))
-               show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
+               s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
        else
                die("Invalid untracked files mode '%s'", untracked_files_arg);
 
@@ -810,28 +862,95 @@ static int parse_and_validate_options(int argc, const char *argv[],
        return argc;
 }
 
-int cmd_status(int argc, const char **argv, const char *prefix)
+static int dry_run_commit(int argc, const char **argv, const char *prefix,
+                         struct wt_status *s)
 {
-       const char *index_file;
        int commitable;
+       const char *index_file;
 
-       git_config(git_status_config, NULL);
+       index_file = prepare_index(argc, argv, prefix, 1);
+       commitable = run_status(stdout, index_file, prefix, 0, s);
+       rollback_index_files();
 
-       if (wt_status_use_color == -1)
-               wt_status_use_color = git_use_color_default;
+       return commitable ? 0 : 1;
+}
 
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
+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;
+}
 
-       argc = parse_and_validate_options(argc, argv, builtin_status_usage, prefix);
+static int git_status_config(const char *k, const char *v, void *cb)
+{
+       struct wt_status *s = cb;
 
-       index_file = prepare_index(argc, argv, prefix);
+       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);
+}
 
-       commitable = run_status(stdout, index_file, prefix, 0);
+int cmd_status(int argc, const char **argv, const char *prefix)
+{
+       struct wt_status s;
 
-       rollback_index_files();
+       wt_status_prepare(&s);
+       git_config(git_status_config, &s);
+       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;
 
-       return commitable ? 0 : 1;
+       argc = parse_and_validate_options(argc, argv, builtin_status_usage,
+                                         prefix, &s);
+       return dry_run_commit(argc, argv, prefix, &s);
 }
 
 static void print_summary(const char *prefix, const unsigned char *sha1)
@@ -874,8 +993,10 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
                initial_commit ? " (root-commit)" : "");
 
        if (!log_tree_commit(&rev, commit)) {
+               struct pretty_print_context ctx = {0};
                struct strbuf buf = STRBUF_INIT;
-               format_commit_message(commit, format + 7, &buf, DATE_NORMAL);
+               ctx.date_mode = DATE_NORMAL;
+               format_commit_message(commit, format + 7, &buf, &ctx);
                printf("%s\n", buf.buf);
                strbuf_release(&buf);
        }
@@ -883,10 +1004,12 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
 
 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_string(&template_file, k, v);
+               return git_config_pathname(&template_file, k, v);
 
-       return git_status_config(k, v, cb);
+       return git_status_config(k, v, s);
 }
 
 int cmd_commit(int argc, const char **argv, const char *prefix)
@@ -899,19 +1022,26 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        struct commit_list *parents = NULL, **pptr = &parents;
        struct stat statbuf;
        int allow_fast_forward = 1;
+       struct wt_status s;
 
-       git_config(git_commit_config, NULL);
-
-       if (wt_status_use_color == -1)
-               wt_status_use_color = git_use_color_default;
+       wt_status_prepare(&s);
+       git_config(git_commit_config, &s);
 
-       argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix);
+       if (s.use_color == -1)
+               s.use_color = git_use_color_default;
 
-       index_file = prepare_index(argc, argv, prefix);
+       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)) {
+       if (!prepare_to_commit(index_file, prefix, &s)) {
                rollback_index_files();
                return 1;
        }
index 7a662980d102a275fceb8299bba0b84b41a8f9cb..71be2a9364748668996696f6c74057dba43315b5 100644 (file)
@@ -5,12 +5,14 @@
 #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
 };
 
@@ -20,8 +22,16 @@ 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;
@@ -49,6 +59,7 @@ static void add_to_known_names(const char *path,
                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)
@@ -94,8 +105,6 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void
        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;
@@ -178,11 +187,11 @@ 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;
+       unsigned int unannotated_cnt = 0;
 
        if (get_sha1(arg, sha1))
                die("Not a valid object name %s", arg);
@@ -190,19 +199,16 @@ static void describe(const char *arg, int last_one)
        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) {
+       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;
        }
@@ -221,7 +227,9 @@ static void describe(const char *arg, int last_one)
                seen_commits++;
                n = c->util;
                if (n) {
-                       if (match_cnt < max_candidates) {
+                       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;
@@ -260,10 +268,20 @@ static void describe(const char *arg, int last_one)
        if (!match_cnt) {
                const unsigned char *sha1 = cmit->object.sha1;
                if (always) {
-                       printf("%s\n", find_unique_abbrev(sha1, abbrev));
+                       printf("%s", find_unique_abbrev(sha1, abbrev));
+                       if (dirty)
+                               printf("%s", dirty);
+                       printf("\n");
                        return;
                }
-               die("cannot describe '%s'", sha1_to_hex(sha1));
+               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);
@@ -295,6 +313,8 @@ static void describe(const char *arg, int last_one)
        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)
@@ -319,6 +339,9 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                           "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(),
        };
 
@@ -354,8 +377,16 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                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);
index 79cedb72c44dca3edf400d86e401a93531b54407..2380c21951fb5fb8050ab1acf0e7f01f36ea5520 100644 (file)
@@ -104,6 +104,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
        git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
        opt->abbrev = 0;
        opt->diff = 1;
+       opt->disable_stdin = 1;
        argc = setup_revisions(argc, argv, opt, NULL);
 
        while (--argc > 0) {
index 2e51f408f9f3399195604fd23d430c180a7f20a4..ffcdd055ca0b9b30bec2ce1f22e348ec15d58c81 100644 (file)
@@ -218,6 +218,8 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
                        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--;
index c48c18d0c89d1050a531d0521135c2bed9e3b77d..b0a4029c94d1bdb1c673fe604cdbfec93df875aa 100644 (file)
@@ -26,6 +26,7 @@ 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 parse_opt_signed_tag_mode(const struct option *opt,
                                     const char *arg, int unset)
@@ -116,6 +117,9 @@ static void handle_object(const unsigned char *sha1)
        char *buf;
        struct object *object;
 
+       if (no_data)
+               return;
+
        if (is_null_sha1(sha1))
                return;
 
@@ -173,7 +177,7 @@ static void show_filemodify(struct diff_queue_struct *q,
                         * Links refer to objects in another repositories;
                         * output the SHA-1 verbatim.
                         */
-                       if (S_ISGITLINK(spec->mode))
+                       if (no_data || S_ISGITLINK(spec->mode))
                                printf("M %06o %s %s\n", spec->mode,
                                       sha1_to_hex(spec->sha1), spec->path);
                        else {
@@ -580,6 +584,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
                             "Import marks from this file"),
                OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger,
                             "Fake a tagger when tags lack one"),
+               { OPTION_NEGBIT, 0, "data", &no_data, NULL,
+                       "Skip output of blob data",
+                       PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },
                OPT_END()
        };
 
index 629735f54723a3bc3b24f8c91e9a6226ae28d742..8ed4a6feaac2868523e6516e02865132dedbc9f5 100644 (file)
@@ -157,6 +157,66 @@ static const unsigned char *get_rev(void)
        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)
 {
@@ -165,7 +225,11 @@ static int find_common(int fd[2], unsigned char *result_sha1,
        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;
@@ -175,6 +239,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
        fetching = 0;
        for ( ; refs ; refs = refs->next) {
                unsigned char *remote = refs->old_sha1;
+               const char *remote_hex;
                struct object *o;
 
                /*
@@ -192,32 +257,42 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                        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));
+               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(fd[1], 1);
+               write_shallow_commits(&req_buf, 1);
        if (args.depth > 0)
-               packet_write(fd[1], "deepen %d", args.depth);
-       packet_flush(fd[1]);
-       if (!fetching)
-               return 1;
+               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))
@@ -239,45 +314,73 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                        }
                        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_write(fd[1], "have %s\n", sha1_to_hex(sha1));
+               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_flush(fd[1]);
+                       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 (count == 32)
+                       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));
-                               if (ack == 1) {
+                               switch (ack) {
+                               case ACK:
                                        flushes = 0;
                                        multi_ack = 0;
                                        retval = 0;
                                        goto done;
-                               } else if (ack == 2) {
+                               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--;
@@ -289,20 +392,24 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                }
        }
 done:
-       packet_write(fd[1], "done\n");
+       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 == 1)
+                       if (ack == ACK)
                                return 0;
                        multi_ack = 1;
                        continue;
@@ -584,7 +691,12 @@ static struct ref *do_fetch_pack(int fd[2],
 
        if (is_repository_shallow() && !server_supports("shallow"))
                die("Server does not support shallow clients");
-       if (server_supports("multi_ack")) {
+       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;
@@ -615,6 +727,8 @@ static struct ref *do_fetch_pack(int fd[2],
                         */
                        warning("no common commits");
 
+       if (args.stateless_rpc)
+               packet_flush(fd[1]);
        if (get_pack(fd, pack_lockfile))
                die("git fetch-pack: fetch failed.");
 
@@ -685,6 +799,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
        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;
@@ -734,6 +850,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
                                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;
@@ -744,19 +869,27 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
        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;
+       if (args.stateless_rpc) {
+               conn = NULL;
+               fd[0] = 0;
+               fd[1] = 1;
        } else {
-               ref = NULL;
+               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) {
@@ -809,6 +942,7 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
 
        if (args.depth > 0) {
                struct cache_time mtime;
+               struct strbuf sb = STRBUF_INIT;
                char *shallow = git_path("shallow");
                int fd;
 
@@ -826,12 +960,14 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
 
                fd = hold_lock_file_for_update(&lock, shallow,
                                               LOCK_DIE_ON_ERROR);
-               if (!write_shallow_commits(fd, 0)) {
+               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();
index 817dd6bff0bf0ddc9a3d00191bb3c422300b878b..8654fa7a2dbe2c1ca76a493a4322447de5219b99 100644 (file)
@@ -14,6 +14,9 @@
 
 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
 };
 
@@ -23,7 +26,7 @@ enum {
        TAGS_SET = 2
 };
 
-static int append, force, keep, update_head_ok, verbosity;
+static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
 static int tags = TAGS_DEFAULT;
 static const char *depth;
 static const char *upload_pack;
@@ -32,16 +35,24 @@ 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"),
@@ -178,6 +189,8 @@ static int s_update_ref(const char *action,
        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);
@@ -269,7 +282,7 @@ static int update_local_ref(struct ref *ref,
                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);
+               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)" : "");
@@ -287,7 +300,7 @@ static int update_local_ref(struct ref *ref,
                        r ? "unable to update local ref" : "forced update");
                return r;
        } else {
-               sprintf(display, "! %-*s %-*s -> %s  (non fast forward)",
+               sprintf(display, "! %-*s %-*s -> %s  (non-fast-forward)",
                        SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
                        pretty_ref);
                return 1;
@@ -303,13 +316,16 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
        char note[1024];
        const char *what, *kind;
        struct ref *rm;
-       char *url, *filename = git_path("FETCH_HEAD");
+       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));
 
-       url = transport_anonymize_url(raw_url);
+       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;
 
@@ -454,7 +470,7 @@ static int quickfetch(struct ref *ref_map)
 
        for (ref = ref_map; ref; ref = ref->next) {
                if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 ||
-                   write_in_full(revlist.in, "\n", 1) < 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;
@@ -485,11 +501,34 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map)
        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",
+                               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;
-       string_list_insert(refname, list);
+       struct string_list_item *item = string_list_insert(refname, list);
+       item->util = (void *)sha1;
        return 0;
 }
 
@@ -504,57 +543,98 @@ static int will_fetch(struct ref **head, const unsigned char *sha1)
        return 0;
 }
 
+struct tag_data {
+       struct ref **head;
+       struct ref ***tail;
+};
+
+static int add_to_tail(struct string_list_item *item, void *cb_data)
+{
+       struct tag_data *data = (struct tag_data *)cb_data;
+       struct ref *rm = NULL;
+
+       /* We have already decided to ignore this item */
+       if (!item->util)
+               return 0;
+
+       rm = alloc_ref(item->string);
+       rm->peer_ref = alloc_ref(item->string);
+       hashcpy(rm->old_sha1, item->util);
+
+       **data->tail = rm;
+       *data->tail = &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;
+       struct string_list remote_refs = { NULL, 0, 0, 0 };
+       struct tag_data data = {head, tail};
        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;
 
-               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;
-                       }
+               /*
+                * 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 (!strcmp(ref->name + strlen(ref->name) - 3, "^{}")) {
+                       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 (!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);
+               /*
+                * 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;
 
-                       rm = alloc_ref(ref_name);
-                       rm->peer_ref = alloc_ref(ref_name);
-                       hashcpy(rm->old_sha1, ref_sha1);
+               item = NULL;
 
-                       **tail = rm;
-                       *tail = &rm->next;
-               }
-               free(ref_name);
+               /* 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(ref->name, &remote_refs);
+               item->util = (void *)ref->old_sha1;
        }
        string_list_clear(&existing_refs, 0);
-       string_list_clear(&new_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, call
+        * add_to_tail to add them to the list of refs to be fetched
+        */
+       for_each_string_list(add_to_tail, &remote_refs, &data);
+
+       string_list_clear(&remote_refs, 0);
 }
 
 static void check_not_current_branch(struct ref *ref_map)
@@ -574,9 +654,14 @@ static void check_not_current_branch(struct ref *ref_map)
 static int do_fetch(struct transport *transport,
                    struct refspec *refs, int ref_count)
 {
+       struct string_list existing_refs = { NULL, 0, 0, 0 };
+       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 (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
                tags = TAGS_SET;
        if (transport->remote->fetch_tags == -1)
@@ -586,7 +671,7 @@ static int do_fetch(struct transport *transport,
                die("Don't know how to fetch from %s", transport->url);
 
        /* if not appending, truncate FETCH_HEAD */
-       if (!append) {
+       if (!append && !dry_run) {
                char *filename = git_path("FETCH_HEAD");
                FILE *fp = fopen(filename, "w");
                if (!fp)
@@ -599,8 +684,13 @@ static int do_fetch(struct transport *transport,
                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 (rm->peer_ref) {
+                       peer_item = string_list_lookup(rm->peer_ref->name,
+                                                      &existing_refs);
+                       if (peer_item)
+                               hashcpy(rm->peer_ref->old_sha1,
+                                       peer_item->util);
+               }
        }
 
        if (tags == TAGS_DEFAULT && autotags)
@@ -609,6 +699,8 @@ static int do_fetch(struct transport *transport,
                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
@@ -639,33 +731,100 @@ static void set_option(const char *name, const char *value)
                        name, transport->url);
 }
 
-int cmd_fetch(int argc, const char **argv, const char *prefix)
+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(remote->name, list);
+       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(xstrndup(value, space),
+                                                  g->list);
+                       }
+                       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 = { name, 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(remote->name, list);
+       }
+       return 1;
+}
+
+static int fetch_multiple(struct string_list *list)
+{
+       int i, result = 0;
+       const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL, NULL };
+       int argc = 1;
+
+       if (dry_run)
+               argv[argc++] = "--dry-run";
+       if (prune)
+               argv[argc++] = "--prune";
+       if (verbosity >= 2)
+               argv[argc++] = "-v";
+       if (verbosity >= 1)
+               argv[argc++] = "-v";
+       else if (verbosity < 0)
+               argv[argc++] = "-q";
+
+       for (i = 0; i < list->nr; i++) {
+               const char *name = list->items[i].string;
+               argv[argc] = name;
+               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)
 {
-       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, prefix,
-                            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]);
+       transport = transport_get(remote, NULL);
        if (verbosity >= 2)
-               transport->verbose = 1;
+               transport->verbose = verbosity <= 3 ? verbosity : 3;
        if (verbosity < 0)
                transport->verbose = -1;
        if (upload_pack)
@@ -675,10 +834,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        if (depth)
                set_option(TRANS_OPT_DEPTH, depth);
 
-       if (argc > 1) {
+       if (argc > 0) {
                int j = 0;
                refs = xcalloc(argc + 1, sizeof(const char *));
-               for (i = 1; i < argc; i++) {
+               for (i = 0; i < argc; i++) {
                        if (!strcmp(argv[i], "tag")) {
                                char *ref;
                                i++;
@@ -705,3 +864,57 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        transport = NULL;
        return exit_code;
 }
+
+int cmd_fetch(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       struct string_list list = { NULL, 0, 0, 0 };
+       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;
+}
index d7cc8cafbfb4ea39e347828013b28d21cfe2be74..a5a83f14693b94adf3ae0dbc1b500b2e6b2be54d 100644 (file)
@@ -576,7 +576,7 @@ static void populate_value(struct refinfo *ref)
 
                if (!prefixcmp(name, "refname"))
                        refname = ref->refname;
-               else if(!prefixcmp(name, "upstream")) {
+               else if (!prefixcmp(name, "upstream")) {
                        struct branch *branch;
                        /* only local branches may have an upstream */
                        if (prefixcmp(ref->refname, "refs/heads/"))
index b3d38fa277b3d0024b9c46a62cf15bc2448cc1e7..0e5faae381ed94c26671fcae04ea96d3656d7627 100644 (file)
@@ -19,7 +19,7 @@ static int show_root;
 static int show_tags;
 static int show_unreachable;
 static int include_reflogs = 1;
-static int check_full;
+static int check_full = 1;
 static int check_strict;
 static int keep_cache_objects;
 static unsigned char head_sha1[20];
@@ -47,6 +47,7 @@ static void objreport(struct object *obj, const char *severity,
        fputs("\n", stderr);
 }
 
+__attribute__((format (printf, 2, 3)))
 static int objerror(struct object *obj, const char *err, ...)
 {
        va_list params;
@@ -57,6 +58,7 @@ static int objerror(struct object *obj, const char *err, ...)
        return -1;
 }
 
+__attribute__((format (printf, 3, 4)))
 static int fsck_error_func(struct object *obj, int type, const char *err, ...)
 {
        va_list params;
@@ -589,6 +591,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
        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) {
index 7d3e9cc7a04e0aa3eb378cb54b7b71fe17981c95..093517e390044055039b9f4e58132c73d17b741d 100644 (file)
@@ -216,10 +216,13 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                 */
                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");
+               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")
index f477659100fdc63bff5938b4c96f28eaefc07460..a5b6719a1af014497399ea6ff4f1a6f3852a0570 100644 (file)
@@ -13,6 +13,7 @@
 #include "parse-options.h"
 #include "userdiff.h"
 #include "grep.h"
+#include "quote.h"
 
 #ifndef NO_EXTERNAL_GREP
 #ifdef __unix__
@@ -52,26 +53,58 @@ static int grep_config(const char *var, const char *value, void *cb)
        return git_color_default_config(var, value, cb);
 }
 
+/*
+ * 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)
+static int pathspec_matches(const char **paths, const char *name, int max_depth)
 {
        int namelen, i;
        if (!paths || !*paths)
-               return 1;
+               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 (!matchlen ||
-                   ((matchlen <= namelen) &&
-                    !strncmp(name, match, matchlen) &&
-                    (match[matchlen-1] == '/' ||
-                     name[matchlen] == '\0' || name[matchlen] == '/')))
+               if (is_subdir(name, namelen, match, matchlen, max_depth))
                        return 1;
                if (!fnmatch(match, name, 0))
                        return 1;
@@ -125,8 +158,8 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char
        unsigned long size;
        char *data;
        enum object_type type;
-       char *to_free = NULL;
        int hit;
+       struct strbuf pathbuf = STRBUF_INIT;
 
        data = read_sha1_file(sha1, &type, &size);
        if (!data) {
@@ -134,26 +167,13 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char
                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;
-               }
+               quote_path_relative(name + tree_name_len, -1, &pathbuf, opt->prefix);
+               strbuf_insert(&pathbuf, 0, name, tree_name_len);
+               name = pathbuf.buf;
        }
        hit = grep_buffer(opt, name, data, size);
+       strbuf_release(&pathbuf);
        free(data);
-       free(to_free);
        return hit;
 }
 
@@ -163,6 +183,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)
        int i;
        char *data;
        size_t sz;
+       struct strbuf buf = STRBUF_INIT;
 
        if (lstat(filename, &st) < 0) {
        err_ret:
@@ -187,8 +208,9 @@ static int grep_file(struct grep_opt *opt, const char *filename)
        }
        close(i);
        if (opt->relative && opt->prefix_length)
-               filename += opt->prefix_length;
+               filename = quote_path_relative(filename, -1, &buf, opt->prefix);
        i = grep_buffer(opt, filename, data, sz);
+       strbuf_release(&buf);
        free(data);
        return i;
 }
@@ -345,7 +367,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                push_arg("-h");
        if (opt->regflags & REG_EXTENDED)
                push_arg("-E");
-       if (opt->regflags & REG_ICASE)
+       if (opt->ignore_case)
                push_arg("-i");
        if (opt->binary == GREP_BINARY_NOMATCH)
                push_arg("-I");
@@ -411,7 +433,11 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
 
                if (opt->color_external && strlen(opt->color_external) > 0)
                        push_arg(opt->color_external);
+       } else {
+               unsetenv("GREP_COLOR");
+               unsetenv("GREP_COLORS");
        }
+       unsetenv("GREP_OPTIONS");
 
        hit = 0;
        argc = nr;
@@ -421,7 +447,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                int kept;
                if (!S_ISREG(ce->ce_mode))
                        continue;
-               if (!pathspec_matches(paths, ce->name))
+               if (!pathspec_matches(paths, ce->name, opt->max_depth))
                        continue;
                name = ce->name;
                if (name[0] == '-') {
@@ -471,6 +497,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached,
                hit = external_grep(opt, paths, cached);
                if (hit >= 0)
                        return hit;
+               hit = 0;
        }
 #endif
 
@@ -478,7 +505,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached,
                struct cache_entry *ce = active_cache[nr];
                if (!S_ISREG(ce->ce_mode))
                        continue;
-               if (!pathspec_matches(paths, ce->name))
+               if (!pathspec_matches(paths, ce->name, opt->max_depth))
                        continue;
                /*
                 * If CE_VALID is on, we assume worktree file and its cache entry
@@ -538,7 +565,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
                        strbuf_addch(&pathbuf, '/');
 
                down = pathbuf.buf + tn_len;
-               if (!pathspec_matches(paths, down))
+               if (!pathspec_matches(paths, down, opt->max_depth))
                        ;
                else if (S_ISREG(entry.mode))
                        hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
@@ -608,7 +635,7 @@ 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;
+       struct strbuf sb = STRBUF_INIT;
 
        patterns = fopen(arg, "r");
        if (!patterns)
@@ -683,8 +710,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                OPT_GROUP(""),
                OPT_BOOLEAN('v', "invert-match", &opt.invert,
                        "show non-matching lines"),
-               OPT_BIT('i', "ignore-case", &opt.regflags,
-                       "case insensitive matching", REG_ICASE),
+               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,
@@ -692,6 +719,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                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),
@@ -762,12 +792,21 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                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.regflags = REG_NEWLINE;
+       opt.max_depth = -1;
 
        strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD);
        opt.color = -1;
@@ -802,6 +841,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                external_grep_allowed = 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");
        compile_grep_patterns(&opt);
@@ -832,15 +873,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                        verify_filename(prefix, argv[j]);
        }
 
-       if (i < argc) {
+       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;
index e1eba778a526f500b0a65a6b112e468f04a3f869..09ad4b04f9fa860a32580717e61acc942de23388 100644 (file)
@@ -372,6 +372,7 @@ 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);
+       die("no info viewer handled the request");
 }
 
 static void get_html_page_path(struct strbuf *page_path, const char *page)
@@ -416,9 +417,6 @@ int cmd_help(int argc, const char **argv, const char *prefix)
        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, prefix, builtin_help_options,
                        builtin_help_usage, 0);
 
@@ -436,6 +434,9 @@ int cmd_help(int argc, const char **argv, const char *prefix)
                return 0;
        }
 
+       setup_git_directory_gently(&nongit);
+       git_config(git_help_config, NULL);
+
        alias = alias_lookup(argv[0]);
        if (alias && !is_git_command(argv[0])) {
                printf("`git %s' is aliased to `%s'\n", argv[0], alias);
index 4a5600631c5800d7f9d844118f37c0672b89b581..dd84caecbc2a07bca90c8524157d50a8fd5ae316 100644 (file)
@@ -6,6 +6,7 @@
 #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"
@@ -370,8 +371,16 @@ static int guess_repository_type(const char *git_dir)
        return 1;
 }
 
-static const char init_db_usage[] =
-"git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]]";
+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.
@@ -384,25 +393,60 @@ 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);
+       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)), 0);
        }
 
        if (init_shared_repository != -1)
index 0c2fa0ae2dc3f23cd52d2e8d765805b07262a7d6..1766349550f5b4204e77f6f6eeca486cee322ca1 100644 (file)
@@ -27,10 +27,15 @@ static int default_show_root = 1;
 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 void cmd_log_init(int argc, const char **argv, const char *prefix,
                      struct rev_info *rev)
 {
        int i;
+       int decoration_style = 0;
 
        rev->abbrev = DEFAULT_ABBREV;
        rev->commit_format = CMIT_FMT_DEFAULT;
@@ -45,6 +50,12 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
        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, "HEAD");
 
        if (rev->diffopt.pickaxe || rev->diffopt.filter)
@@ -57,13 +68,26 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
                if (!strcmp(arg, "--decorate")) {
-                       load_ref_decorations();
-                       rev->show_decorations = 1;
+                       decoration_style = DECORATE_SHORT_REFS;
+               } else if (!prefixcmp(arg, "--decorate=")) {
+                       const char *v = skip_prefix(arg, "--decorate=");
+                       if (!strcmp(v, "full"))
+                               decoration_style = DECORATE_FULL_REFS;
+                       else if (!strcmp(v, "short"))
+                               decoration_style = DECORATE_SHORT_REFS;
+                       else
+                               die("invalid --decorate option: %s", arg);
                } else if (!strcmp(arg, "--source")) {
                        rev->show_source = 1;
+               } else if (!strcmp(arg, "-h")) {
+                       usage(builtin_log_usage);
                } else
                        die("unrecognized argument: %s", arg);
        }
+       if (decoration_style) {
+               rev->show_decorations = 1;
+               load_ref_decorations(decoration_style);
+       }
 }
 
 /*
@@ -257,7 +281,7 @@ static void show_tagger(char *buf, int len, struct rev_info *rev)
        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);
+       printf("%s", out.buf);
        strbuf_release(&out);
 }
 
@@ -329,11 +353,14 @@ int cmd_show(int argc, const char **argv, const char *prefix)
                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);
@@ -345,12 +372,15 @@ int cmd_show(int argc, const char **argv, const char *prefix)
                        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;
@@ -658,6 +688,10 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
        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);
@@ -863,6 +897,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        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",
@@ -894,6 +929,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                            "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", PARSE_OPT_NONEG,
@@ -938,7 +976,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
         */
        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_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
+                            PARSE_OPT_KEEP_DASHDASH);
 
        if (do_signoff) {
                const char *committer;
@@ -999,9 +1038,20 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        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 (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);
@@ -1209,6 +1259,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
                argv++;
        }
 
+       if (argc > 1 && !strcmp(argv[1], "-h"))
+               usage(cherry_usage);
+
        switch (argc) {
        case 4:
                limit = argv[3];
@@ -1276,8 +1329,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
 
                if (verbose) {
                        struct strbuf buf = STRBUF_INIT;
+                       struct pretty_print_context ctx = {0};
                        pretty_print_commit(CMIT_FMT_ONELINE, commit,
-                                           &buf, 0, NULL, NULL, 0, 0);
+                                           &buf, &ctx);
                        printf("%c %s %s\n", sign,
                               sha1_to_hex(commit->object.sha1), buf.buf);
                        strbuf_release(&buf);
index f473220502027b4f9e6ed9a17ffafd42538add80..c9a03e5427bbf1390aeefb9d48b4e362ca6a8e1b 100644 (file)
@@ -171,8 +171,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
                        int dtype = ce_to_dtype(ce);
-                       if (excluded(dir, ce->name, &dtype) !=
-                                       !!(dir->flags & DIR_SHOW_IGNORED))
+                       if (dir->flags & DIR_SHOW_IGNORED &&
+                           !excluded(dir, ce->name, &dtype))
                                continue;
                        if (show_unmerged && !ce_stage(ce))
                                continue;
@@ -187,8 +187,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
                        struct stat st;
                        int err;
                        int dtype = ce_to_dtype(ce);
-                       if (excluded(dir, ce->name, &dtype) !=
-                                       !!(dir->flags & DIR_SHOW_IGNORED))
+                       if (dir->flags & DIR_SHOW_IGNORED &&
+                           !excluded(dir, ce->name, &dtype))
                                continue;
                        if (ce->ce_flags & CE_UPDATE)
                                continue;
@@ -524,11 +524,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                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);
-       }
+       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 |
index 78a88f74769645f0be86aa77d3dee3f5e99c916f..70f5622d9d49aae4080b38ee4487cc6e403a9d2c 100644 (file)
@@ -86,10 +86,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
                        pattern[j - i] = p;
                }
        }
-       remote = nongit ? NULL : remote_get(dest);
-       if (remote && !remote->url_nr)
+       remote = remote_get(dest);
+       if (!remote->url_nr)
                die("remote %s has no configured URL", dest);
-       transport = transport_get(remote, remote ? remote->url[0] : dest);
+       transport = transport_get(remote, NULL);
        if (uploadpack != NULL)
                transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
 
index 22008dfa8fb94d971b6fb1bd7342ede02a8d3fd0..4484185afc4c144bc0d88d9c3832cbeb1515841b 100644 (file)
@@ -9,6 +9,7 @@
 #include "commit.h"
 #include "quote.h"
 #include "builtin.h"
+#include "parse-options.h"
 
 static int line_termination = '\n';
 #define LS_RECURSIVE 1
@@ -22,8 +23,10 @@ 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 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)
 {
@@ -117,76 +120,53 @@ 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);
-       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++;
+
+       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 < 2)
-               usage(ls_tree_usage);
-       if (get_sha1(argv[1], sha1))
-               die("Not a valid object name %s", argv[1]);
+       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 + 2);
+       pathspec = get_pathspec(prefix, argv + 1);
        tree = parse_tree_indirect(sha1);
        if (!tree)
                die("not a tree object");
index 92637ac0bae82d0b88e267b572a51a75299cda5c..a50ac2256cdbacd76ed44a50804212be07f949db 100644 (file)
@@ -10,6 +10,7 @@
 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;
@@ -25,6 +26,8 @@ static enum  {
 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
@@ -219,35 +222,41 @@ static int is_multipart_boundary(const struct strbuf *line)
 
 static void cleanup_subject(struct strbuf *subject)
 {
-       char *pos;
-       size_t remove;
-       while (subject->len) {
-               switch (*subject->buf) {
+       size_t at = 0;
+
+       while (at < subject->len) {
+               char *pos;
+               size_t remove;
+
+               switch (subject->buf[at]) {
                case 'r': case 'R':
-                       if (subject->len <= 3)
+                       if (subject->len <= at + 3)
                                break;
-                       if (!memcmp(subject->buf + 1, "e:", 2)) {
-                               strbuf_remove(subject, 0, 3);
+                       if (!memcmp(subject->buf + at + 1, "e:", 2)) {
+                               strbuf_remove(subject, at, 3);
                                continue;
                        }
+                       at++;
                        break;
                case ' ': case '\t': case ':':
-                       strbuf_remove(subject, 0, 1);
+                       strbuf_remove(subject, at, 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;
+                       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;
                }
-               strbuf_trim(subject);
-               return;
+               break;
        }
+       strbuf_trim(subject);
 }
 
 static void cleanup_space(struct strbuf *sb)
@@ -712,6 +721,56 @@ static inline int patchbreak(const struct strbuf *line)
        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))) {
+                       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;
@@ -723,14 +782,42 @@ 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)
-                       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;
@@ -765,7 +852,6 @@ static void handle_filter(struct strbuf *line)
 
 static void handle_body(void)
 {
-       int len = 0;
        struct strbuf prev = STRBUF_INIT;
 
        /* Skip up to the first boundary */
@@ -775,8 +861,6 @@ static void handle_body(void)
        }
 
        do {
-               strbuf_setlen(&line, line.len + len);
-
                /* process any boundary lines */
                if (*content_top && is_multipart_boundary(&line)) {
                        /* flush any leftover */
@@ -832,10 +916,7 @@ static void handle_body(void)
                        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)));
+       } while (!strbuf_getwholeline(&line, fin, '\n'));
 
 handle_body_out:
        strbuf_release(&prev);
@@ -891,12 +972,9 @@ static void handle_info(void)
        fprintf(fout, "\n");
 }
 
-static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
-                   const char *msg, const char *patch)
+static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch)
 {
        int peek;
-       keep_subject = ks;
-       metainfo_charset = encoding;
        fin = in;
        fout = out;
 
@@ -930,8 +1008,20 @@ static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
        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] [-u | --encoding=<encoding> | -n] msg patch <mail >info";
+       "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)
 {
@@ -940,7 +1030,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
        /* NEEDSWORK: might want to do the optional .git/ directory
         * discovery
         */
-       git_config(git_default_config, NULL);
+       git_config(git_mailinfo_config, NULL);
 
        def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8");
        metainfo_charset = def_charset;
@@ -948,12 +1038,20 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
        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++;
@@ -962,5 +1060,5 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
        if (argc != 3)
                usage(mailinfo_usage);
 
-       return !!mailinfo(stdin, stdout, keep_subject, metainfo_charset, argv[1], argv[2]);
+       return !!mailinfo(stdin, stdout, argv[1], argv[2]);
 }
index ad5f6b593df45f01360f3daa8b37d024ee793e9e..207e358ed19cecb8cf7b57d59a9149619909459d 100644 (file)
@@ -7,6 +7,7 @@
 #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] -o<directory> [<mbox>|<Maildir>...]";
@@ -42,26 +43,8 @@ static int is_from_line(const char *line, int len)
        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;
-}
+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
@@ -71,10 +54,9 @@ int read_line_with_nul(char *buf, int size, FILE *in)
 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(buflen);
+       int is_bare = !is_from_line(buf.buf, buf.len);
 
        if (is_bare && !allow_bare)
                goto corrupt;
@@ -82,26 +64,29 @@ static int split_one(FILE *mbox, const char *name, int allow_bare)
        fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
        if (fd < 0)
                die_errno("cannot open output file '%s'", name);
-       output = fdopen(fd, "w");
+       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 (;;) {
-               int is_partial = len && buf[len-1] != '\n';
+               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, 1, len, output) != len)
+               if (fwrite(buf.buf, 1, buf.len, output) != buf.len)
                        die_errno("cannot write output");
 
-               len = read_line_with_nul(buf, sizeof(buf), mbox);
-               if (len == 0) {
+               if (strbuf_getwholeline(&buf, mbox, '\n')) {
                        if (feof(mbox)) {
                                status = 1;
                                break;
                        }
                        die_errno("cannot read mbox");
                }
-               if (!is_partial && !is_bare && is_from_line(buf, len))
+               if (!is_bare && is_from_line(buf.buf, buf.len))
                        break; /* done with one message */
        }
        fclose(output);
@@ -166,7 +151,7 @@ static int split_maildir(const char *maildir, const char *dir,
                        goto out;
                }
 
-               if (fgets(buf, sizeof(buf), f) == NULL) {
+               if (strbuf_getwholeline(&buf, f, '\n')) {
                        error("cannot read mail %s (%s)", file, strerror(errno));
                        goto out;
                }
@@ -203,7 +188,7 @@ static int split_mbox(const char *file, const char *dir, int allow_bare,
        } while (isspace(peek));
        ungetc(peek, f);
 
-       if (fgets(buf, sizeof(buf), f) == NULL) {
+       if (strbuf_getwholeline(&buf, f, '\n')) {
                /* empty stdin is OK */
                if (f != stdin) {
                        error("cannot read mbox %s", file);
@@ -246,8 +231,12 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix)
                        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] ) {
index a6ec2f7ab76db27afbb63aacf3bba60b4e73b3f6..54e7ec22370ce63150ddc93ebe252bea09f5064a 100644 (file)
@@ -23,7 +23,7 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
 }
 
 static const char * const merge_base_usage[] = {
-       "git merge-base [--all] <commit-id> <commit-id>...",
+       "git merge-base [-a|--all] <commit> <commit>...",
        NULL
 };
 
index 8f5bbaf402e020e308e7af9cedb7df1b2ec5a2b7..684411694f64ca216fb03430df2d2a0ec9ea93ff 100644 (file)
@@ -10,6 +10,9 @@
 #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
 };
@@ -17,6 +20,9 @@ static const char *diff_index_args[] = {
 
 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
index d26a96e486f2a84fdfc54527d15b9acdf7ea34bb..710674c6b2cccec4f1e0baa477a11d78cda72833 100644 (file)
@@ -33,7 +33,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
        }
 
        if (argc < 4)
-               die("Usage: %s <base>... -- <head> <remote> ...", argv[0]);
+               usagef("%s <base>... -- <head> <remote> ...", argv[0]);
 
        for (i = 1; i < argc; ++i) {
                if (!strcmp(argv[i], "--"))
index 82b546689c500649285ea2c7825171f572c3758e..f1c84d759dd44f661fb76741d528e43702dfc901 100644 (file)
@@ -43,6 +43,7 @@ static const char * const builtin_merge_usage[] = {
 
 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;
@@ -70,7 +71,7 @@ static int option_parse_message(const struct option *opt,
        if (unset)
                strbuf_setlen(buf, 0);
        else if (arg) {
-               strbuf_addf(buf, "%s\n\n", arg);
+               strbuf_addf(buf, "%s%s", buf->len ? "\n\n" : "", arg);
                have_message = 1;
        } else
                return error("switch `m' requires a value");
@@ -106,8 +107,8 @@ static struct strategy *get_strategy(const char *name)
                                        found = 1;
                        if (!found)
                                add_cmdname(&not_strategies, ent->name, ent->len);
-                       exclude_cmds(&main_cmds, &not_strategies);
                }
+               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);
@@ -166,7 +167,9 @@ static struct option builtin_merge_options[] = {
        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)"),
+               "allow fast-forward (default)"),
+       OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
+               "abort if fast-forward is not possible"),
        OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
                "merge strategy to use", option_parse_strategy),
        OPT_CALLBACK('m', "message", &merge_msg, "message",
@@ -264,6 +267,7 @@ static void squash_message(void)
        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);
@@ -285,13 +289,15 @@ static void squash_message(void)
        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, rev.abbrev,
-                       NULL, NULL, rev.date_mode, 0);
+               pretty_print_commit(rev.commit_format, commit, &out, &ctx);
        }
        if (write(fd, out.buf, out.len) < 0)
                die_errno("Writing SQUASH_MSG");
@@ -358,6 +364,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
        struct strbuf buf = STRBUF_INIT;
        struct strbuf bname = STRBUF_INIT;
        const char *ptr;
+       char *found_ref;
        int len, early;
 
        strbuf_branchname(&bname, remote);
@@ -368,14 +375,17 @@ static void merge_name(const char *remote, struct strbuf *msg)
        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, NULL);
-
-       if (!hashcmp(remote_head->sha1, branch_head)) {
-               strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
-                       sha1_to_hex(branch_head), remote);
-               goto cleanup;
+       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> */
@@ -594,7 +604,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                discard_cache();
                if (read_cache() < 0)
                        die("failed to read the cache");
-               return -ret;
+               return ret;
        }
 }
 
@@ -646,6 +656,7 @@ static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
        opts.verbose_update = 1;
        opts.merge = 1;
        opts.fn = twoway_merge;
+       opts.msgs = get_porcelain_error_msgs();
 
        trees[nr_trees] = parse_tree_indirect(head);
        if (!trees[nr_trees++])
@@ -788,7 +799,7 @@ static int suggest_conflicts(void)
 static struct commit *is_old_style_invocation(int argc, const char **argv)
 {
        struct commit *second_token = NULL;
-       if (argc > 1) {
+       if (argc > 2) {
                unsigned char second_sha1[20];
 
                if (get_sha1(argv[1], second_sha1))
@@ -836,7 +847,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        const char *best_strategy = NULL, *wt_strategy = NULL;
        struct commit_list **remotes = &remoteheads;
 
-       setup_work_tree();
        if (file_exists(git_path("MERGE_HEAD")))
                die("You have not concluded your merge. (MERGE_HEAD exists)");
        if (read_cache_unmerged())
@@ -870,6 +880,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                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);
@@ -924,11 +937,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                 * 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 (!have_message) {
+                       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)
@@ -1009,7 +1024,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                                hex,
                                find_unique_abbrev(remoteheads->item->object.sha1,
                                DEFAULT_ABBREV));
-               strbuf_addstr(&msg, "Fast forward");
+               strbuf_addstr(&msg, "Fast-forward");
                if (have_message)
                        strbuf_addstr(&msg,
                                " (no commit created; -m option ignored)");
@@ -1027,16 +1042,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        } else if (!remoteheads->next && common->next)
                ;
                /*
-                * We are not doing octopus and not fast forward.  Need
+                * 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
+                * We are not doing octopus, not fast-forward, and have
                 * only one common.
                 */
                refresh_cache(REFRESH_QUIET);
-               if (allow_trivial) {
+               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");
@@ -1075,6 +1090,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                }
        }
 
+       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);
 
index b592c367b2cc016a50db6d49948e9efbbf0e8f2f..f633d81424f5e41cb85ac660dbdc9f4913852673 100644 (file)
@@ -53,7 +53,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
        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('f', "force", &force, "force move/rename even if target exists"),
                OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
                OPT_END(),
        };
@@ -64,15 +64,15 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
 
        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, 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);
index ef4bf6bc14aadffcd85999c2eca59e5a0e7e6451..4429d53a1e82b81f0a82e34bf2d6ae463aeeadee 100644 (file)
 #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]";
+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;
@@ -86,7 +87,7 @@ 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 max_delta_cache_size = 256 * 1024 * 1024;
 static unsigned long cache_max_small_delta_size = 1000;
 
 static unsigned long window_memory_limit = 0;
@@ -1008,6 +1009,33 @@ static void add_preferred_base(unsigned char *sha1)
        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) {
@@ -1599,9 +1627,11 @@ static void *threaded_find_deltas(void *arg)
 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];
+       struct thread_params *p;
        int i, ret, active_threads = 0;
 
+       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);
                return;
@@ -1609,6 +1639,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
        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++) {
@@ -1717,6 +1748,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
                        active_threads--;
                }
        }
+       free(p);
 }
 
 #else
@@ -1808,7 +1840,7 @@ static void prepare_pack(int window, int depth)
 
 static int git_pack_config(const char *k, const char *v, void *cb)
 {
-       if(!strcmp(k, "pack.window")) {
+       if (!strcmp(k, "pack.window")) {
                window = git_config_int(k, v);
                return 0;
        }
@@ -2093,11 +2125,14 @@ 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";
@@ -2190,6 +2225,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        progress = 2;
                        continue;
                }
+               if (!strcmp("--all-progress-implied", arg)) {
+                       all_progress_implied = 1;
+                       continue;
+               }
                if (!strcmp("-q", arg)) {
                        progress = 0;
                        continue;
@@ -2293,10 +2332,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        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
+       if (progress && all_progress_implied)
+               progress = 2;
 
        prepare_packed_git();
 
@@ -2308,6 +2345,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                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);
index 00590b1c3c2cceda7a75537c8680a96cd2ef84d4..f9463deec2f5a5875ee9870e67c990a2fea942db 100644 (file)
@@ -1,9 +1,12 @@
 #include "builtin.h"
 #include "cache.h"
 #include "progress.h"
+#include "parse-options.h"
 
-static const char prune_packed_usage[] =
-"git prune-packed [-n] [-q]";
+static const char * const prune_packed_usage[] = {
+       "git prune-packed [-n|--dry-run] [-q|--quiet]",
+       NULL
+};
 
 #define DRY_RUN 01
 #define VERBOSE 02
@@ -68,24 +71,16 @@ void prune_packed_objects(int opts)
 
 int cmd_prune_packed(int argc, const char **argv, const char *prefix)
 {
-       int i;
-       int opts = VERBOSE;
+       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()
+       };
 
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
+       argc = parse_options(argc, argv, prefix, prune_packed_options,
+                            prune_packed_usage, 0);
 
-               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;
 }
index 0ed9cce4a23aa575c1e131a808cedd4f8bb542f4..8459aec8e8ea9d24a13448cf950d2e160361fd9d 100644 (file)
@@ -140,6 +140,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
        char *s;
 
        save_commit_buffer = 0;
+       read_replace_refs = 0;
        init_revisions(&revs, prefix);
 
        argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
index 1d92e22f0aef914217c6a68e2597426bb529e4ba..dcfb53f1884d2648993214b4378afbd1f83760b0 100644 (file)
@@ -10,7 +10,7 @@
 #include "parse-options.h"
 
 static const char * const push_usage[] = {
-       "git push [--all | --mirror] [--dry-run] [--porcelain] [--tags] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v] [<repository> <refspec>...]",
+       "git push [<options>] [<repository> <refspec>...]",
        NULL,
 };
 
@@ -66,7 +66,6 @@ static void setup_push_tracking(void)
 
 static void setup_default_push_refspecs(void)
 {
-       git_config(git_default_config, NULL);
        switch (push_default) {
        default:
        case PUSH_DEFAULT_MATCHING:
@@ -88,6 +87,37 @@ static void setup_default_push_refspecs(void)
        }
 }
 
+static int push_with_options(struct transport *transport, int flags)
+{
+       int err;
+       int nonfastforward;
+       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", 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) {
+               printf("To prevent you from losing history, non-fast-forward updates were rejected\n"
+                      "Merge the remote changes before pushing again.  See the 'non-fast-forward'\n"
+                      "section of 'git push --help' for details.\n");
+       }
+
+       return 1;
+}
+
 static int do_push(const char *repo, int flags)
 {
        int i, errs;
@@ -136,26 +166,19 @@ static int do_push(const char *repo, int flags)
                url = remote->url;
                url_nr = remote->url_nr;
        }
-       for (i = 0; i < url_nr; i++) {
+       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, 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", 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'", url[i]);
-               errs++;
+                       transport_get(remote, NULL);
+
+               if (push_with_options(transport, flags))
+                       errs++;
        }
        return !!errs;
 }
@@ -166,15 +189,15 @@ int cmd_push(int argc, const char **argv, const char *prefix)
        int tags = 0;
        int rc;
        const char *repo = NULL;        /* default repository */
-
        struct option options[] = {
+               OPT_BIT('q', "quiet", &flags, "be quiet", TRANSPORT_PUSH_QUIET),
                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_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"),
@@ -183,6 +206,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
+       git_config(git_default_config, NULL);
        argc = parse_options(argc, argv, prefix, options, push_usage, 0);
 
        if (tags)
index 82e25eaa0758d8b9584592f4072f81eeccb0bf1d..2a3a32cbfe83a0e0366d04a99bb49606f647f594 100644 (file)
@@ -12,6 +12,7 @@
 #include "unpack-trees.h"
 #include "dir.h"
 #include "builtin.h"
+#include "parse-options.h"
 
 static int nr_trees;
 static struct tree *trees[MAX_UNPACK_TREES];
@@ -29,7 +30,39 @@ static int list_tree(unsigned char *sha1)
        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 const char * const read_tree_usage[] = {
+       "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]]  [--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 struct lock_file lock_file;
 
@@ -39,6 +72,34 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
        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_END()
+       };
 
        memset(&opts, 0, sizeof(opts));
        opts.head_idx = -1;
@@ -47,107 +108,23 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
 
        git_config(git_default_config, NULL);
 
-       newfd = hold_locked_index(&lock_file, 1);
+       argc = parse_options(argc, argv, unused_prefix, read_tree_options,
+                            read_tree_usage, 0);
 
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               /* "-u" means "update", meaning that a merge will update
-                * the working tree.
-                */
-               if (!strcmp(arg, "-u")) {
-                       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;
-               }
+       newfd = hold_locked_index(&lock_file, 1);
 
-               /* "-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;
-               }
+       prefix_set = opts.prefix ? 1 : 0;
+       if (1 < opts.merge + opts.reset + prefix_set)
+               die("Which one? -m, --reset, or --prefix?");
 
-               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;
-               }
+       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;
+       }
 
-               /* using -u and -i at the same time makes no sense */
-               if (1 < opts.index_only + opts.update)
-                       usage(read_tree_usage);
+       for (i = 0; i < argc; i++) {
+               const char *arg = argv[i];
 
                if (get_sha1(arg, sha1))
                        die("Not a valid object name %s", arg);
@@ -155,8 +132,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                        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)
-               usage(read_tree_usage);
+               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)
index db12b813acbe9bade2b854b3a94fed13f6210499..4320c93e700a08911e42e3e949656af67b675244 100644 (file)
@@ -28,6 +28,8 @@ static int transfer_unpack_limit = -1;
 static int unpack_limit = 100;
 static int report_status;
 static int prefer_ofs_delta = 1;
+static int auto_update_server_info;
+static int auto_gc = 1;
 static const char *head_name;
 static char *capabilities_to_send;
 
@@ -88,6 +90,16 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
                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);
 }
 
@@ -123,31 +135,6 @@ static struct command *commands;
 static const char pre_receive_hook[] = "hooks/pre-receive";
 static const char post_receive_hook[] = "hooks/post-receive";
 
-static int run_status(int code, const char *cmd_name)
-{
-       switch (code) {
-       case 0:
-               return 0;
-       case -ERR_RUN_COMMAND_FORK:
-               return error("fork of %s failed", cmd_name);
-       case -ERR_RUN_COMMAND_EXEC:
-               return error("execute of %s failed", cmd_name);
-       case -ERR_RUN_COMMAND_PIPE:
-               return error("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", cmd_name);
-       case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
-               return error("%s died strangely", cmd_name);
-       default:
-               error("%s exited with error code %d", cmd_name, -code);
-               return -code;
-       }
-}
-
 static int run_receive_hook(const char *hook_name)
 {
        static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
@@ -174,7 +161,7 @@ static int run_receive_hook(const char *hook_name)
 
        code = start_command(&proc);
        if (code)
-               return run_status(code, hook_name);
+               return code;
        for (cmd = commands; cmd; cmd = cmd->next) {
                if (!cmd->error_string) {
                        size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
@@ -186,7 +173,7 @@ static int run_receive_hook(const char *hook_name)
                }
        }
        close(proc.in);
-       return run_status(finish_command(&proc), hook_name);
+       return finish_command(&proc);
 }
 
 static int run_update_hook(struct command *cmd)
@@ -203,9 +190,8 @@ static int run_update_hook(struct command *cmd)
        argv[3] = sha1_to_hex(cmd->new_sha1);
        argv[4] = NULL;
 
-       return run_status(run_command_v_opt(argv, RUN_COMMAND_NO_STDIN |
-                                       RUN_COMMAND_STDOUT_TO_STDERR),
-                       update_hook);
+       return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN |
+                                       RUN_COMMAND_STDOUT_TO_STDERR);
 }
 
 static int is_ref_checked_out(const char *ref)
@@ -343,9 +329,9 @@ static const char *update(struct command *cmd)
                                break;
                free_commit_list(bases);
                if (!ent) {
-                       error("denying non-fast forward %s"
+                       error("denying non-fast-forward %s"
                              " (you should pull first)", name);
-                       return "non-fast forward";
+                       return "non-fast-forward";
                }
        }
        if (run_update_hook(cmd)) {
@@ -407,7 +393,6 @@ static void run_update_post_hook(struct command *cmd)
        argv[argc] = NULL;
        status = run_command_v_opt(argv, RUN_COMMAND_NO_STDIN
                        | RUN_COMMAND_STDOUT_TO_STDERR);
-       run_status(status, update_post_hook);
 }
 
 static void execute_commands(const char *unpacker_error)
@@ -525,7 +510,6 @@ static const char *unpack(void)
                code = run_command_v_opt(unpacker, RUN_GIT_CMD);
                if (!code)
                        return NULL;
-               run_status(code, unpacker[0]);
                return "unpack-objects abnormal exit";
        } else {
                const char *keeper[7];
@@ -551,7 +535,6 @@ static const char *unpack(void)
                ip.git_cmd = 1;
                status = start_command(&ip);
                if (status) {
-                       run_status(status, keeper[0]);
                        return "index-pack fork failed";
                }
                pack_lockfile = index_pack_lockfile(ip.out);
@@ -561,7 +544,6 @@ static const char *unpack(void)
                        reprepare_packed_git();
                        return NULL;
                }
-               run_status(status, keeper[0]);
                return "index-pack abnormal exit";
        }
 }
@@ -633,6 +615,8 @@ static void add_alternate_refs(void)
 
 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;
 
@@ -641,7 +625,15 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                const char *arg = *argv++;
 
                if (*arg == '-') {
-                       /* Do flag handling here */
+                       if (!strcmp(arg, "--advertise-refs")) {
+                               advertise_refs = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--stateless-rpc")) {
+                               stateless_rpc = 1;
+                               continue;
+                       }
+
                        usage(receive_pack_usage);
                }
                if (dir)
@@ -670,12 +662,16 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                " report-status delete-refs ofs-delta " :
                " report-status delete-refs ";
 
-       add_alternate_refs();
-       write_head_info();
-       clear_extra_refs();
+       if (advertise_refs || !stateless_rpc) {
+               add_alternate_refs();
+               write_head_info();
+               clear_extra_refs();
 
-       /* EOF */
-       packet_flush(1);
+               /* EOF */
+               packet_flush(1);
+       }
+       if (advertise_refs)
+               return 0;
 
        read_head_info();
        if (commands) {
@@ -690,6 +686,14 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                        report(unpack_status);
                run_receive_hook(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);
        }
        return 0;
 }
index ddfdf5a3cbc79003dfbad14af8d1f047e3594aa6..749821078df129cf13de34ebbd40a8cb7a38e00e 100644 (file)
@@ -362,7 +362,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
                } 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 ||
+                        write_str_in_full(lock->lock_fd, "\n") != 1 ||
                         close_ref(lock) < 0)) {
                        status |= error("Couldn't write %s",
                                lock->lk->filename);
@@ -694,10 +694,13 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
  */
 
 static const char reflog_usage[] =
-"git reflog (expire | ...)";
+"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);
index 008abfe0922f6d1662aebcfbf63ba9f47de4ae65..a5019397ff840204963e4e4b23d2ca16de1a332a 100644 (file)
@@ -12,10 +12,45 @@ static const char * const builtin_remote_usage[] = {
        "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
        "git remote rename <old> <new>",
        "git remote rm <name>",
-       "git remote set-head <name> [-a | -d | <branch>]",
-       "git remote show [-n] <name>",
+       "git remote 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]",
+       "git remote [-v | --verbose] update [-p | --prune] [group | remote]",
+       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_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
 };
 
@@ -70,7 +105,6 @@ static int add(int argc, const char **argv)
        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),
@@ -79,11 +113,11 @@ static int add(int argc, const char **argv)
                OPT_END()
        };
 
-       argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_add_usage,
                             0);
 
        if (argc < 2)
-               usage_with_options(builtin_remote_usage, options);
+               usage_with_options(builtin_remote_add_usage, options);
 
        name = argv[0];
        url = argv[1];
@@ -227,32 +261,10 @@ struct ref_states {
        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;
+       struct ref *ref, *stale_refs;
        int i;
 
        for (i = 0; i < states->remote->fetch_refspec_nr; i++)
@@ -260,7 +272,9 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
                        die("Could not get fetch map for refspec %s",
                                states->remote->fetch_refspec[i]);
 
-       states->new.strdup_strings = states->tracked.strdup_strings = 1;
+       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))
@@ -268,11 +282,17 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
                else
                        string_list_append(abbrev_branch(ref->name), &states->tracked);
        }
+       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(abbrev_branch(ref->name), &states->stale);
+               item->util = xstrdup(ref->name);
+       }
+       free_refs(stale_refs);
        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;
@@ -385,7 +405,7 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
        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)
+       for (ref = matches; ref; ref = ref->next)
                string_list_append(abbrev_branch(ref->name), &states->heads);
 
        free_refs(fetch_map);
@@ -484,7 +504,7 @@ static int read_remote_branches(const char *refname,
        const char *symref;
 
        strbuf_addf(&buf, "refs/remotes/%s", rename->old);
-       if(!prefixcmp(refname, buf.buf)) {
+       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)
@@ -540,7 +560,7 @@ static int mv(int argc, const char **argv)
        int i;
 
        if (argc != 3)
-               usage_with_options(builtin_remote_usage, options);
+               usage_with_options(builtin_remote_rename_usage, options);
 
        rename.old = argv[1];
        rename.new = argv[2];
@@ -681,7 +701,7 @@ static int rm(int argc, const char **argv)
        int i, result;
 
        if (argc != 2)
-               usage_with_options(builtin_remote_usage, options);
+               usage_with_options(builtin_remote_rm_usage, options);
 
        remote = remote_get(argv[1]);
        if (!remote)
@@ -750,7 +770,7 @@ static void clear_push_info(void *util, const char *string)
 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->stale, 1);
        string_list_clear(&states->tracked, 0);
        string_list_clear(&states->heads, 0);
        string_list_clear_func(&states->push, clear_push_info);
@@ -953,7 +973,7 @@ static int show_push_info_item(struct string_list_item *item, void *cb_data)
                status = "up to date";
                break;
        case PUSH_STATUS_FASTFORWARD:
-               status = "fast forwardable";
+               status = "fast-forwardable";
                break;
        case PUSH_STATUS_OUTOFDATE:
                status = "local out of date";
@@ -976,7 +996,6 @@ 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()
        };
@@ -984,7 +1003,7 @@ static int show(int argc, const char **argv)
        struct string_list info_list = { NULL, 0, 0, 0 };
        struct show_info info;
 
-       argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage,
                             0);
 
        if (argc < 1)
@@ -1081,14 +1100,13 @@ static int set_head(int argc, const char **argv)
        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, NULL, options, builtin_remote_usage,
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_sethead_usage,
                             0);
        if (argc)
                strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
@@ -1114,7 +1132,7 @@ static int set_head(int argc, const char **argv)
                if (delete_ref(buf.buf, NULL, REF_NODEREF))
                        result |= error("Could not delete %s", buf.buf);
        } else
-               usage_with_options(builtin_remote_usage, options);
+               usage_with_options(builtin_remote_sethead_usage, options);
 
        if (head_name) {
                unsigned char sha1[20];
@@ -1138,16 +1156,15 @@ 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, NULL, options, builtin_remote_usage,
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_prune_usage,
                             0);
 
        if (argc < 1)
-               usage_with_options(builtin_remote_usage, options);
+               usage_with_options(builtin_remote_prune_usage, options);
 
        for (; argc; argc--, argv++)
                result |= prune_remote(*argv, dry_run);
@@ -1182,95 +1199,62 @@ static int prune_remote(const char *remote, int dry_run)
 
                printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
                       abbrev_ref(refname, "refs/remotes/"));
-               warn_dangling_symref(dangling_msg, refname);
+               warn_dangling_symref(stdout, dangling_msg, refname);
        }
 
        free_remote_ref_states(&states);
        return result;
 }
 
-static int get_one_remote_for_update(struct remote *remote, void *priv)
+static int get_remote_default(const char *key, const char *value, void *priv)
 {
-       struct string_list *list = priv;
-       if (!remote->skip_default_update)
-               string_list_append(remote->name, list);
-       return 0;
-}
-
-static 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");
-               }
+       if (strcmp(key, "remotes.default") == 0) {
+               int *found = priv;
+               *found = 1;
        }
-
        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 };
+       int i, prune = 0;
        struct option options[] = {
-               OPT_GROUP("update specific 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_usage,
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_update_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);
-               }
-       }
+       fetch_argv[fetch_argc++] = "fetch";
 
-       if (!result && !list.nr  && argc == 2 && !strcmp(argv[1], "default"))
-               result = for_each_remote(get_one_remote_for_update, &list);
+       if (prune)
+               fetch_argv[fetch_argc++] = "--prune";
+       if (verbose)
+               fetch_argv[fetch_argc++] = "-v";
+       if (argc < 2) {
+               fetch_argv[fetch_argc++] = "default";
+       } else {
+               fetch_argv[fetch_argc++] = "--multiple";
+               for (i = 1; i < argc; i++)
+                       fetch_argv[fetch_argc++] = argv[i];
+       }
 
-       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);
+       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";
        }
 
-       /* all names were strdup()ed or strndup()ed */
-       list.strdup_strings = 1;
-       string_list_clear(&list, 0);
+       fetch_argv[fetch_argc] = NULL;
 
-       return result;
+       return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
 }
 
 static int get_one_entry(struct remote *remote, void *priv)
@@ -1334,7 +1318,7 @@ static int show_all(void)
 int cmd_remote(int argc, const char **argv, const char *prefix)
 {
        struct option options[] = {
-               OPT__VERBOSE(&verbose),
+               OPT_BOOLEAN('v', "verbose", &verbose, "be verbose; must be placed before a subcommand"),
                OPT_END()
        };
        int result;
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]);
+}
index adfb7b5f48597c19c23235b74cf1a0c779985dc6..2be9ffb77b38ae687537f3599c9d11e3fbc026a9 100644 (file)
@@ -48,6 +48,8 @@ static void garbage_collect(struct string_list *rr)
 
        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;
@@ -106,6 +108,9 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
        if (argc < 2)
                return rerere();
 
+       if (!strcmp(argv[1], "-h"))
+               usage(git_rerere_usage);
+
        fd = setup_rerere(&merge_rr);
        if (fd < 0)
                return 0;
index 5fa1789d0c2b90ef9ee83d091b67292f2ff0db26..11d1c6e4d6d2ec0580cae3725f6b3a093e68aa67 100644 (file)
@@ -108,7 +108,8 @@ static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
        if (read_cache() < 0)
                return error("Could not read index");
 
-       result = refresh_cache(flags) ? 1 : 0;
+       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");
@@ -142,6 +143,17 @@ static void update_index_from_diff(struct diff_queue_struct *q,
        }
 }
 
+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)
 {
@@ -183,6 +195,7 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)
 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];
@@ -198,6 +211,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                                "reset HEAD, index and working tree", MERGE),
                OPT_BOOLEAN('q', NULL, &quiet,
                                "disable showing new HEAD in hard reset and progress message"),
+               OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
                OPT_END()
        };
 
@@ -251,6 +265,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                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. */
@@ -261,13 +281,15 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                        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);
+                               quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
        }
        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");
+       if ((reset_type == HARD || reset_type == MERGE)
+           && !is_inside_work_tree())
+               die("%s reset requires a work tree",
+                   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
@@ -302,7 +324,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                break;
        case MIXED: /* Report what has not been updated. */
                update_index_refresh(0, NULL,
-                               quiet ? REFRESH_QUIET : REFRESH_SAY_CHANGED);
+                               quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
                break;
        }
 
index 4ba1c12e0b6fedb056aebed6ffc8f8bb8d6cfd18..91b604289dd59101cb6477fa49dd9c342e5b39e5 100644 (file)
@@ -96,9 +96,10 @@ static void show_commit(struct commit *commit, void *data)
 
        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);
+               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)
@@ -305,7 +306,6 @@ 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;
@@ -319,6 +319,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
 
        memset(&info, 0, sizeof(info));
        info.revs = &revs;
+       if (revs.bisect)
+               bisect_list = 1;
 
        quiet = DIFF_OPT_TST(&revs.diffopt, QUIET);
        for (i = 1 ; i < argc; i++) {
@@ -348,12 +350,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                        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);
 
        }
index 45bead65451e87a4b564e59fed3518a4de9198e9..37d02335212dea9672e2472970f66dc4ac95dfd1 100644 (file)
@@ -180,6 +180,12 @@ static int show_reference(const char *refname, const unsigned char *sha1, int fl
        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];
@@ -426,6 +432,13 @@ static void die_no_single_rev(int quiet)
                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;
@@ -438,6 +451,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
        if (argc > 1 && !strcmp("--sq-quote", argv[1]))
                return cmd_sq_quote(argc - 2, argv + 2);
 
+       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++) {
@@ -548,6 +564,11 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                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 (!strcmp(arg, "--branches")) {
                                for_each_branch_ref(show_reference, NULL);
                                continue;
index 47fb9f7baa9ad9c070e1a6d9c245300ba2402ce5..8fffdbf20058e9970af4b5e4a14349ecb4ff455c 100644 (file)
@@ -2,9 +2,11 @@
 #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"
 
 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"
@@ -38,12 +40,13 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
         */
        const char *argv[] = {
                "pack-objects",
-               "--all-progress",
+               "--all-progress-implied",
                "--revs",
                "--stdout",
                NULL,
                NULL,
                NULL,
+               NULL,
        };
        struct child_process po;
        int i;
@@ -53,10 +56,12 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
                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 = fd;
+       po.out = args->stateless_rpc ? -1 : fd;
        po.git_cmd = 1;
        if (start_command(&po))
                die_errno("git pack-objects failed");
@@ -80,6 +85,20 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
        }
 
        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;
@@ -243,7 +262,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count)
                break;
        case REF_STATUS_REJECT_NONFASTFORWARD:
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
-                               "non-fast forward");
+                               "non-fast-forward");
                break;
        case REF_STATUS_REMOTE_REJECT:
                print_ref_status('!', "[remote rejected]", ref,
@@ -300,6 +319,59 @@ static int refs_pushed(struct ref *ref)
        return 0;
 }
 
+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);
+}
+
 int send_pack(struct send_pack_args *args,
              int fd[], struct child_process *conn,
              struct ref *remote_refs,
@@ -307,6 +379,7 @@ int send_pack(struct send_pack_args *args,
 {
        int in = fd[0];
        int out = fd[1];
+       struct strbuf req_buf = STRBUF_INIT;
        struct ref *ref;
        int new_refs;
        int ask_for_status_report = 0;
@@ -388,14 +461,14 @@ int send_pack(struct send_pack_args *args,
                        char *new_hex = sha1_to_hex(ref->new_sha1);
 
                        if (ask_for_status_report) {
-                               packet_write(out, "%s %s %s%c%s",
+                               packet_buf_write(&req_buf, "%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",
+                               packet_buf_write(&req_buf, "%s %s %s",
                                        old_hex, new_hex, ref->name);
                }
                ref->status = expect_status_report ?
@@ -403,7 +476,17 @@ int send_pack(struct send_pack_args *args,
                        REF_STATUS_OK;
        }
 
-       packet_flush(out);
+       if (args->stateless_rpc) {
+               if (!args->dry_run) {
+                       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 (new_refs && !args->dry_run) {
                if (pack_objects(out, remote_refs, extra_have, args) < 0) {
                        for (ref = remote_refs; ref; ref = ref->next)
@@ -411,11 +494,15 @@ int send_pack(struct send_pack_args *args,
                        return -1;
                }
        }
+       if (args->stateless_rpc && !args->dry_run)
+               packet_flush(out);
 
        if (expect_status_report)
                ret = receive_status(in, remote_refs);
        else
                ret = 0;
+       if (args->stateless_rpc)
+               packet_flush(out);
 
        if (ret < 0)
                return ret;
@@ -475,6 +562,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
        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;
@@ -520,6 +608,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                                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) {
@@ -548,7 +644,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                }
        }
 
-       conn = git_connect(fd, dest, receivepack, args.verbose ? CONNECT_VERBOSE : 0);
+       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));
 
@@ -572,12 +675,16 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
 
        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);
 
-       print_push_status(dest, remote_refs);
+       if (!helper_status)
+               print_push_status(dest, remote_refs);
 
        if (!args.dry_run && remote) {
                struct ref *ref;
index 6a3812ee18a2ca1bc251f67582ebaee3675d9af5..b3b055f68ce59b6b91ef6949bd8c4bd0bed68b55 100644 (file)
@@ -56,7 +56,7 @@ static void insert_one_record(struct shortlog *log,
        /* 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]))
+       while (len > 0 && isspace(namebuf[len-1]))
                len--;
        namebuf[len] = 0;
 
@@ -139,8 +139,12 @@ static void read_from_stdin(struct shortlog *log)
 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};
 
-       buffer = commit->buffer;
+       pretty_print_commit(CMIT_FMT_RAW, commit, &buf, &ctx);
+       buffer = buf.buf;
        while (*buffer && *buffer != '\n') {
                const char *eol = strchr(buffer, '\n');
 
@@ -157,17 +161,19 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
                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)
+               struct pretty_print_context ctx = {0};
+               ctx.abbrev = DEFAULT_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)
index 01bea3b583b53f4930a1ab17d64b1b714f58099c..9f13caa76d3b147993b2cf39397d2f5761cfca22 100644 (file)
@@ -6,8 +6,8 @@
 #include "parse-options.h"
 
 static const char* show_branch_usage[] = {
-    "git show-branch [--sparse] [--current] [--all] [--remotes] [--topo-order] [--more=count | --list | --independent | --merge-base] [--topics] [--color] [<refs>...]",
-    "--reflog[=n[,b]] [--list] [--color] <branch>",
+    "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color | --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
 };
 
@@ -293,8 +293,8 @@ static void show_one_commit(struct commit *commit, int no_name)
        struct commit_name *name = commit->util;
 
        if (commit->object.parsed) {
-               pretty_print_commit(CMIT_FMT_ONELINE, commit,
-                                   &pretty, 0, NULL, NULL, 0, 0);
+               struct pretty_print_context ctx = {0};
+               pretty_print_commit(CMIT_FMT_ONELINE, commit, &pretty, &ctx);
                pretty_str = pretty.buf;
        }
        if (!prefixcmp(pretty_str, "[PATCH] "))
@@ -565,7 +565,15 @@ 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_arg is now passed to parse_options(), so we need to
+                * mimick 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);
                }
@@ -665,7 +673,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                OPT_BOOLEAN(0, "sha1-name", &sha1_name,
                            "name commits with their object names"),
                OPT_BOOLEAN(0, "merge-base", &merge_base,
-                           "act like git merge-base -a"),
+                           "show possible merge bases"),
                OPT_BOOLEAN(0, "independent", &independent,
                            "show refs unreachable from any other ref"),
                OPT_BOOLEAN(0, "topo-order", &lifo,
@@ -692,8 +700,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 
        /* 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] */
+               ac = default_num;
+               av = default_arg;
        }
 
        ac = parse_options(ac, av, prefix, builtin_show_branch_options,
index c46550c9c01da8fe74145e16656a5163a7e4f07b..17ada88dfb9543ba782b84507b451fabfec28e10 100644 (file)
@@ -7,7 +7,7 @@
 #include "parse-options.h"
 
 static const char * const show_ref_usage[] = {
-       "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [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",
        NULL
 };
@@ -183,7 +183,10 @@ static const struct option show_ref_options[] = {
        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"),
-       OPT_BOOLEAN('h', "head", &show_head, "show the HEAD reference"),
+       { 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",
@@ -201,6 +204,9 @@ static const struct option show_ref_options[] = {
 
 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);
 
index 1fd2205d530ad510a18ff5a441ca7b5cb1cc34a6..4d3b93fedb5f2eca68edca15ca1e10cb60176d36 100644 (file)
@@ -73,9 +73,11 @@ 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") ||
+       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");
index a51a6d1ea21412f151238e48984bc13110fcbc6c..c4790185ebb9979a6772cf7159d16c2a1deeeaf3 100644 (file)
@@ -390,7 +390,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                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_BOOLEAN('f', "force", &force, "replace the tag if exists"),
 
                OPT_GROUP("Tag listing options"),
                {
index 8b3a35e12da2cca8b632d4139bc2c5ba2409d322..3f1e7012db1af7d3695e6517956721a313229d65 100644 (file)
@@ -11,6 +11,9 @@ 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)
 {
        /*
@@ -81,6 +84,9 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
        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");
index 557148a693c058f51222cb3d996c309791d43d8b..685566e0b5e458c510fdf989744d63dda29e28f0 100644 (file)
@@ -181,10 +181,10 @@ static void write_cached_object(struct object *obj)
 static int check_object(struct object *obj, int type, void *data)
 {
        if (!obj)
-               return 0;
+               return 1;
 
        if (obj->flags & FLAG_WRITTEN)
-               return 1;
+               return 0;
 
        if (type != OBJ_ANY && obj->type != type)
                die("object type mismatch");
@@ -195,22 +195,24 @@ static int check_object(struct object *obj, int type, void *data)
                if (type != obj->type || type <= 0)
                        die("object of unexpected type");
                obj->flags |= FLAG_WRITTEN;
-               return 1;
+               return 0;
        }
 
        if (fsck_object(obj, 1, fsck_error_function))
                die("Error in object");
-       if (!fsck_walk(obj, check_object, NULL))
+       if (fsck_walk(obj, check_object, NULL))
                die("Error on reachable objects of %s", sha1_to_hex(obj->sha1));
        write_cached_object(obj);
-       return 1;
+       return 0;
 }
 
 static void write_rest(void)
 {
        unsigned i;
-       for (i = 0; i < nr_objects; i++)
-               check_object(obj_list[i].obj, OBJ_ANY, NULL);
+       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,
@@ -495,6 +497,8 @@ 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);
index 92beaaf4b3ca5520a9af27bde7f15dda46d80197..a6b7f2d6361fc1e8ea9713082affc72362f08d76 100644 (file)
@@ -27,6 +27,7 @@ static int mark_valid_only;
 #define MARK_VALID 1
 #define UNMARK_VALID 2
 
+__attribute__((format (printf, 1, 2)))
 static void report(const char *fmt, ...)
 {
        va_list vp;
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);
+}
index c4cd1e1327f18ae27b74fac6dfd41938cd9dfe5d..73f788ef247febabbabe9bc0d21a890afa198cff 100644 (file)
@@ -67,6 +67,7 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix)
        return write_archive(sent_argc, sent_argv, prefix, 0);
 }
 
+__attribute__((format (printf, 1, 2)))
 static void error_clnt(const char *fmt, ...)
 {
        char buf[1024];
@@ -132,7 +133,6 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
 
        while (1) {
                struct pollfd pfd[2];
-               ssize_t processed[2] = { 0, 0 };
                int status;
 
                pfd[0].fd = fd1[0];
@@ -147,15 +147,14 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
                        }
                        continue;
                }
-               if (pfd[0].revents & POLLIN)
-                       /* Data stream ready */
-                       processed[0] = process_input(pfd[0].fd, 1);
                if (pfd[1].revents & POLLIN)
                        /* Status stream ready */
-                       processed[1] = process_input(pfd[1].fd, 2);
-               /* Always finish to read data when available */
-               if (processed[0] || processed[1])
-                       continue;
+                       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);
index 0ee0a9af60b0601fe0e6db98ec582e059f5e9064..b6079ae6cb03c7f3112c6eebc8c9a012d690a125 100644 (file)
@@ -2,15 +2,23 @@
 #include "cache.h"
 #include "pack.h"
 #include "pack-revindex.h"
+#include "parse-options.h"
 
 #define MAX_CHAIN 50
 
-static void show_pack_info(struct packed_git *p)
+#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, chain_histogram[MAX_CHAIN+1];
+       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;
@@ -28,14 +36,19 @@ static void show_pack_info(struct packed_git *p)
                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);
+               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 {
-                       printf("%-6s %lu %lu %"PRIuMAX" %u %s\n",
-                              type, size, store_size, (uintmax_t)offset,
-                              delta_chain_length, sha1_to_hex(base_sha1));
+                       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
@@ -43,21 +56,29 @@ static void show_pack_info(struct packed_git *p)
                }
        }
 
-       for (i = 0; i <= MAX_CHAIN; i++) {
-               if (!chain_histogram[i])
+       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 = %"PRIu32": %"PRIu32" object%s\n", i,
-                      chain_histogram[i], chain_histogram[i] > 1 ? "s" : "");
+               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: %"PRIu32" object%s\n", MAX_CHAIN,
-                      chain_histogram[0], chain_histogram[0] > 1 ? "s" : "");
+               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, int verbose)
+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;
 
@@ -93,50 +114,53 @@ static int verify_one_pack(const char *path, int verbose)
                return error("packfile %s not found.", arg);
 
        install_packed_git(pack);
-       err = verify_pack(pack);
 
-       if (verbose) {
+       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);
-                       printf("%s: ok\n", pack->pack_name);
+                       show_pack_info(pack, flags);
+                       if (!stat_only)
+                               printf("%s: ok\n", pack->pack_name);
                }
        }
 
        return err;
 }
 
-static const char verify_pack_usage[] = "git verify-pack [-v] <pack>...";
+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;
-       int verbose = 0;
-       int no_more_options = 0;
-       int nothing_done = 1;
+       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);
-       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++;
+       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();
        }
 
-       if (nothing_done)
-               usage(verify_pack_usage);
-
        return err;
 }
index 7f7fda42f9b7ab272ff2b3fa4ad1984a79f11ce0..9f482c29f516bde84023f401b28b133c1e605333 100644 (file)
 #include "tag.h"
 #include "run-command.h"
 #include <signal.h>
+#include "parse-options.h"
 
-static const char builtin_verify_tag_usage[] =
-               "git verify-tag [-v|--verbose] <tag>...";
+static const char * const verify_tag_usage[] = {
+               "git verify-tag [-v|--verbose] <tag>...",
+               NULL
+};
 
 #define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
 
@@ -89,17 +92,17 @@ static int verify_tag(const char *name, int verbose)
 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);
 
-       if (argc > 1 &&
-           (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))) {
-               verbose = 1;
-               i++;
-       }
-
+       argc = parse_options(argc, argv, prefix, verify_tag_options,
+                            verify_tag_usage, PARSE_OPT_KEEP_ARGV0);
        if (argc <= i)
-               usage(builtin_verify_tag_usage);
+               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: */
index 3a24ce8157be7c4209d831027693653b8d65ca13..b223af416fee5fc219fbcca7afa2b9e03feaa7d0 100644 (file)
@@ -7,9 +7,12 @@
 #include "cache.h"
 #include "tree.h"
 #include "cache-tree.h"
+#include "parse-options.h"
 
-static const char write_tree_usage[] =
-"git write-tree [--missing-ok] [--prefix=<prefix>/]";
+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)
 {
@@ -17,27 +20,22 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
        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);
-       while (1 < argc) {
-               const char *arg = argv[1];
-               if (!strcmp(arg, "--missing-ok"))
-                       flags |= WRITE_TREE_MISSING_OK;
-               else if (!prefixcmp(arg, "--prefix="))
-                       prefix = arg + 9;
-               else if (!prefixcmp(arg, "--ignore-cache-tree"))
-                       /*
-                        * This is only useful for debugging, so I
-                        * do not bother documenting it.
-                        */
-                       flags |= WRITE_TREE_IGNORE_CACHE_TREE;
-               else
-                       usage(write_tree_usage);
-               argc--; argv++;
-       }
-
-       if (argc > 2)
-               die("too many options");
+       argc = parse_options(argc, argv, unused_prefix, write_tree_options,
+                            write_tree_usage, 0);
 
        ret = write_cache_as_tree(sha1, flags, prefix);
        switch (ret) {
index 20427d2963fa25c1177bd05ab9f8be2890b9f7ba..c3f83c093f6d18dc21ff4112513abbc84fb7858c 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -13,7 +13,6 @@ 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,
@@ -49,7 +48,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);
@@ -103,6 +101,7 @@ extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix);
 extern int cmd_update_index(int argc, const char **argv, const char *prefix);
 extern int cmd_update_ref(int argc, const char **argv, const char *prefix);
+extern int cmd_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_verify_tag(int argc, const char **argv, const char *prefix);
@@ -112,5 +111,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
index e4b2aa9c4a2a7fb52121828343aa27ab47d17279..ff97adcb891caf98dcc655e71e29eaec5dae0135 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -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;
diff --git a/cache.h b/cache.h
index e6c7f3307d09e592aa5bcc63fe6d372aa54a5da3..bf468e52352c193b355222b718d9f5125c26052c 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
@@ -330,7 +331,7 @@ 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))
@@ -368,9 +369,12 @@ static inline enum object_type object_type(unsigned int mode)
 #define CONFIG_ENVIRONMENT "GIT_CONFIG"
 #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"
 
 extern int is_bare_repository_cfg;
 extern int is_bare_repository(void);
@@ -395,6 +399,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);
 
@@ -468,13 +473,16 @@ extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_obje
 extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
 extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 
+/* "careful lstat()" */
+extern int check_path(const char *path, int len, struct stat *st, int skiplen);
+
 #define REFRESH_REALLY         0x0001  /* ignore_valid */
 #define REFRESH_UNMERGED       0x0002  /* allow unmerged */
 #define REFRESH_QUIET          0x0004  /* be quiet about it */
 #define REFRESH_IGNORE_MISSING 0x0008  /* ignore non-existent */
 #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 +493,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,6 +518,7 @@ 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;
@@ -516,6 +526,7 @@ 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;
 
@@ -560,6 +571,8 @@ enum object_creation_mode {
 
 extern enum object_creation_mode object_creation_mode;
 
+extern char *notes_ref_name;
+
 extern int grafts_replace_parents;
 
 #define GIT_REPO_VERSION 0
@@ -637,6 +650,7 @@ 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)
 {
@@ -649,10 +663,15 @@ 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);
 
 /* 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 pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
@@ -722,9 +741,14 @@ 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);
 void datestamp(char *buf, int bufsize);
 unsigned long approxidate(const char *);
+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
@@ -734,6 +758,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(void);
 
 struct checkout {
        const char *base_dir;
@@ -840,7 +866,6 @@ extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
 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];
@@ -886,6 +911,7 @@ 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_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 *);
@@ -909,13 +935,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;
@@ -938,7 +970,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 */
@@ -964,10 +998,12 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i
  * 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_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 *);
diff --git a/color.h b/color.h
index 18abeb7c7dd47794d4f5fea4049b5719f03383fe..3cb4b7fc890880b0fcf19a11c6bc7de6b10d6e8d 100644 (file)
--- a/color.h
+++ b/color.h
@@ -4,6 +4,11 @@
 /* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
 #define COLOR_MAXLEN 24
 
+/*
+ * 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"
@@ -29,7 +34,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);
 
index 5b63af1eeb71907f23117bc552fa7a4019f09722..61626912e3bca2571b41fd1256067470dc170cc1 100644 (file)
@@ -524,6 +524,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int 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);
@@ -588,7 +589,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]);
                }
index fb03a2ebb5d51f46d00fad3b3f6b1794d4fdad2b..cc5d48b3851cda643b3790ff326c04fdf1333d1c 100644 (file)
@@ -74,6 +74,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 +93,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 e2bcbe814936989e7a86018e46ed6ca86f4c1f10..632061c2c3669991b6526a5b6b452e3b54ee718c 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;
 
@@ -132,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) {
@@ -199,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++)
@@ -208,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;
@@ -564,13 +563,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 +597,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;
 }
index ba9f63813eba004ae409eba8741266a074161239..e5332efcfc9449e0f3af4d6c49be63adfeb138b1 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -63,19 +63,28 @@ enum cmit_fmt {
        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;
+       struct reflog_walk_info *reflog_info;
+};
+
 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 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 +131,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 +148,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)
 {
diff --git a/compat/bswap.h b/compat/bswap.h
new file mode 100644 (file)
index 0000000..f3b8c44
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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));
+}
+
+#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 bed417875e1344690977fd6aa41b955f127b74b4..0d73f15fa894cb0ded20f0cdd1f7fec3da1643f1 100644 (file)
@@ -3,7 +3,7 @@
 #include <conio.h>
 #include "../strbuf.h"
 
-unsigned int _CRT_fmode = _O_BINARY;
+#include <shellapi.h>
 
 static int err_win_to_posix(DWORD winerr)
 {
@@ -123,13 +123,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))
@@ -580,10 +584,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);
@@ -824,7 +829,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;
@@ -861,7 +866,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);
@@ -886,19 +891,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;
@@ -983,6 +1176,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];
@@ -1012,7 +1217,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)
@@ -1108,9 +1313,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;
 }
@@ -1139,7 +1344,7 @@ void mingw_open_html(const char *unixpath)
 
 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(
@@ -1197,8 +1402,9 @@ struct dirent *mingw_readdir(DIR *dir)
 
        if (dir->dd_handle == (long)INVALID_HANDLE_VALUE && dir->dd_stat == 0)
        {
+               DWORD lasterr;
                handle = FindFirstFileA(dir->dd_name, &buf);
-               DWORD lasterr = GetLastError();
+               lasterr = GetLastError();
                dir->dd_handle = (long)handle;
                if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) {
                        errno = err_win_to_posix(lasterr);
index c1859c5480f67dbe9640975d68407f2c1efd15f5..b3d299f5bc16810867b7768725b8d33ee999f6b5 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
@@ -40,6 +42,7 @@ struct passwd {
 
 extern char *getpass(const char *prompt);
 
+#ifndef POLLIN
 struct pollfd {
        int fd;           /* file descriptor */
        short events;     /* requested events */
@@ -47,6 +50,7 @@ struct pollfd {
 };
 #define POLLIN 1
 #define POLLHUP 2
+#endif
 
 typedef void (__cdecl *sig_handler_t)(int);
 struct sigaction {
@@ -121,6 +125,27 @@ static inline int waitpid(pid_t pid, int *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
  */
@@ -154,6 +179,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
 
@@ -163,7 +200,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
@@ -221,18 +258,22 @@ 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); \
 } \
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..9c753a5
--- /dev/null
@@ -0,0 +1,50 @@
+#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
+#undef stat
+#undef _stati64
+#include "compat/mingw.h"
+#undef stat
+#define stat _stati64
+#define _stat64(x,y) mingw_lstat(x,y)
+
+/*
+   Even though _stati64 is normally just defined at _stat64
+   on Windows, we specify it here as a proper struct to avoid
+   compiler warnings about macro redefinition due to magic in
+   mingw.h. Struct taken from ReactOS (GNU GPL license).
+*/
+struct _stati64 {
+       _dev_t  st_dev;
+       _ino_t  st_ino;
+       unsigned short st_mode;
+       short   st_nlink;
+       short   st_uid;
+       short   st_gid;
+       _dev_t  st_rdev;
+       __int64 st_size;
+       time_t  st_atime;
+       time_t  st_mtime;
+       time_t  st_ctime;
+};
+#endif
index 5ea007567d15c229685f571628d4afcce6783e8d..67d5c370a04c0630089e8601eafde5c05957959c 100644 (file)
@@ -4852,11 +4852,8 @@ regexec (preg, string, nmatch, pmatch, eflags)
    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;
+regerror(int errcode, const regex_t *preg,
+        char *errbuf, size_t errbuf_size)
 {
   const char *msg;
   size_t msg_size;
index 6c0fb056a571b30627e404d7f164de31ed1a8699..e1e0e7543d9414726122c121b7909bf73809a81a 100644 (file)
@@ -2,11 +2,14 @@
 
 /*
  * 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(__MINGW32__) && defined(__GNUC__) && __GNUC__ < 4
+#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4)
 #define SNPRINTF_SIZE_CORR 1
 #else
 #define SNPRINTF_SIZE_CORR 0
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/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..8a2112f
--- /dev/null
@@ -0,0 +1,51 @@
+#!/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");
+               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
index 779d796cd5f017d4effbede360185a4690cf414e..1c5a14922f255af2c3b0e75e06925b748d3d7684 100644 (file)
@@ -1,17 +1,5 @@
 #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;
index 9217c24b43f7546815079d540304aa71821df926..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"
 
 /*
index 738b24419dcd0deb4bb0700f25f8984b8862d14f..37385ce9d338ecbd2556cef9455000784ebab7e5 100644 (file)
--- a/config.c
+++ b/config.c
@@ -62,7 +62,8 @@ static char *parse_value(void)
                if (comment)
                        continue;
                if (isspace(c) && !quote) {
-                       space = 1;
+                       if (len)
+                               space++;
                        continue;
                }
                if (!quote) {
@@ -71,11 +72,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) {
@@ -353,6 +351,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 */
@@ -469,6 +477,11 @@ static int git_default_core_config(const char *var, const char *value)
                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);
 
@@ -476,7 +489,7 @@ static int git_default_core_config(const char *var, const char *value)
                return git_config_string(&editor_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)
@@ -629,6 +642,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;
@@ -1118,7 +1134,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];
index 3f1922d0bf66abdbcad03dfd9981ca5293a4b860..78345ebb60240e6642f319467defe3e07d4aa263 100644 (file)
@@ -68,6 +68,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)
@@ -156,19 +176,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 +246,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 +276,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)
+#
 # Define ZLIB_PATH to provide path to zlib.
 GIT_ARG_SET_PATH(zlib)
 #
index 76e542776abff8d07e7250ecfc8c7ae813302b2d..db965c9982f1f2ae7e08331d3fa1d40e194a6520 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -107,27 +107,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;
@@ -513,7 +492,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
        signal(SIGCHLD, SIG_DFL);
 
        host = strstr(url, "://");
-       if(host) {
+       if (host) {
                *host = '\0';
                protocol = get_protocol(url);
                host += 3;
@@ -630,6 +609,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
                        GIT_WORK_TREE_ENVIRONMENT,
                        GRAFT_ENVIRONMENT,
                        INDEX_ENVIRONMENT,
+                       NO_REPLACE_OBJECTS_ENVIRONMENT,
                        NULL
                };
                conn->env = env;
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..d506717
--- /dev/null
@@ -0,0 +1,356 @@
+#!/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 =~ / -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";
+        }
+    }
+
+#    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");
+            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();
+}
index 745b5fb78be02086d7d94aaaeaa40930150430f9..fbfa5f25c1b2c75943464757d571d3fa89e209aa 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
@@ -318,22 +312,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/\.$//
@@ -343,8 +329,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 ()
 {
@@ -422,7 +417,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
@@ -478,29 +483,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;;
@@ -508,17 +510,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;;
@@ -599,17 +602,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
@@ -628,8 +636,8 @@ __git_aliased_command ()
        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
 
@@ -674,8 +682,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
@@ -695,6 +704,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
@@ -746,7 +756,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
@@ -816,7 +826,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= --patch
+                       "
+               ;;
+       *)
+               __gitcomp "$(__git_refs)"
+               ;;
+       esac
 }
 
 _git_cherry ()
@@ -882,10 +906,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
@@ -918,6 +963,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 ()
@@ -938,11 +985,13 @@ _git_diff ()
 }
 
 __git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff
-                       tkdiff vimdiff gvimdiff xxdiff araxis
+                       tkdiff vimdiff gvimdiff xxdiff araxis p4merge
 "
 
 _git_difftool ()
 {
+       __git_has_doubledash && return
+
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
        --tool=*)
@@ -950,16 +999,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 ()
@@ -1047,13 +1100,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 ()
@@ -1065,7 +1120,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
@@ -1170,6 +1226,10 @@ _git_log ()
                __gitcomp "$__git_log_date_formats" "" "${cur##--date=}"
                return
                ;;
+       --decorate=*)
+               __gitcomp "long short" "" "${cur##--decorate=}"
+               return
+               ;;
        --*)
                __gitcomp "
                        $__git_log_common_options
@@ -1182,7 +1242,7 @@ _git_log ()
                        --pretty= --format= --oneline
                        --cherry-pick
                        --graph
-                       --decorate
+                       --decorate --decorate=
                        --walk-reflogs
                        --parents --children
                        $merge
@@ -1197,7 +1257,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 ()
@@ -1302,8 +1362,18 @@ _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=
+                       "
+
                return
        esac
        __gitcomp "$(__git_refs)"
@@ -1407,7 +1477,8 @@ _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|\
@@ -1508,7 +1579,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.*.*)
@@ -1529,13 +1601,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
@@ -1753,7 +1826,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
@@ -1765,13 +1838,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"
                ;;
@@ -1781,6 +1850,11 @@ _git_remote ()
        esac
 }
 
+_git_replace ()
+{
+       __gitcomp "$(__git_refs)"
+}
+
 _git_reset ()
 {
        __git_has_doubledash && return
@@ -1788,7 +1862,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
@@ -1884,18 +1958,30 @@ _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,--*|pop,--*)
-                       __gitcomp "--index"
+                       __gitcomp "--index --quiet"
                        ;;
                show,--*|drop,--*|branch,--*)
                        COMPREPLY=()
@@ -1916,7 +2002,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
                --*)
@@ -1938,7 +2024,7 @@ _git_svn ()
                proplist show-ignore show-externals branch tag blame
                migrate
                "
-       local subcommand="$(__git_find_subcommand "$subcommands")"
+       local subcommand="$(__git_find_on_cmdline "$subcommands")"
        if [ -z "$subcommand" ]; then
                __gitcomp "$subcommands"
        else
@@ -2091,7 +2177,8 @@ _git ()
                        --help
                        "
                        ;;
-               *)     __gitcomp "$(__git_porcelain_commands) $(__git_aliases)" ;;
+               *)     __git_compute_porcelain_commands
+                      __gitcomp "$__git_porcelain_commands $(__git_aliases)" ;;
                esac
                return
        fi
@@ -2137,6 +2224,7 @@ _git ()
        push)        _git_push ;;
        rebase)      _git_rebase ;;
        remote)      _git_remote ;;
+       replace)     _git_replace ;;
        reset)       _git_reset ;;
        revert)      _git_revert ;;
        rm)          _git_rm ;;
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]
similarity index 98%
rename from builtin-fetch--tool.c
rename to contrib/examples/builtin-fetch--tool.c
index 3dbdf7a2887002f0d7a67a1db35bd3c72d7a9d30..cd10dbcbc90ee155e05cffb49e88defcf35c5a59 100644 (file)
@@ -97,21 +97,21 @@ static int update_local_ref(const char *name,
        strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV));
 
        if (in_merge_bases(current, &updated, 1)) {
-               fprintf(stderr, "* %s: fast forward to %s\n",
+               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);
+               return update_ref_env("fast-forward", name, sha1_new, sha1_old);
        }
        if (!force) {
                fprintf(stderr,
-                       "* %s: not updating to non-fast forward %s\n",
+                       "* %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",
+               "* %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);
index e9588eec33ba5b64d186ff048bb040c18c57e6bc..500635fe4bb5a8f60bc76502ef15cd97bd273f74 100755 (executable)
@@ -14,7 +14,7 @@ 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)
 s,strategy=          merge strategy to use
 m,message=           message to be used for the merge commit (if any)
 "
@@ -353,7 +353,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,11 +365,11 @@ 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
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 342529db309821f461e8f77d05bc5e01c76802ec..48059d0aa74fe5f4c14bd74f599d449335ef8519 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")
             system(editor + " " + fileName)
 
             response = "y"
@@ -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,84 @@ 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
+            last = contents.pop()
+            last = last[:-1]
+            contents.append(last)
+
+        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
@@ -967,50 +1039,35 @@ class P4Sync(Command):
                 filesForCommit.append(f)
                 if f['action'] not in ('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 +1080,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 +1107,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 +1657,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..5782d80
--- /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 preceeding 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 78e40d2a13e5c754b10a8e68641236bf58394e05..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 {
@@ -109,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
index 7b03204ed18500756ba55818f0808b52db68d048..2a6839d81ee970cb799859c7a833f8f8cb780864 100755 (executable)
@@ -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
index 2a66063e44e2ca8e1742b59e046b3702721086cb..58a35c82870c54f844fd1154a82237891655f38f 100755 (executable)
@@ -315,8 +315,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=""
@@ -411,7 +411,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
index 1816e977b7b13782003fc78a9de9b47cd3554a32..491e7141b4ea29b3cf754cbaf2656a0c3ca8c46c 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -267,7 +267,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);
 }
 
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 1b5ada6648da66d14dec6c399898916920582852..5783e2401108adb1fef6943ef80bd78dbc76ecad 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -77,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;
@@ -85,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;
@@ -101,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];
@@ -157,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;
        }
diff --git a/date.c b/date.c
index 409a17d46424083b62f21e7e278393cee7bfa080..5d05ef61cfb140f004702a5ed614afa755c50670 100644 (file)
--- a/date.c
+++ b/date.c
@@ -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)
@@ -425,13 +435,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 +541,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;
 }
@@ -583,6 +596,9 @@ int parse_date(const char *date, char *result, int maxlen)
        tm.tm_mon = -1;
        tm.tm_mday = -1;
        tm.tm_isdst = -1;
+       tm.tm_hour = -1;
+       tm.tm_min = -1;
+       tm.tm_sec = -1;
        offset = -1;
        tm_gmt = 0;
 
@@ -657,42 +673,59 @@ 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_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 +739,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 +753,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 +761,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 },
@@ -757,7 +790,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)
 {
        const struct typelen *tl;
        const struct special *s;
@@ -778,7 +811,7 @@ 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);
                        return end;
                }
        }
@@ -800,7 +833,7 @@ 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;
                        return end;
                }
@@ -818,13 +851,15 @@ 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);
                        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;
@@ -835,6 +870,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
        }
 
        if (match_string(date, "years") >= 4) {
+               update_tm(tm, now, 0); /* fill in date fields if needed */
                tm->tm_year -= *num;
                *num = 0;
                return end;
@@ -866,36 +902,82 @@ 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 number = 0;
        struct tm tm, now;
-       struct timeval tv;
        time_t time_sec;
-       char buffer[50];
-
-       if (parse_date(date, buffer, sizeof(buffer)) > 0)
-               return strtoul(buffer, NULL, 10);
 
-       gettimeofday(&tv, NULL);
-       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);
                        continue;
                }
                if (isalpha(c))
-                       date = approxidate_alpha(date-1, &tm, &number);
+                       date = approxidate_alpha(date-1, &tm, &now, &number);
        }
-       if (number > 0 && number < 32)
-               tm.tm_mday = number;
-       if (tm.tm_mon > now.tm_mon && tm.tm_year == now.tm_year)
-               tm.tm_year--;
-       return mktime(&tm);
+       pending_number(&tm, &number);
+       return update_tm(&tm, &now, 0);
+}
+
+unsigned long approxidate_relative(const char *date, const struct timeval *tv)
+{
+       char buffer[50];
+
+       if (parse_date(date, buffer, sizeof(buffer)) > 0)
+               return strtoul(buffer, NULL, 0);
+
+       return approxidate_str(date, tv);
+}
+
+unsigned long approxidate(const char *date)
+{
+       struct timeval tv;
+       char buffer[50];
+
+       if (parse_date(date, buffer, sizeof(buffer)) > 0)
+               return strtoul(buffer, NULL, 0);
+
+       gettimeofday(&tv, NULL);
+       return approxidate_str(date, &tv);
 }
index a4e28df714b4834e5efe42fa3abb647711913d71..464ac3ffc0a45e95637e2cecdf97b3d39e5c7933 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
index ad2a4cde74e0c2ec6367a7128aa0732f131f6bdf..adf1c5fdee2bc836f03eaa89160573973bb46a67 100644 (file)
@@ -162,7 +162,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                if (ce_uptodate(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);
@@ -308,22 +309,6 @@ static int show_modified(struct rev_info *revs,
        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 +322,8 @@ 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));
        /*
         * Backward compatibility wart - "diff-index -m" does
         * not mean "do not ignore merges", but "match_missing".
@@ -344,12 +331,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;
        }
 
@@ -397,7 +383,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
@@ -435,8 +421,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);
index 4ebc1dbd87d9b90ad2aaa5c7d4601761e2d1d724..aae8e7accc1ff955bd76c62b379b37f343f61cc4 100644 (file)
@@ -201,8 +201,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 cd35e0c2d7e29012e24cc8f5f49d9d351420b56e..08bbd3e9070996b38f4d34cedf7640d93aa5808d 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -13,6 +13,7 @@
 #include "utf8.h"
 #include "userdiff.h"
 #include "sigchain.h"
+#include "submodule.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -38,6 +39,7 @@ 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);
@@ -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)
@@ -118,6 +122,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]);
@@ -174,6 +180,212 @@ 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;
+       int *found_changesp;
+       FILE *file;
+};
+
+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(FILE *file, const char *set, const char *reset,
+                       int first, const char *line, int len)
+{
+       int has_trailing_newline, has_trailing_carriage_return;
+       int nofirst;
+
+       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(FILE *file, const char *set, const char *reset,
+                     const char *line, int len)
+{
+       emit_line_0(file, 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->file, 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->file, ws, reset, '+', line, len);
+       else {
+               /* Emit just the prefix, then the rest. */
+               emit_line_0(ecbdata->file, set, reset, '+', "", 0);
+               ws_check_emit(line, len, ecbdata->ws_rule,
+                             ecbdata->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;
+
+       /*
+        * 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->file, plain, reset, line, len);
+               return;
+       }
+       ep += 2; /* skip over @@ */
+
+       /* The hunk header in fraginfo color */
+       emit_line(ecbdata->file, frag, reset, line, ep - line);
+
+       /* blank before the func header */
+       for (cp = ep; ep - line < len; ep++)
+               if (*ep != ' ' && *ep != '\t')
+                       break;
+       if (ep != cp)
+               emit_line(ecbdata->file, plain, reset, cp, ep - cp);
+
+       if (ep < line + len)
+               emit_line(ecbdata->file, func, reset, ep, line + len - ep);
+}
+
 static struct diff_tempfile *claim_diff_tempfile(void) {
        int i;
        for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
@@ -201,29 +413,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,26 +428,36 @@ 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->file, 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->file, 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,
@@ -274,13 +473,12 @@ 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;
        size_t size_one, size_two;
+       struct emit_callback ecbdata;
 
        if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) {
                a_prefix = o->b_prefix;
@@ -321,6 +519,22 @@ static void emit_rewrite_diff(const char *name_a,
                size_two = two->size;
        }
 
+       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.file = o->file;
+       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,
@@ -332,24 +546,9 @@ static void emit_rewrite_diff(const char *name_a,
        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);
 }
 
 struct diff_words_buffer {
@@ -529,26 +728,18 @@ static void diff_words_show(struct diff_words_data *diff_words)
        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);
@@ -566,42 +757,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,10 +775,23 @@ 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);
@@ -650,31 +818,22 @@ 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);
                return;
        }
 
-       if (len < ecbdata->nparents) {
+       if (len < 1) {
                emit_line(ecbdata->file, reset, reset, line, len);
                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 +844,25 @@ 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);
+               diff_words_flush(ecbdata);
                line++;
                len--;
                emit_line(ecbdata->file, plain, reset, line, len);
                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->file, 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)
@@ -999,7 +1155,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
               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;
 
@@ -1211,7 +1367,6 @@ struct checkdiff_t {
        struct diff_options *o;
        unsigned ws_rule;
        unsigned status;
-       int trailing_blanks_start;
 };
 
 static int is_conflict_marker(const char *line, unsigned long len)
@@ -1255,10 +1410,6 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
        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)) {
                        data->status |= 1;
                        fprintf(data->o->file,
@@ -1278,14 +1429,12 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
                              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;
        }
 }
 
@@ -1453,6 +1602,17 @@ static void builtin_diff(const char *name_a,
        const char *a_prefix, *b_prefix;
        const char *textconv_one = NULL, *textconv_two = NULL;
 
+       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,
+                               del, add, reset);
+               return;
+       }
+
        if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
                textconv_one = get_textconv(one);
                textconv_two = get_textconv(two);
@@ -1562,6 +1722,8 @@ 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);
+               if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
+                       check_blank_at_eof(&mf1, &mf2, &ecbdata);
                ecbdata.file = o->file;
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = o->context;
@@ -1704,11 +1866,22 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
                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 */
+               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:
@@ -2640,6 +2813,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_OPT_CLR(options, ALLOW_TEXTCONV);
        else if (!strcmp(arg, "--ignore-submodules"))
                DIFF_OPT_SET(options, IGNORE_SUBMODULES);
+       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"))
@@ -2691,7 +2870,7 @@ static int parse_num(const char **cp_p)
        num = 0;
        scale = 1;
        dot = 0;
-       for(;;) {
+       for (;;) {
                ch = *cp;
                if ( !dot && ch == '.' ) {
                        scale = 1;
diff --git a/diff.h b/diff.h
index 6616877ee5d15a8101cbdea0271cc18f8837d2f1..15fcecdecd9b033700902de44138eafdb66932eb 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -66,6 +66,9 @@ 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_SUBMODULE_LOG       (1 << 23)
+
 #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)
@@ -127,6 +130,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) \
index d7097bb576cef8900e8e0218ba82e1cc7a87a567..3a7b60a037b2e3c869afe76a23b671cfd5311338 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;
@@ -204,12 +204,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);
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 63ac998bfaf64da807bec0ae0bd57c391fb651fe..d6fd3cacd6de4757994c61903dd07e0c4d74a9e9 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++;
        }
 
diff --git a/dir.c b/dir.c
index e05b850acf69867f2b931e7ca2f7430a9e7fc22d..d0999ba055367c31571b251fb34bb46ed6c7051d 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -861,12 +861,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] != '/')
diff --git a/dir.h b/dir.h
index a6314464f9e0d896f131e08434228d5b1130a4f5..320b6a2f38b9289f910141148b5dddb4cb80815f 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -88,7 +88,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..615f5754d66ba7ea611a4837b1ff802f9f9de598 100644 (file)
--- a/editor.c
+++ b/editor.c
@@ -2,24 +2,38 @@
 #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 = DEFAULT_EDITOR;
+
+       return editor;
+}
+
+int launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
+{
+       const char *editor = git_editor();
 
        if (!editor)
-               editor = "vi";
+               return error("Terminal is dumb, but EDITOR unset");
 
        if (strcmp(editor, ":")) {
                size_t len = strlen(editor);
@@ -28,7 +42,7 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
                const char *args[6];
                struct strbuf arg0 = STRBUF_INIT;
 
-               if (strcspn(editor, "$ \t'") != len) {
+               if (strcspn(editor, "|&;<>()$`\\\"' \t\n*?[#~=%") != len) {
                        /* there are specials */
                        strbuf_addf(&arg0, "%s \"$@\"", editor);
                        args[i++] = "sh";
diff --git a/entry.c b/entry.c
index d3e86c722a6663a65f69b73621f57aa6331a5e5b..06d24f14c6ba9401637aebfb11659ae747796c06 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -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".
+ */
+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,7 +205,7 @@ 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)) {
+       if (!check_path(path, len, &st, state->base_dir_len)) {
                unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
                if (!changed)
                        return 0;
index 8f5eaa7dd89cbedd5a89e11f0ea24b29701b33c9..5171d9f9a4a30054983638e9e19e61f422724373 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;
@@ -38,6 +39,7 @@ int pager_use_color = 1;
 const char *editor_program;
 const char *excludes_file;
 int auto_crlf = 0;     /* 1: both ways, -1: only when adding git objects */
+int read_replace_refs = 1;
 enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
 unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
 enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
@@ -47,6 +49,7 @@ enum push_default_type push_default = PUSH_DEFAULT_MATCHING;
 #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;
 
 /* Parallel index stat data preload? */
@@ -81,6 +84,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 7ef9865aa6da794ab52cfc50f21f9f41f861fc4f..dd3c99d60d0ccf2506b08632c35c5ffc2bbc3255 100644 (file)
@@ -22,8 +22,8 @@ Format of STDIN stream:
     ('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
+    '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;
@@ -1744,10 +1748,12 @@ 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 +1761,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);
@@ -2003,6 +2010,80 @@ static void file_change_cr(struct branch *b, int rename)
                leaf.tree);
 }
 
+static void note_change_n(struct branch *b)
+{
+       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];
+       uint16_t inline_data = 0;
+
+       /* <dataref> or 'inline' */
+       if (*p == ':') {
+               char *x;
+               oe = find_mark(strtoumax(p + 1, &x, 10));
+               hashcpy(sha1, oe->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->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) {
+               static struct strbuf buf = STRBUF_INIT;
+
+               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);
+       } else {
+               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);
+       }
+
+       tree_content_set(&b->branch_tree, sha1_to_hex(commit_sha1), sha1,
+               S_IFREG | 0644, NULL);
+}
+
 static void file_change_deleteall(struct branch *b)
 {
        release_tree_content_recursive(b->branch_tree.tree);
@@ -2172,6 +2253,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);
                else if (!strcmp("deleteall", command_buf.buf))
                        file_change_deleteall(b);
                else {
@@ -2402,6 +2485,9 @@ int main(int argc, const char **argv)
 
        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)
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.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 df9f231635d2bc53073375971c1383966e2a140b..cd43c3491260cb2aa51f0d19fd18ab66e4ad8217 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 {
@@ -841,6 +936,10 @@ sub coalesce_overlapping_hunks {
        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 &&
@@ -877,6 +976,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).
@@ -884,14 +984,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) {
@@ -918,11 +1017,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 {
@@ -988,12 +1084,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 and all the remaining hunks in the file
+d - do not $verb this hunk nor any of the remaining hunks in the file
 g - select a hunk to go to
 / - search for a hunk matching the given regex
 j - leave this hunk undecided, see next undecided hunk
@@ -1006,8 +1104,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;
 
@@ -1078,7 +1208,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;
        }
@@ -1086,6 +1216,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;
@@ -1138,8 +1275,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) {
@@ -1313,16 +1453,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 = (@{$head->{TEXT}}, @result);
+               my $apply_routine = $patch_mode_flavour{APPLY};
+               &$apply_routine(@patch);
                refresh();
        }
 
@@ -1363,11 +1496,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 d64d9975358a6b9a18596c45b13434463903a344..4838cdb9ede8765b37ab663aa59fb1b011a30b4c 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -15,7 +15,10 @@ 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
+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
@@ -191,13 +194,33 @@ check_patch_format () {
                        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...
+                       sed -n -e '/^$/q' -e '/^[       ]/d' -e p "$1" |
+                       sane_egrep -v '^[!-9;-~]+:' >/dev/null ||
+                       patch_format=mbox
+               fi
        } < "$1" || clean_abort
 }
 
 split_patches () {
        case "$patch_format" in
        mbox)
-               git mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" ||
+               case "$rebasing" in
+               '')
+                       keep_cr= ;;
+               ?*)
+                       keep_cr=--keep-cr ;;
+               esac
+               git mailsplit -d"$prec" -o"$dotest" -b $keep_cr -- "$@" > "$dotest/last" ||
                clean_abort
                ;;
        stgit-series)
@@ -254,7 +277,11 @@ split_patches () {
                msgnum=
                ;;
        *)
-               clean_abort "Patch format $patch_format is not supported."
+               if test -n "$parse_patch" ; then
+                       clean_abort "Patch format $patch_format is not supported."
+               else
+                       clean_abort "Patch format detection failed."
+               fi
                ;;
        esac
 }
@@ -262,7 +289,7 @@ split_patches () {
 prec=4
 dotest="$GIT_DIR/rebase-apply"
 sign= utf8=t keep= skip= interactive= resolved= rebasing= abort=
-resolvemsg= resume=
+resolvemsg= resume= scissors= no_inbody_headers=
 git_apply_opt=
 committer_date_is_author_date=
 ignore_date=
@@ -284,6 +311,10 @@ do
                utf8= ;;
        -k|--keep)
                keep=t ;;
+       -c|--scissors)
+               scissors=t ;;
+       --no-scissors)
+               scissors=f ;;
        -r|--resolved)
                resolved=t ;;
        --skip)
@@ -291,7 +322,7 @@ do
        --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."
                ;;
@@ -303,7 +334,7 @@ do
                git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;
        --patch-format)
                shift ; patch_format="$1" ;;
-       --reject)
+       --reject|--ignore-whitespace|--ignore-space-change)
                git_apply_opt="$git_apply_opt $1" ;;
        --committer-date-is-author-date)
                committer_date_is_author_date=t ;;
@@ -409,14 +440,15 @@ else
 
        split_patches "$@"
 
-       # -s, -u, -k, --whitespace, -3, -C, -q and -p flags are kept
-       # for the resuming session after a patch failure.
-       # -i can and must be given when resuming.
+       # -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 "$scissors" >"$dotest/scissors"
+       echo "$no_inbody_headers" >"$dotest/no_inbody_headers"
        echo "$GIT_QUIET" >"$dotest/quiet"
        echo 1 >"$dotest/next"
        if test -n "$rebasing"
@@ -458,6 +490,18 @@ if test "$(cat "$dotest/keep")" = t
 then
        keep=-k
 fi
+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
@@ -512,12 +556,12 @@ 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
 
@@ -533,11 +577,12 @@ do
                        git cat-file commit "$commit" |
                        sed -e '1,/^$/d' >"$dotest/msg-clean"
                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
@@ -612,7 +657,10 @@ do
                [eE]*) git_editor "$dotest/final-commit"
                       action=again ;;
                [vV]*) action=again
-                      LESS=-S ${PAGER:-less} "$dotest/patch" ;;
+                      : ${GIT_PAGER=$(git var GIT_PAGER)}
+                      : ${LESS=-FRSX}
+                      export LESS
+                      $GIT_PAGER "$dotest/patch" ;;
                *)     action=again ;;
                esac
            done
index 8969553658bb5f4f2191f370062af9af33989d88..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>
@@ -300,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() {
@@ -311,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 ;;
@@ -393,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
@@ -405,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 9f941e42b16d927f738b4911b41344dd2f3ed0da..5c596875c2e78a92031915948444971126d91a78 100644 (file)
 # endif
 #elif !defined(__APPLE__) && !defined(__FreeBSD__)  && !defined(__USLC__) && !defined(_M_UNIX) && !defined(sgi)
 #define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */
-#ifndef __sun__
 #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
 #endif
-#endif
 #define _ALL_SOURCE 1
 #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>
 #include <sys/stat.h>
 /* 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>
@@ -169,21 +176,26 @@ extern char *gitbasename(char *);
 
 #ifdef __GNUC__
 #define NORETURN __attribute__((__noreturn__))
+#define NORETURN_PTR __attribute__((__noreturn__))
 #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 die_errno(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2)));
+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);
index e43920296182f320dac31b5832a30844ffaef38f..a7d215c8aa479bc6b456ec5c3792e931598ac365 100755 (executable)
@@ -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 = "";
@@ -606,7 +655,7 @@ 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");
 }
 
@@ -641,7 +690,7 @@ unless ($opt_P) {
        $? == 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 $!;
index ab6cea3e538047c30a5d728c7a16ee3c935c070b..6dc45f5d45a44c9bbe31987c1a10e4554f037d6c 100755 (executable)
@@ -285,7 +285,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";
@@ -702,7 +702,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");
@@ -1289,7 +1289,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 +1331,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 +1351,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 +1368,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 +1821,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 +1830,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 +1861,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 +2078,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} ) )
         {
@@ -2935,7 +2935,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 +3021,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 +3033,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 +3075,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 +3149,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 +3451,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 37e044db40bb89470bb64a4108c86d5173e03b5a..cb9d2022cc93f1f371264098828a52e0d6bf1d7c 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,11 @@ do
                force=t
                continue
                ;;
+       --remap-to-ancestor)
+               shift
+               remap_to_ancestor=t
+               continue
+               ;;
        --prune-empty)
                shift
                prune_empty=t
@@ -182,6 +188,7 @@ do
                ;;
        --subdirectory-filter)
                filter_subdir="$OPTARG"
+               remap_to_ancestor=t
                ;;
        --original)
                orig_namespace=$(expr "$OPTARG/" : '\(.*[^/]\)/*$')/
@@ -257,15 +264,24 @@ 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
+test -z "$nonrevs" && dashdash=-- || dashdash=
+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"
@@ -345,19 +361,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
@@ -457,7 +473,7 @@ if [ "$filter_tag_name" ]; then
                                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 14b92ba786554f4e714c89191e5584a825964ab2..718277a651f1fbc8e100e7eb657d87699b1059cb 100755 (executable)
@@ -745,6 +745,8 @@ 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 font_descs {
        {fontui   font_ui   {mc "Main Font"}}
        {fontdiff font_diff {mc "Diff/Console Font"}}
@@ -1132,6 +1134,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
@@ -1698,10 +1701,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 +1718,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]
@@ -2010,6 +2026,19 @@ 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]
@@ -2023,6 +2052,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 .
 }
 
@@ -3054,7 +3088,7 @@ 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} \
@@ -3212,7 +3246,7 @@ proc popup_diff_menu {ctxm ctxmmg x y X Y} {
                        set l [mc "Stage Hunk For Commit"]
                        set t [mc "Stage Line For Commit"]
                }
-               if {$::is_3way_diff
+               if {$::is_3way_diff || $::is_submodule_diff
                        || $current_diff_path eq {}
                        || {__} eq $state
                        || {_O} eq $state
@@ -3249,6 +3283,14 @@ wm geometry . [lindex $gm 0]
 unset gm
 }
 
+# -- Load window state
+#
+catch {
+set gws $repo_config(gui.wmstate)
+wm state . $gws
+unset gws
+}
+
 # -- Key Bindings
 #
 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
index 1f3b08f9efff873631d73e128ee97c1b9dad1822..8525b79aaa8fbc2d76cd82694d5042398be968a0 100644 (file)
@@ -1245,6 +1245,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 +1280,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 a18ac8b4308d8263a0688058524282b72bafe77a..d4e0bed0b6b3ca0f8e45435df8a62fd447e47c86 100644 (file)
@@ -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..bd5d189ed1ec1a6520b7e9fefc4d5d6f36b9a007 100644 (file)
@@ -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 {}
 
@@ -295,6 +296,16 @@ proc start_show_diff {cont_info {add_opts {}}} {
                lappend cmd $path
        }
 
+       if {[string match {160000 *} [lindex $s 2]]
+        || [string match {160000 *} [lindex $s 3]]} {
+               set is_submodule_diff 1
+               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 +323,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 +385,23 @@ proc read_diff {fd cont_info} {
                                set tags {}
                        }
                        }
+               } elseif {$is_submodule_diff} {
+                       if {$line == ""} continue
+                       if {[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 {
index 4e02fc0d393ff9fe8d2983fec99a03db7f36273a..241642062e7d4b8ad267f10fc6123bab52dfc744 100644 (file)
@@ -208,13 +208,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 +250,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 +260,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
+               }
        }
 }
 
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 53b7d3634d842a43f8043e3f825d4232e17588aa..074582d979e87704969ebdd2380566537c6a9548 100644 (file)
@@ -90,6 +90,11 @@ msgstr ""
 msgid "Ready."
 msgstr ""
 
+#: git-gui.sh:1726
+#, tcl-format
+msgid "Displaying only %s of %s files."
+msgstr ""
+
 #: git-gui.sh:1819
 msgid "Unmodified"
 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 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 32f6496b0d36cff061f00a4ad70b6fa4d34c2646..b8e6456208d8cccb669eb125cfb2ac677443db82 100755 (executable)
@@ -41,7 +41,7 @@ resolve_full_httpd () {
        case "$httpd" in
        *apache2*|*lighttpd*)
                # 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
@@ -73,15 +73,39 @@ 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*)
+               #The mongoose server doesn'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 () {
@@ -278,8 +302,8 @@ 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' "$GIT_DIR/gitweb/gitweb.cgi" >/dev/null
        then
                # favor mod_perl if available
                cat >> "$conf" <<EOF
@@ -297,8 +321,22 @@ EOF
                # 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 | 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
 AddHandler cgi-script .cgi
 <Location /gitweb.cgi>
@@ -308,6 +346,31 @@ EOF
        fi
 }
 
+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           $fqgitdir/gitweb
+ports          $port
+index_files    gitweb.cgi
+#ssl_cert      $fqgitdir/gitweb/ssl_cert.pem
+error_log      $fqgitdir/gitweb/error.log
+access_log     $fqgitdir/gitweb/access.log
+
+#cgi setup
+cgi_env                PATH=/usr/local/bin:/usr/bin:/bin,GIT_DIR=$GIT_DIR,GIT_EXEC_PATH=$GIT_EXEC_PATH
+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
+}
+
+
 script='
 s#^(my|our) \$projectroot =.*#$1 \$projectroot = "'$(dirname "$fqgitdir")'";#;
 s#(my|our) \$gitbin =.*#$1 \$gitbin = "'$GIT_EXEC_PATH'";#;
@@ -331,8 +394,15 @@ gitweb_css () {
 EOFGITWEB
 }
 
+gitweb_js () {
+       cat > "$1" <<\EOFGITWEB
+@@GITWEB_JS@@
+EOFGITWEB
+}
+
 gitweb_cgi "$GIT_DIR/gitweb/gitweb.cgi"
 gitweb_css "$GIT_DIR/gitweb/gitweb.css"
+gitweb_js  "$GIT_DIR/gitweb/gitweb.js"
 
 case "$httpd" in
 *lighttpd*)
@@ -344,6 +414,9 @@ case "$httpd" in
 webrick)
        webrick_conf
        ;;
+*mongoose*)
+       mongoose_conf
+       ;;
 *)
        echo "Unknown httpd specified: $httpd"
        exit 1
index 1dadbb49666c6d796df76babbfd291a2de4357e4..825c52c2436d15edb12a2ce7850437e0e531e0e3 100755 (executable)
@@ -81,7 +81,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: $SHA1"
                git read-tree -u -m $head $SHA1 || exit
                MRC=$SHA1 MRT=$(git write-tree)
                continue
index 9c2c1b7202462370509a7bdb391982ae32564bd6..d067894bf45fd0a50513e196ea2a5e671d901681 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
index bfb01f7842e6192e0b3fbc0e3f2ab96269578289..5b6278572a48569a806a5a976cdd08eb7c117fb7 100644 (file)
@@ -46,7 +46,7 @@ check_unchanged () {
 valid_tool () {
        case "$1" in
        kdiff3 | tkdiff | xxdiff | meld | opendiff | \
-       emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis)
+       emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis | p4merge)
                ;; # happy
        tortoisemerge)
                if ! merge_mode; then
@@ -130,6 +130,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"
@@ -323,17 +336,16 @@ guess_merge_tool () {
                else
                        tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
                fi
-               tools="$tools gvimdiff diffuse ecmerge araxis"
+               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.
diff --git a/git-notes.sh b/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 4b78a0cd37ba70236a05f78fb5bed3f763348096..9e69ada413bd81948591a9e99287f97499573673 100755 (executable)
@@ -16,7 +16,8 @@ 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=
+strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
+log_arg= verbosity=
 curr_branch=$(git symbolic-ref -q HEAD)
 curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||")
 rebase=$(git config --bool branch.$curr_branch_short.rebase)
@@ -45,6 +46,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)
@@ -88,46 +91,93 @@ error_on_no_merge_candidates () {
                esac
        done
 
-       curr_branch=${curr_branch#refs/heads/}
+       if test true = "$rebase"
+       then
+               op_type=rebase
+               op_prep=against
+       else
+               op_type=merge
+               op_prep=with
+       fi
 
-       if [ -z "$curr_branch" ]; then
+       curr_branch=${curr_branch#refs/heads/}
+       upstream=$(git config "branch.$curr_branch.merge")
+       remote=$(git config "branch.$curr_branch.remote")
+
+       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}\"]"
+               echo "    remote = <nickname>"
+               echo "    merge = <remote-ref>"
+               test rebase = "$op_type" &&
+                       echo "    rebase = true"
                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 "    [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 &&
-       reflist="$(get_remote_merge_branch "$@" 2>/dev/null)" &&
-       oldremoteref="$(git rev-parse -q --verify \
-               "$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
@@ -142,7 +192,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" ||
@@ -161,14 +211,7 @@ 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"
@@ -193,5 +236,5 @@ 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 \
+exec git-merge $diffstat $no_commit $squash $no_ff $ff_only $log_arg $strategy_args \
        "$merge_name" HEAD $merge_head $verbosity
index 23ded48322bf795b06aa40e4684bc710c8232549..d52932878c05af1aeb714c0cf4d48dbbcddc19ee 100755 (executable)
@@ -106,8 +106,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
@@ -147,7 +147,7 @@ die_abort () {
 }
 
 has_action () {
-       grep '^[^#]' "$1" >/dev/null
+       sane_grep '^[^#]' "$1" >/dev/null
 }
 
 pick_one () {
@@ -168,7 +168,7 @@ pick_one () {
                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
+               output warn Fast-forward to $sha1
        else
                output git cherry-pick "$@"
        fi
@@ -248,9 +248,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" : ' \([^ ]*\)')
@@ -340,6 +340,14 @@ do_next () {
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $sha1... $rest"
                ;;
+       reword|r)
+               comment_for_reflog reword
+
+               mark_action_done
+               pick_one $sha1 ||
+                       die_with_patch $sha1 "Could not apply $sha1... $rest"
+               git commit --amend
+               ;;
        edit|e)
                comment_for_reflog edit
 
@@ -408,7 +416,12 @@ do_next () {
                ;;
        *)
                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
@@ -731,7 +744,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,7 +752,7 @@ 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
@@ -752,6 +765,7 @@ first and then run 'git rebase --continue' again."
 #
 # 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
 #
@@ -765,7 +779,7 @@ 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"
index 18bc6946cfa592c11cf7499b1c1ff9a6cb378b26..b121f4537ccb173d9f289734f1fe2e89b28d7562 100755 (executable)
@@ -333,6 +333,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
@@ -382,8 +385,10 @@ else
 fi
 
 # The tree must be really really clean.
-if ! git update-index --ignore-submodules --refresh; then
-       die "cannot rebase: you have unstaged changes"
+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 --)
 case "$diff" in
@@ -462,7 +467,7 @@ 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
@@ -491,7 +496,7 @@ 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
        say "Fast-forwarded $branch_name to $onto_name."
index fd95beadab6aa01fc3fb26c7d72ba182201f8502..630ceddf0356429f7ff71d280ee1056a2eb939c6 100755 (executable)
@@ -8,13 +8,33 @@ 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_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}
@@ -54,5 +74,5 @@ echo "  $url $branch"
 echo
 
 git shortlog ^$baserev $headrev
-git diff -M --stat --summary $merge_base $headrev
+git diff -M --stat --summary $patch $merge_base..$headrev
 exit $status
index d508f83349b57ac2e7148f090e2abbf0a398fce4..e05455f74c7e23c28cae41b68fa80df87c633ce9 100755 (executable)
@@ -71,7 +71,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:
@@ -162,7 +162,8 @@ 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 = Git::command_oneline('var', 'GIT_EDITOR');
+
 sub do_edit {
        if (defined($multiedit) && !$multiedit) {
                map {
@@ -186,9 +187,11 @@ my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts);
 my ($validate, $confirm);
 my (@suppress_cc);
 
+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
@@ -213,6 +216,19 @@ my %config_settings = (
     "from" => \$sender,
 );
 
+# 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 {
 
@@ -401,7 +417,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
@@ -450,7 +466,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);
@@ -836,7 +851,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";
@@ -862,7 +877,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);
@@ -922,7 +939,7 @@ X-Mailer: git-send-email $gitversion
                        $smtp ||= Net::SMTP->new((defined $smtp_server_port)
                                                 ? "$smtp_server:$smtp_server_port"
                                                 : $smtp_server);
-                       if ($smtp_encryption eq 'tls') {
+                       if ($smtp_encryption eq 'tls' && $smtp) {
                                require Net::SMTP::SSL;
                                $smtp->command('STARTTLS');
                                $smtp->response();
@@ -977,7 +994,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";
                }
@@ -1155,7 +1174,7 @@ foreach my $t (@files) {
 
        # set up for the next message
        if ($thread && $message_was_sent &&
-               ($chain_reply_to || !defined $reply_to || length($reply_to) == 0)) {
+               (chain_reply_to() || !defined $reply_to || length($reply_to) == 0)) {
                $reply_to = $message_id;
                if (length $references > 0) {
                        $references .= "\n $message_id";
index c41c2f7439724adc3dd13b92c86b25d254fc23b8..dfcb8078f508d6cc26d312d61b64e0185e90b74b 100755 (executable)
@@ -99,19 +99,20 @@ 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" '"$@"'
+}
+
+sane_grep () {
+       GREP_OPTIONS= LC_ALL=C grep "$@"
+}
+
+sane_egrep () {
+       GREP_OPTIONS= LC_ALL=C egrep "$@"
 }
 
 is_bare_repository () {
index 03e589f764ba0570559fdb23a9a11cc2a0f24044..f796c2fe24f0eaec3de45bc5bf14f7dd2e29f4bd 100755 (executable)
@@ -7,7 +7,7 @@ USAGE="list [<options>]
    or: $dashless drop [-q|--quiet] [<stash>]
    or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
    or: $dashless branch <branchname> [<stash>]
-   or: $dashless [save [--keep-index] [-q|--quiet] [<message>]]
+   or: $dashless [save [-k|--keep-index] [-q|--quiet] [<message>]]
    or: $dashless clear"
 
 SUBDIRECTORY_OK=Yes
@@ -21,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
@@ -68,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 add -u &&
+                       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
@@ -95,15 +128,31 @@ create_stash () {
 
 save_stash () {
        keep_index=
+       patch_mode=
        while test $# != 0
        do
                case "$1" in
-               --keep-index)
+               -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"
+                       usage
+                       ;;
                *)
                        break
                        ;;
@@ -131,11 +180,22 @@ save_stash () {
                die "Cannot save the current status"
        say Saved working directory and index state "$stash_msg"
 
-       git reset --hard ${GIT_QUIET:+-q}
-
-       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
 }
 
@@ -145,8 +205,7 @@ 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 () {
@@ -162,10 +221,6 @@ show_stash () {
 }
 
 apply_stash () {
-       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=
 
        while test $# != 0
@@ -184,18 +239,27 @@ apply_stash () {
                shift
        done
 
-       # current index state
-       c_tree=$(git write-tree) ||
-               die 'Cannot apply a stash in the middle of a merge'
+       if test $# = 0
+       then
+               have_stash || die 'Nothing to apply'
+       fi
 
        # 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:") ||
+       s=$(git rev-parse --quiet --verify --default $ref_stash "$@") &&
+       w_tree=$(git rev-parse --quiet --verify "$s:") &&
+       b_tree=$(git rev-parse --quiet --verify "$s^1:") &&
+       i_tree=$(git rev-parse --quiet --verify "$s^2:") ||
                die "$*: no valid stashed state found"
 
+       git update-index -q --refresh &&
+       git diff-files --quiet --ignore-submodules ||
+               die 'Cannot apply to a dirty working tree, please stage your changes'
+
+       # current index state
+       c_tree=$(git write-tree) ||
+               die 'Cannot apply a stash in the middle of a merge'
+
        unstashed_index_tree=
        if test -n "$unstash_index" && test "$b_tree" != "$i_tree" &&
                        test "$c_tree" != "$i_tree"
@@ -302,15 +366,22 @@ apply_to_branch () {
        drop_stash $stash
 }
 
+# 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)
@@ -353,12 +424,13 @@ branch)
        apply_to_branch "$@"
        ;;
 *)
-       if test $# -eq 0
-       then
+       case $# in
+       0)
                save_stash &&
                say '(To restore them type "git stash apply")'
-       else
+               ;;
+       *)
                usage
-       fi
+       esac
        ;;
 esac
index ebed711da41a7ac3c4caa7ae9b7f73e0c75f4fe7..77d223292c5b167a8a33b04b10b46426b68e0576 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] [--rebase|--merge]|summary [-n|--summary-limit <n>] [<commit>]] \
-[--] [<path>...]|[foreach <command>]|[sync [--] [<path>...]]"
+dashless=$(basename "$0" | sed -e 's/-/ /')
+USAGE="[--quiet] add [-b branch] [--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
@@ -16,8 +21,10 @@ command=
 branch=
 reference=
 cached=
+files=
 nofetch=
 update=
+prefix=
 
 # Resolve relative url by appending to parent's url
 resolve_relative_url ()
@@ -50,7 +57,7 @@ resolve_relative_url ()
 #
 module_list()
 {
-       git ls-files --error-unmatch --stage -- "$@" | grep '^160000 '
+       git ls-files --error-unmatch --stage -- "$@" | sane_grep '^160000 '
 }
 
 #
@@ -91,7 +98,7 @@ 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" &&
@@ -153,6 +160,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
@@ -237,13 +249,43 @@ 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
+
        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/"
+                               unset GIT_DIR
+                               cd "$path" &&
+                               eval "$@" &&
+                               if test -n "$recursive"
+                               then
+                                       cmd_foreach "--recursive" "$@"
+                               fi
+                       ) ||
                        die "Stopping at '$path'; script returned non-zero status."
                fi
        done
@@ -316,6 +358,7 @@ cmd_init()
 cmd_update()
 {
        # parse $args after "submodule ... update".
+       orig_args="$@"
        while test $# -ne 0
        do
                case "$1" in
@@ -348,6 +391,10 @@ cmd_update()
                        shift
                        update="merge"
                        ;;
+               --recursive)
+                       shift
+                       recursive=1
+                       ;;
                --)
                        shift
                        break
@@ -434,6 +481,12 @@ cmd_update()
                        die "Unable to $action '$sha1' in submodule path '$path'"
                        say "Submodule path '$path': $msg '$sha1'"
                fi
+
+               if test -n "$recursive"
+               then
+                       (unset GIT_DIR; cd "$path" && cmd_update $orig_args) ||
+                       die "Failed to recurse into submodule path '$path'"
+               fi
        done
 }
 
@@ -460,6 +513,7 @@ set_name_rev () {
 cmd_summary() {
        summary_limit=-1
        for_status=
+       diff_cmd=diff-index
 
        # parse $args after "submodule ... summary".
        while test $# -ne 0
@@ -468,6 +522,9 @@ cmd_summary() {
                --cached)
                        cached="$1"
                        ;;
+               --files)
+                       files="$1"
+                       ;;
                --for-status)
                        for_status="$1"
                        ;;
@@ -504,10 +561,18 @@ cmd_summary() {
                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 --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)
@@ -520,8 +585,8 @@ cmd_summary() {
 
        test -z "$modules" && return
 
-       git diff-index $cached --raw $head -- $modules |
-       egrep '^:([0-7]* )?160000' |
+       git $diff_cmd $cached --raw $head -- $modules |
+       sane_egrep '^:([0-7]* )?160000' |
        cut -c2- |
        while read mod_src mod_dst sha1_src sha1_dst status name
        do
@@ -643,6 +708,7 @@ cmd_summary() {
 cmd_status()
 {
        # parse $args after "submodule ... status".
+       orig_args="$@"
        while test $# -ne 0
        do
                case "$1" in
@@ -652,6 +718,9 @@ cmd_status()
                --cached)
                        cached=1
                        ;;
+               --recursive)
+                       recursive=1
+                       ;;
                --)
                        shift
                        break
@@ -671,22 +740,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"
                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)
                                set_name_rev "$path" "$sha1"
                        fi
-                       say "+$sha1 $path$revname"
+                       say "+$sha1 $displaypath$revname"
+               fi
+
+               if test -n "$recursive"
+               then
+                       (
+                               prefix="$displaypath/"
+                               unset GIT_DIR
+                               cd "$path" &&
+                               cmd_status $orig_args
+                       ) ||
+                       die "Failed to recurse into submodule path '$path'"
                fi
        done
 }
index d0758107249f3743795d0961d36906452ac7d00a..650c9e5f02ead07351629d6572e82c3a9ac7ef92 100755 (executable)
@@ -21,6 +21,15 @@ $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;
+               }
+       }
+}
+
 $Git::SVN::Log::TZ = $ENV{TZ};
 $ENV{TZ} = 'UTC';
 $| = 1; # unbuffer STDOUT
@@ -159,6 +168,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 } ],
@@ -265,7 +277,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');
 }
@@ -380,9 +392,11 @@ sub cmd_clone {
                $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);
-       command_oneline('config', 'svn.authorsfile', $_authors) if $_authors;
 }
 
 sub cmd_init {
@@ -416,6 +430,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) {
@@ -594,8 +609,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: ",
@@ -641,7 +663,8 @@ 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 $allglobs = $remote->{ $_tag ? 'tags' : 'branches' };
@@ -753,6 +776,7 @@ sub cmd_rebase {
                $_fetch_all ? $gs->fetch_all : $gs->fetch;
        }
        command_noisy(rebase_cmd(), $gs->refname);
+       $gs->mkemptydirs;
 }
 
 sub cmd_show_ignore {
@@ -764,6 +788,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";
@@ -801,6 +826,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.
@@ -812,6 +838,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;
@@ -907,7 +939,7 @@ sub cmd_multi_init {
        }
        do_git_init_db();
        if (defined $_trunk) {
-               my $trunk_ref = $_prefix . 'trunk';
+               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) {
@@ -928,6 +960,7 @@ sub cmd_multi_init {
 }
 
 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}) {
@@ -1154,6 +1187,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'));
@@ -1167,6 +1211,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 {
@@ -1209,6 +1254,7 @@ sub complete_url_ls_init {
        }
        command_oneline('config', $k, $gs->{url}) unless $orig_url;
        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 !~ /\*/;
@@ -1291,9 +1337,8 @@ 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 $!;
        {
@@ -1360,8 +1405,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) {
@@ -1591,6 +1635,7 @@ 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
 
 my ($_gc_nr, $_gc_period);
 
@@ -1641,23 +1686,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";
@@ -1698,7 +1743,11 @@ sub fetch_all {
        my $ra = Git::SVN::Ra->new($url);
        my $uuid = $ra->get_uuid;
        my $head = $ra->get_latest_revnum;
-       $ra->get_log("", $head, 0, 1, 0, 1, sub { $head = $_[1] });
+
+       # 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/*)
@@ -1735,33 +1784,35 @@ 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/};
                        $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);
+               } 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/};
                        my $rs = {
-                           t => $2,
-                           remote => $1,
-                           path => Git::SVN::GlobSpec->new($p),
-                           ref => Git::SVN::GlobSpec->new($g) };
+                           t => $t,
+                           remote => $remote,
+                           path => Git::SVN::GlobSpec->new($local_ref),
+                           ref => Git::SVN::GlobSpec->new($remote_ref) };
                        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->{$1}->{$2} }, $rs;
+                       push @{ $r->{$remote}->{$t} }, $rs;
                }
        }
 
@@ -1869,14 +1920,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);
@@ -1946,7 +1998,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#^\./?#);
@@ -1963,16 +2015,16 @@ 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->{url} = command_oneline('config', '--get',
@@ -1983,7 +2035,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:
@@ -2400,12 +2452,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;
 }
@@ -2600,7 +2646,8 @@ sub find_parent_branch {
        my $url = $self->ra->{url};
        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,
                                 $branch_from, $r, $self->{ref_id});
@@ -2621,11 +2668,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
@@ -2640,18 +2689,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;
@@ -2687,6 +2738,61 @@ 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 if -d $d;
+               if (-e _) {
+                       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;
@@ -2796,7 +2902,7 @@ sub other_gs {
                $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";
+               print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1;
                my ($u, $p, $repo_id) = ($new_url, '', $ref_id);
                if ($u =~ s#^\Q$url\E(/|$)##) {
                        $p = $u;
@@ -2810,6 +2916,7 @@ sub other_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"
@@ -2839,14 +2946,260 @@ sub check_author {
        $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->rewrite_root || $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)};
+       }
+       return (keys %commits);
+}
+
+BEGIN {
+       memoize 'lookup_svn_merge';
+       memoize 'check_cherry_pick';
+}
+
+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...
+
+       # 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->rewrite_root || $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 = command_oneline(
+                       "merge-base",
+                       @$parents, $merge_tip,
+                      );
+
+               # 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[$i]);
+                               }
+                       }
+               }
+       }
+       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;
@@ -3262,7 +3615,7 @@ 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;
@@ -3317,12 +3670,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",
@@ -3360,6 +3725,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};
 }
@@ -3641,11 +4012,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;
 }
 
@@ -4978,10 +5349,8 @@ 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') {
+       chomp(my $pager = command_oneline(qw(var GIT_PAGER)));
+       if ($pager eq 'cat') {
                $pager = undef;
        }
        $ENV{GIT_PAGER_IN_USE} = defined($pager);
@@ -5495,7 +5864,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}}->
@@ -5564,7 +5933,7 @@ sub new {
        my ($class, $glob) = @_;
        my $re = $glob;
        $re =~ s!/+$!!g; # no need for trailing slashes
-       $re =~ m!^([^*]*)(\*(?:/\*)*)([^*]*)$!;
+       $re =~ m!^([^*]*)(\*(?:/\*)*)(.*)$!;
        my $temp = $re;
        my ($left, $right) = ($1, $3);
        $re = $2;
index 4f5c740df5651853e575ba68366152992b0f043d..a578c3a73203fbf1bf4abfb024b1e83c45f2b2ce 100755 (executable)
@@ -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
diff --git a/git.c b/git.c
index 807d875ae06ce7bbf61bb846c5b4cb5a51855eba..11544cdb4037715b1e9edc14689896d99d6e3284 100644 (file)
--- a/git.c
+++ b/git.c
@@ -5,7 +5,10 @@
 #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"
+       "           [--help] COMMAND [ARGS]";
 
 const char git_more_info_string[] =
        "See 'git help COMMAND' for more information on a specific command.";
@@ -84,6 +87,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" );
@@ -224,21 +232,24 @@ 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();
-
-       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;
+       help = argc == 2 && !strcmp(argv[1], "-h");
+       if (!help) {
+               if (p->option & RUN_SETUP)
+                       prefix = setup_git_directory();
+
+               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;
+       }
        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");
@@ -299,7 +310,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 },
@@ -309,9 +319,6 @@ static void handle_internal_command(int argc, const char **argv)
                { "get-tar-commit-id", cmd_get_tar_commit_id },
                { "grep", cmd_grep, RUN_SETUP | USE_PAGER },
                { "help", cmd_help },
-#ifndef NO_CURL
-               { "http-fetch", cmd_http_fetch, RUN_SETUP },
-#endif
                { "init", cmd_init_db },
                { "init-db", cmd_init_db },
                { "log", cmd_log, RUN_SETUP | USE_PAGER },
@@ -339,6 +346,7 @@ 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 },
+               { "replace", cmd_replace, RUN_SETUP },
                { "repo-config", cmd_config },
                { "rerere", cmd_rerere, RUN_SETUP },
                { "reset", cmd_reset, RUN_SETUP },
@@ -358,6 +366,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "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 },
                { "verify-tag", cmd_verify_tag, RUN_SETUP },
                { "version", cmd_version },
@@ -416,13 +425,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;
 
index 4be0834f0bc1cebc2b341ef6f54c3c37f48eb832..ab224f7eae858912f1bc13b5dfba4181fe6cfdc6 100644 (file)
@@ -233,7 +233,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..c62dfd0
--- /dev/null
@@ -0,0 +1,35 @@
+#
+# 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
+       PYTHON_PATH = /usr/bin/python
+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/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/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 4604c831fe0c5ac9b855cdef6f71888e99dd9462..364c7a84cbcf923deb72c2a91b4ec5f5d75bf4c3 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)]} {
@@ -265,7 +267,7 @@ proc parseviewrevs {view revs} {
                }
                lappend badrev $line
            }
-       }                   
+       }
        error_popup "[mc "Error parsing revisions:"] $err"
        return {}
     }
@@ -288,7 +290,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
        }
@@ -987,6 +989,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
 
@@ -1677,6 +1691,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}
@@ -1689,7 +1704,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
            }
@@ -1768,6 +1783,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
 
@@ -1786,10 +1810,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"
@@ -1799,45 +1826,56 @@ 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 *Labelframe.font uifont startupFile
 }
 
 # Make a menu and submenus.
@@ -1894,6 +1932,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
@@ -1909,7 +1963,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
@@ -1961,8 +2015,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)]} {
@@ -1970,14 +2029,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
@@ -1997,19 +2059,28 @@ 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
@@ -2018,7 +2089,7 @@ proc makewindow {} {
        -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
 
@@ -2038,36 +2109,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
@@ -2076,14 +2154,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:"]]
@@ -2093,14 +2171,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 -font textfont -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
@@ -2112,38 +2190,41 @@ 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 -font textfont -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 0 -increment 1 -to 10000000 \
@@ -2153,7 +2234,7 @@ proc makewindow {} {
     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
@@ -2164,9 +2245,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
@@ -2206,14 +2286,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
@@ -2229,7 +2311,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 \
@@ -2264,6 +2346,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}
@@ -2483,7 +2576,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]}]
@@ -2518,12 +2616,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
@@ -2539,9 +2639,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]
@@ -2556,8 +2659,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]"
 
@@ -2575,10 +2683,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)}]
@@ -2599,16 +2712,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 {
@@ -2621,7 +2743,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
 }
@@ -2641,31 +2767,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-2009 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
@@ -2676,7 +2804,7 @@ 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 "
@@ -2720,7 +2848,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"
@@ -3164,6 +3292,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
 
@@ -3198,11 +3348,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
@@ -3224,22 +3373,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]
@@ -3366,6 +3501,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
@@ -3828,16 +3966,16 @@ proc editview {} {
 
 proc vieweditor {top n title} {
     global newviewname newviewopts viewfiles bgcolor
-    global known_view_options
+    global known_view_options NS
 
-    toplevel $top
+    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 "View 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 5}
     pack $top.name -in $top.nfr -side left -padx {0 25}
@@ -3856,13 +3994,13 @@ 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
-           frame $cframe
+           ${NS}::frame $cframe
            pack $cframe -in $top -fill x -pady 3 -padx [list 15 3]
            set cexpand [expr {$flags eq "*."}]
        } else {
@@ -3870,31 +4008,31 @@ proc vieweditor {top n title} {
        }
 
        if {$type eq "l"} {
-            label $cframe.l_$id -text $title
+            ${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"} {
-           checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
+           ${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
-           radiobutton $cframe.c_$id -text $title -variable newviewopts($n,$button_id) -value $sz
+           ${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"} {
-           message $top.l -aspect 1500 -text $title
+           ${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 -font uifont
            if {[info exists viewfiles($n)]} {
@@ -3909,10 +4047,10 @@ proc vieweditor {top n title} {
        }
     }
 
-    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]
@@ -6776,14 +6914,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
 
@@ -6928,10 +7065,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
@@ -6951,14 +7090,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
 }
 
@@ -6967,6 +7137,7 @@ proc goback {} {
     focus .
 
     if {$historyindex > 1} {
+       save_position
        incr historyindex -1
        godo [lindex $history [expr {$historyindex - 1}]]
        .tf.bar.rightbut conf -state normal
@@ -6981,6 +7152,7 @@ proc goforw {} {
     focus .
 
     if {$historyindex < [llength $history]} {
+       save_position
        set cmd [lindex $history $historyindex]
        incr historyindex
        godo $cmd
@@ -7213,7 +7385,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]
@@ -7332,7 +7504,11 @@ proc getblobdiffs {ids} {
     if {[package vcompare $git_version "1.6.1"] >= 0} {
        set textconv "--textconv"
     }
-    set cmd [diffcmd $ids "-p $textconv -C --cc --no-commit-id -U$diffcontext"]
+    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"
     }
@@ -7352,6 +7528,34 @@ proc getblobdiffs {ids} {
     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
 
@@ -7397,7 +7601,7 @@ proc getblobdiffline {bdf ids} {
     $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]} {
@@ -7470,6 +7674,21 @@ 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 {[string compare [$ctext get "end - 4c" end] "\n \n\n"]} {
+               $ctext insert end "\n";     # Add newline after commit message
+           }
+           set curdiffstart [$ctext index "end - 1c"]
+           lappend ctext_file_names ""
+           set fname [string range $line 10 [expr [string last " " $line] - 1]]
+           lappend ctext_file_lines $fname
+           makediffhdr $fname $ids
+           $ctext insert end "\n$line\n" filesep
+       } elseif {![string compare -length 3 "  >" $line]} {
+           $ctext insert end "$line\n" dresult
+       } elseif {![string compare -length 3 "  <" $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]
@@ -7547,9 +7766,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}]
@@ -7906,6 +8126,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]} {
@@ -7915,7 +8140,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
 }
@@ -8041,7 +8266,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
@@ -8072,6 +8297,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 {}
 }
@@ -8085,10 +8311,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
     }
 }
 
@@ -8265,33 +8491,61 @@ 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
+
+    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
+    filerun $fd [list getblobdiffline $fd $diffids]
+}
+
 proc diffvssel {dirn} {
     global rowmenuid selectedline
 
@@ -8303,7 +8557,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
 }
 
@@ -8331,7 +8585,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
@@ -8341,38 +8595,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
@@ -8423,30 +8677,30 @@ 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}::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
@@ -8529,34 +8783,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
@@ -8587,25 +8841,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
@@ -8750,34 +9004,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"
@@ -8833,6 +9084,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
     }
@@ -8925,7 +9179,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
@@ -8935,7 +9189,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 \
@@ -8946,19 +9200,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
@@ -9156,7 +9410,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]
@@ -10218,7 +10472,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
@@ -10235,6 +10489,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 {}
 }
@@ -10253,19 +10508,20 @@ 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 \
+    if {!$use_ttk} {$top.${font}but configure  -font optionfont}
+    ${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
@@ -10278,21 +10534,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}
@@ -10310,9 +10566,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
@@ -10348,7 +10604,7 @@ proc fontok {} {
     }
     set w $prefstop.$f
     $w conf -text $fontparam(family) -font $fontpref($f)
-       
+
     fontcan
 }
 
@@ -10363,6 +10619,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
 
@@ -10379,10 +10657,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
@@ -10391,106 +10670,131 @@ 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
+    if {!$use_ttk} {
+       foreach w {maxpctl maxwidthl showlocal autoselect tabstopl ntag
+           ldiff lattr extdifff.l extdifff.b bgbut fgbut
+           diffoldbut diffnewbut hunksepbut markbgbut selbgbut
+           want_ttk ttk_note} {
+           $top.$w configure -font optionfont
+       }
+    }
+
+    ${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"
 }
 
@@ -10524,6 +10828,20 @@ 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} {
+    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
 
@@ -10547,7 +10865,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)
     }
@@ -10561,6 +10879,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
@@ -10606,6 +10925,9 @@ proc prefsok {} {
          $limitdiffs != $oldprefs(limitdiffs)} {
        reselectline
     }
+    if {$hideremotes != $oldprefs(hideremotes)} {
+       rereadrefs
+    }
 }
 
 proc formatdate {d} {
@@ -10901,7 +11223,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
     }
@@ -10929,7 +11251,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]]
                    }
@@ -10954,8 +11276,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
 }
 
@@ -11011,6 +11333,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
@@ -11018,6 +11341,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"
@@ -11026,12 +11350,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}
@@ -11077,6 +11409,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...
@@ -11154,6 +11488,12 @@ 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 {}
diff --git a/gitk-git/po/ja.po b/gitk-git/po/ja.po
new file mode 100644 (file)
index 0000000..c0c92ad
--- /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 © 2005-2008 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr ""
+"\n"
+"Gitk - gitコミットビューア\n"
+"\n"
+"Copyright © 2005-2008 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 947b53f6b0d76bc169e4aee2e8a030963fdc131f..624eb2281e0bbd7c6dc55e0345bfd9acf6e46eb4 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-2009.
 # 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: 2009-08-13 13:38+0100\n"
+"PO-Revision-Date: 2009-08-13 13:40+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
 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:269
+msgid "Error parsing revisions:"
+msgstr "Fel vid tolkning av revisioner:"
+
+#: gitk:324
+msgid "Error executing --argscmd command:"
+msgstr "Fel vid körning av --argscmd-kommando:"
+
+#: gitk:337
 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:340
 msgid ""
 "No files selected: --merge specified but no unmerged files are within file "
 "limit."
@@ -34,261 +42,290 @@ msgstr ""
 "Inga filer valdes: --merge angavs men det finns inga filer inom "
 "filbegränsningen."
 
-#: gitk:365 gitk:503
+#: gitk:362 gitk:509
 msgid "Error executing git log:"
 msgstr "Fel vid körning av git log:"
 
-#: gitk:378
+#: gitk:380 gitk:525
 msgid "Reading"
 msgstr "Läser"
 
-#: gitk:438 gitk:3462
+#: gitk:440 gitk:4123
 msgid "Reading commits..."
 msgstr "Läser incheckningar..."
 
-#: gitk:441 gitk:1528 gitk:3465
+#: gitk:443 gitk:1561 gitk:4126
 msgid "No commits selected"
 msgstr "Inga incheckningar markerade"
 
-#: gitk:1399
+#: gitk:1437
 msgid "Can't parse git log output:"
 msgstr "Kan inte tolka utdata från git log:"
 
-#: gitk:1605
+#: gitk:1657
 msgid "No commit information available"
 msgstr "Ingen incheckningsinformation är tillgänglig"
 
-#: gitk:1709 gitk:1731 gitk:3259 gitk:7764 gitk:9293 gitk:9466
+#: gitk:1793 gitk:1817 gitk:3916 gitk:8786 gitk:10322 gitk:10498
 msgid "OK"
 msgstr "OK"
 
-#: gitk:1733 gitk:3260 gitk:7439 gitk:7510 gitk:7613 gitk:7660 gitk:7766
-#: gitk:9294 gitk:9467
+#: gitk:1819 gitk:3918 gitk:8383 gitk:8457 gitk:8567 gitk:8616 gitk:8788
+#: gitk:10323 gitk:10499
 msgid "Cancel"
 msgstr "Avbryt"
 
-#: gitk:1811
+#: gitk:1919
 msgid "Update"
 msgstr "Uppdatera"
 
-#: gitk:1812
+#: gitk:1920
 msgid "Reload"
 msgstr "Ladda om"
 
-#: gitk:1813
+#: gitk:1921
 msgid "Reread references"
 msgstr "Läs om referenser"
 
-#: gitk:1814
+#: gitk:1922
 msgid "List references"
 msgstr "Visa referenser"
 
-#: gitk:1815
+#: gitk:1924
+msgid "Start git gui"
+msgstr "Starta git gui"
+
+#: gitk:1926
 msgid "Quit"
 msgstr "Avsluta"
 
-#: gitk:1810
+#: gitk:1918
 msgid "File"
 msgstr "Arkiv"
 
-#: gitk:1818
+#: gitk:1930
 msgid "Preferences"
 msgstr "Inställningar"
 
-#: gitk:1817
+#: gitk:1929
 msgid "Edit"
 msgstr "Redigera"
 
-#: gitk:1821
+#: gitk:1934
 msgid "New view..."
 msgstr "Ny vy..."
 
-#: gitk:1822
+#: gitk:1935
 msgid "Edit view..."
 msgstr "Ändra vy..."
 
-#: gitk:1823
+#: gitk:1936
 msgid "Delete view"
 msgstr "Ta bort vy"
 
-#: gitk:1825
+#: gitk:1938
 msgid "All files"
 msgstr "Alla filer"
 
-#: gitk:1820 gitk:3196
+#: gitk:1933 gitk:3670
 msgid "View"
 msgstr "Visa"
 
-#: gitk:1828 gitk:2487
+#: gitk:1943 gitk:1953 gitk:2654
 msgid "About gitk"
 msgstr "Om gitk"
 
-#: gitk:1829
+#: gitk:1944 gitk:1958
 msgid "Key bindings"
 msgstr "Tangentbordsbindningar"
 
-#: gitk:1827
+#: gitk:1942 gitk:1957
 msgid "Help"
 msgstr "Hjälp"
 
-#: gitk:1887
+#: gitk:2018
 msgid "SHA1 ID: "
 msgstr "SHA1-id: "
 
-#: gitk:1918
+#: gitk:2049
 msgid "Row"
 msgstr "Rad"
 
-#: gitk:1949
+#: gitk:2080
 msgid "Find"
 msgstr "Sök"
 
-#: gitk:1950
+#: gitk:2081
 msgid "next"
 msgstr "nästa"
 
-#: gitk:1951
+#: gitk:2082
 msgid "prev"
 msgstr "föreg"
 
-#: gitk:1952
+#: gitk:2083
 msgid "commit"
 msgstr "incheckning"
 
-#: gitk:1955 gitk:1957 gitk:3617 gitk:3640 gitk:3664 gitk:5550 gitk:5621
+#: gitk:2086 gitk:2088 gitk:4284 gitk:4307 gitk:4331 gitk:6272 gitk:6344
+#: gitk:6428
 msgid "containing:"
 msgstr "som innehåller:"
 
-#: gitk:1958 gitk:2954 gitk:2959 gitk:3692
+#: gitk:2089 gitk:3162 gitk:3167 gitk:4359
 msgid "touching paths:"
 msgstr "som rör sökväg:"
 
-#: gitk:1959 gitk:3697
+#: gitk:2090 gitk:4364
 msgid "adding/removing string:"
 msgstr "som lägger/till tar bort sträng:"
 
-#: gitk:1968 gitk:1970
+#: gitk:2099 gitk:2101
 msgid "Exact"
 msgstr "Exakt"
 
-#: gitk:1970 gitk:3773 gitk:5518
+#: gitk:2101 gitk:4439 gitk:6240
 msgid "IgnCase"
 msgstr "IgnVersaler"
 
-#: gitk:1970 gitk:3666 gitk:3771 gitk:5514
+#: gitk:2101 gitk:4333 gitk:4437 gitk:6236
 msgid "Regexp"
 msgstr "Reg.uttr."
 
-#: gitk:1972 gitk:1973 gitk:3792 gitk:3822 gitk:3829 gitk:5641 gitk:5708
+#: gitk:2103 gitk:2104 gitk:4458 gitk:4488 gitk:4495 gitk:6364 gitk:6432
 msgid "All fields"
 msgstr "Alla fält"
 
-#: gitk:1973 gitk:3790 gitk:3822 gitk:5580
+#: gitk:2104 gitk:4456 gitk:4488 gitk:6303
 msgid "Headline"
 msgstr "Rubrik"
 
-#: gitk:1974 gitk:3790 gitk:5580 gitk:5708 gitk:6109
+#: gitk:2105 gitk:4456 gitk:6303 gitk:6432 gitk:6866
 msgid "Comments"
 msgstr "Kommentarer"
 
-#: gitk:1974 gitk:3790 gitk:3794 gitk:3829 gitk:5580 gitk:6045 gitk:7285
-#: gitk:7300
+#: gitk:2105 gitk:4456 gitk:4460 gitk:4495 gitk:6303 gitk:6801 gitk:8063
+#: gitk:8078
 msgid "Author"
 msgstr "Författare"
 
-#: gitk:1974 gitk:3790 gitk:5580 gitk:6047
+#: gitk:2105 gitk:4456 gitk:6303 gitk:6803
 msgid "Committer"
 msgstr "Incheckare"
 
-#: gitk:2003
+#: gitk:2134
 msgid "Search"
 msgstr "Sök"
 
-#: gitk:2010
+#: gitk:2141
 msgid "Diff"
 msgstr "Diff"
 
-#: gitk:2012
+#: gitk:2143
 msgid "Old version"
 msgstr "Gammal version"
 
-#: gitk:2014
+#: gitk:2145
 msgid "New version"
 msgstr "Ny version"
 
-#: gitk:2016
+#: gitk:2147
 msgid "Lines of context"
 msgstr "Rader sammanhang"
 
-#: gitk:2026
+#: gitk:2157
 msgid "Ignore space change"
 msgstr "Ignorera ändringar i blanksteg"
 
-#: gitk:2084
+#: gitk:2215
 msgid "Patch"
 msgstr "Patch"
 
-#: gitk:2086
+#: gitk:2217
 msgid "Tree"
 msgstr "Träd"
 
-#: gitk:2213 gitk:2226
+#: gitk:2361 gitk:2378
 msgid "Diff this -> selected"
 msgstr "Diff denna -> markerad"
 
-#: gitk:2214 gitk:2227
+#: gitk:2362 gitk:2379
 msgid "Diff selected -> this"
 msgstr "Diff markerad -> denna"
 
-#: gitk:2215 gitk:2228
+#: gitk:2363 gitk:2380
 msgid "Make patch"
 msgstr "Skapa patch"
 
-#: gitk:2216 gitk:7494
+#: gitk:2364 gitk:8441
 msgid "Create tag"
 msgstr "Skapa tagg"
 
-#: gitk:2217 gitk:7593
+#: gitk:2365 gitk:8547
 msgid "Write commit to file"
 msgstr "Skriv incheckning till fil"
 
-#: gitk:2218 gitk:7647
+#: gitk:2366 gitk:8604
 msgid "Create new branch"
 msgstr "Skapa ny gren"
 
-#: gitk:2219
+#: gitk:2367
 msgid "Cherry-pick this commit"
 msgstr "Plocka denna incheckning"
 
-#: gitk:2220
+#: gitk:2368
 msgid "Reset HEAD branch to here"
 msgstr "Återställ HEAD-grenen hit"
 
-#: gitk:2234
+#: gitk:2369
+msgid "Mark this commit"
+msgstr "Markera denna incheckning"
+
+#: gitk:2370
+msgid "Return to mark"
+msgstr "Återgå till markering"
+
+#: gitk:2371
+msgid "Find descendant of this and mark"
+msgstr "Hitta efterföljare till denna och markera"
+
+#: gitk:2372
+msgid "Compare with marked commit"
+msgstr "Jämför med markerad incheckning"
+
+#: gitk:2386
 msgid "Check out this branch"
 msgstr "Checka ut denna gren"
 
-#: gitk:2235
+#: gitk:2387
 msgid "Remove this branch"
 msgstr "Ta bort denna gren"
 
-#: gitk:2242
+#: gitk:2394
 msgid "Highlight this too"
 msgstr "Markera även detta"
 
-#: gitk:2243
+#: gitk:2395
 msgid "Highlight this only"
 msgstr "Markera bara detta"
 
-#: gitk:2244
+#: gitk:2396
 msgid "External diff"
 msgstr "Extern diff"
 
-#: gitk:2245
+#: gitk:2397
 msgid "Blame parent commit"
-msgstr ""
+msgstr "Klandra föräldraincheckning"
 
-#: gitk:2488
+#: gitk:2404
+msgid "Show origin of this line"
+msgstr "Visa ursprunget för den här raden"
+
+#: gitk:2405
+msgid "Run git gui blame on this line"
+msgstr "Kör git gui blame på den här raden"
+
+#: gitk:2656
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
@@ -304,427 +341,672 @@ msgstr ""
 "\n"
 "Använd och vidareförmedla enligt villkoren i GNU General Public License"
 
-#: gitk:2496 gitk:2557 gitk:7943
+#: gitk:2664 gitk:2726 gitk:8969
 msgid "Close"
 msgstr "Stäng"
 
-#: gitk:2515
+#: gitk:2683
 msgid "Gitk key bindings"
 msgstr "Tangentbordsbindningar för Gitk"
 
-#: gitk:2517
+#: gitk:2686
 msgid "Gitk key bindings:"
 msgstr "Tangentbordsbindningar för Gitk:"
 
-#: gitk:2519
+#: gitk:2688
 #, tcl-format
 msgid "<%s-Q>\t\tQuit"
 msgstr "<%s-Q>\t\tAvsluta"
 
-#: gitk:2520
+#: gitk:2689
 msgid "<Home>\t\tMove to first commit"
 msgstr "<Home>\t\tGå till första incheckning"
 
-#: gitk:2521
+#: gitk:2690
 msgid "<End>\t\tMove to last commit"
 msgstr "<End>\t\tGå till sista incheckning"
 
-#: gitk:2522
+#: gitk:2691
 msgid "<Up>, p, i\tMove up one commit"
 msgstr "<Upp>, p, i\tGå en incheckning upp"
 
-#: gitk:2523
+#: gitk:2692
 msgid "<Down>, n, k\tMove down one commit"
 msgstr "<Ned>, n, k\tGå en incheckning ned"
 
-#: gitk:2524
+#: gitk:2693
 msgid "<Left>, z, j\tGo back in history list"
 msgstr "<Vänster>, z, j\tGå bakåt i historiken"
 
-#: gitk:2525
+#: gitk:2694
 msgid "<Right>, x, l\tGo forward in history list"
 msgstr "<Höger>, x, l\tGå framåt i historiken"
 
-#: gitk:2526
+#: gitk:2695
 msgid "<PageUp>\tMove up one page in commit list"
 msgstr "<PageUp>\tGå upp en sida i incheckningslistan"
 
-#: gitk:2527
+#: gitk:2696
 msgid "<PageDown>\tMove down one page in commit list"
 msgstr "<PageDown>\tGå ned en sida i incheckningslistan"
 
-#: gitk:2528
+#: gitk:2697
 #, tcl-format
 msgid "<%s-Home>\tScroll to top of commit list"
 msgstr "<%s-Home>\tRulla till början av incheckningslistan"
 
-#: gitk:2529
+#: gitk:2698
 #, tcl-format
 msgid "<%s-End>\tScroll to bottom of commit list"
 msgstr "<%s-End>\tRulla till slutet av incheckningslistan"
 
-#: gitk:2530
+#: gitk:2699
 #, tcl-format
 msgid "<%s-Up>\tScroll commit list up one line"
 msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg"
 
-#: gitk:2531
+#: gitk:2700
 #, tcl-format
 msgid "<%s-Down>\tScroll commit list down one line"
 msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg"
 
-#: gitk:2532
+#: gitk:2701
 #, tcl-format
 msgid "<%s-PageUp>\tScroll commit list up one page"
 msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida"
 
-#: gitk:2533
+#: gitk:2702
 #, tcl-format
 msgid "<%s-PageDown>\tScroll commit list down one page"
 msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida"
 
-#: gitk:2534
+#: gitk:2703
 msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
 msgstr "<Skift-Upp>\tSök bakåt (uppåt, senare incheckningar)"
 
-#: gitk:2535
+#: gitk:2704
 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
 msgstr "<Skift-Ned>\tSök framåt (nedåt, tidigare incheckningar)"
 
-#: gitk:2536
+#: gitk:2705
 msgid "<Delete>, b\tScroll diff view up one page"
 msgstr "<Delete>, b\tRulla diffvisningen upp en sida"
 
-#: gitk:2537
+#: gitk:2706
 msgid "<Backspace>\tScroll diff view up one page"
 msgstr "<Baksteg>\tRulla diffvisningen upp en sida"
 
-#: gitk:2538
+#: gitk:2707
 msgid "<Space>\t\tScroll diff view down one page"
 msgstr "<Blanksteg>\tRulla diffvisningen ned en sida"
 
-#: gitk:2539
+#: gitk:2708
 msgid "u\t\tScroll diff view up 18 lines"
 msgstr "u\t\tRulla diffvisningen upp 18 rader"
 
-#: gitk:2540
+#: gitk:2709
 msgid "d\t\tScroll diff view down 18 lines"
 msgstr "d\t\tRulla diffvisningen ned 18 rader"
 
-#: gitk:2541
+#: gitk:2710
 #, tcl-format
 msgid "<%s-F>\t\tFind"
 msgstr "<%s-F>\t\tSök"
 
-#: gitk:2542
+#: gitk:2711
 #, 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:2712
 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:2713
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tFokusera sökrutan"
 
-#: gitk:2545
+#: gitk:2714
 msgid "?\t\tMove to previous find hit"
 msgstr "?\t\tGå till föregående sökträff"
 
-#: gitk:2546
+#: gitk:2715
 msgid "f\t\tScroll diff view to next file"
 msgstr "f\t\tRulla diffvisningen till nästa fil"
 
-#: gitk:2547
+#: gitk:2716
 #, 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:2717
 #, 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:2718
 #, tcl-format
 msgid "<%s-KP+>\tIncrease font size"
 msgstr "<%s-Num+>\tÖka teckenstorlek"
 
-#: gitk:2550
+#: gitk:2719
 #, tcl-format
 msgid "<%s-plus>\tIncrease font size"
 msgstr "<%s-plus>\tÖka teckenstorlek"
 
-#: gitk:2551
+#: gitk:2720
 #, tcl-format
 msgid "<%s-KP->\tDecrease font size"
 msgstr "<%s-Num->\tMinska teckenstorlek"
 
-#: gitk:2552
+#: gitk:2721
 #, tcl-format
 msgid "<%s-minus>\tDecrease font size"
 msgstr "<%s-minus>\tMinska teckenstorlek"
 
-#: gitk:2553
+#: gitk:2722
 msgid "<F5>\t\tUpdate"
 msgstr "<F5>\t\tUppdatera"
 
-#: gitk:3200
+#: gitk:3177
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Fel vid hämtning av  \"%s\" från %s:"
+
+#: gitk:3234 gitk:3243
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "Fel vid skapande av temporär katalog %s:"
+
+#: gitk:3255
+msgid "command failed:"
+msgstr "kommando misslyckades:"
+
+#: gitk:3401
+msgid "No such commit"
+msgstr "Incheckning saknas"
+
+#: gitk:3415
+msgid "git gui blame: command failed:"
+msgstr "git gui blame: kommando misslyckades:"
+
+#: gitk:3446
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "Kunde inte läsa sammanslagningshuvud: %s"
+
+#: gitk:3454
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "Fel vid läsning av index: %s"
+
+#: gitk:3479
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "Kunde inte starta git blame: %s"
+
+#: gitk:3482 gitk:6271
+msgid "Searching"
+msgstr "Söker"
+
+#: gitk:3514
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "Fel vid körning av git blame: %s"
+
+#: gitk:3542
+#, 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:3556
+msgid "External diff viewer failed:"
+msgstr "Externt diff-verktyg misslyckades:"
+
+#: gitk:3674
 msgid "Gitk view definition"
 msgstr "Definition av Gitk-vy"
 
-#: gitk:3225
-msgid "Name"
-msgstr "Namn"
-
-#: gitk:3228
+#: gitk:3678
 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:3679
+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:3680
+msgid "Branches & tags:"
+msgstr "Grenar & taggar:"
+
+#: gitk:3681
+msgid "All refs"
+msgstr "Alla referenser"
+
+#: gitk:3682
+msgid "All (local) branches"
+msgstr "Alla (lokala) grenar"
+
+#: gitk:3683
+msgid "All tags"
+msgstr "Alla taggar"
+
+#: gitk:3684
+msgid "All remote-tracking branches"
+msgstr "Alla fjärrspårande grenar"
+
+#: gitk:3685
+msgid "Commit Info (regular expressions):"
+msgstr "Incheckningsinfo (reguljära uttryck):"
 
-#: gitk:3246
+#: gitk:3686
+msgid "Author:"
+msgstr "Författare:"
+
+#: gitk:3687
+msgid "Committer:"
+msgstr "Incheckare:"
+
+#: gitk:3688
+msgid "Commit Message:"
+msgstr "Incheckningsmeddelande:"
+
+#: gitk:3689
+msgid "Matches all Commit Info criteria"
+msgstr "Motsvarar alla kriterier för incheckningsinfo"
+
+#: gitk:3690
+msgid "Changes to Files:"
+msgstr "Ändringar av filer:"
+
+#: gitk:3691
+msgid "Fixed String"
+msgstr "Fast sträng"
+
+#: gitk:3692
+msgid "Regular Expression"
+msgstr "Reguljärt uttryck"
+
+#: gitk:3693
+msgid "Search string:"
+msgstr "Söksträng:"
+
+#: gitk:3694
+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:3695
+msgid "Since:"
+msgstr "Från:"
+
+#: gitk:3696
+msgid "Until:"
+msgstr "Till:"
+
+#: gitk:3697
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Begränsa och/eller hoppa över ett antal revisioner (positivt heltal):"
+
+#: gitk:3698
+msgid "Number to show:"
+msgstr "Antal att visa:"
+
+#: gitk:3699
+msgid "Number to skip:"
+msgstr "Antal att hoppa över:"
+
+#: gitk:3700
+msgid "Miscellaneous options:"
+msgstr "Diverse alternativ:"
+
+#: gitk:3701
+msgid "Strictly sort by date"
+msgstr "Strikt datumsortering"
+
+#: gitk:3702
+msgid "Mark branch sides"
+msgstr "Markera sidogrenar"
+
+#: gitk:3703
+msgid "Limit to first parent"
+msgstr "Begränsa till första förälder"
+
+#: gitk:3704
+msgid "Simple history"
+msgstr "Enkel historik"
+
+#: gitk:3705
+msgid "Additional arguments to git log:"
+msgstr "Ytterligare argument till git log:"
+
+#: gitk:3706
 msgid "Enter files and directories to include, one per line:"
 msgstr "Ange filer och kataloger att ta med, en per rad:"
 
-#: gitk:3293
+#: gitk:3707
+msgid "Command to generate more commits to include:"
+msgstr "Kommando för att generera fler incheckningar att ta med:"
+
+#: gitk:3829
+msgid "Gitk: edit view"
+msgstr "Gitk: redigera vy"
+
+#: gitk:3837
+msgid "-- criteria for selecting revisions"
+msgstr " - kriterier för val av revisioner"
+
+#: gitk:3842
+msgid "View Name:"
+msgstr "Namn på vy:"
+
+#: gitk:3917
+msgid "Apply (F5)"
+msgstr "Använd (F5)"
+
+#: gitk:3955
 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:4008 gitk:4060 gitk:4508 gitk:4522 gitk:5783 gitk:11196 gitk:11197
 msgid "None"
 msgstr "Inget"
 
-#: gitk:3790 gitk:5580 gitk:7287 gitk:7302
+#: gitk:4456 gitk:6303 gitk:8065 gitk:8080
 msgid "Date"
 msgstr "Datum"
 
-#: gitk:3790 gitk:5580
+#: gitk:4456 gitk:6303
 msgid "CDate"
 msgstr "Skapat datum"
 
-#: gitk:3939 gitk:3944
+#: gitk:4605 gitk:4610
 msgid "Descendant"
 msgstr "Avkomling"
 
-#: gitk:3940
+#: gitk:4606
 msgid "Not descendant"
 msgstr "Inte avkomling"
 
-#: gitk:3947 gitk:3952
+#: gitk:4613 gitk:4618
 msgid "Ancestor"
 msgstr "Förfader"
 
-#: gitk:3948
+#: gitk:4614
 msgid "Not ancestor"
 msgstr "Inte förfader"
 
-#: gitk:4187
+#: gitk:4904
 msgid "Local changes checked in to index but not committed"
 msgstr "Lokala ändringar sparade i indexet men inte incheckade"
 
-#: gitk:4220
+#: gitk:4940
 msgid "Local uncommitted changes, not checked in to index"
 msgstr "Lokala ändringar, ej sparade i indexet"
 
-#: gitk:5549
-msgid "Searching"
-msgstr "Söker"
+#: gitk:6621
+msgid "many"
+msgstr "många"
 
-#: gitk:6049
+#: gitk:6805
 msgid "Tags:"
 msgstr "Taggar:"
 
-#: gitk:6066 gitk:6072 gitk:7280
+#: gitk:6822 gitk:6828 gitk:8058
 msgid "Parent"
 msgstr "Förälder"
 
-#: gitk:6077
+#: gitk:6833
 msgid "Child"
 msgstr "Barn"
 
-#: gitk:6086
+#: gitk:6842
 msgid "Branch"
 msgstr "Gren"
 
-#: gitk:6089
+#: gitk:6845
 msgid "Follows"
 msgstr "Följer"
 
-#: gitk:6092
+#: gitk:6848
 msgid "Precedes"
 msgstr "Föregår"
 
-#: gitk:6378
-msgid "Error getting merge diffs:"
-msgstr "Fel vid hämtning av sammanslagningsdiff:"
+#: gitk:7346
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "Fel vid hämtning av diff: %s"
 
-#: gitk:7113
+#: gitk:7886
 msgid "Goto:"
 msgstr "Gå till:"
 
-#: gitk:7115
+#: gitk:7888
 msgid "SHA1 ID:"
 msgstr "SHA1-id:"
 
-#: gitk:7134
+#: gitk:7907
 #, tcl-format
 msgid "Short SHA1 id %s is ambiguous"
 msgstr "Förkortat SHA1-id %s är tvetydigt"
 
-#: gitk:7146
+#: gitk:7914
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "Revisionen %s är inte känd"
+
+#: gitk:7924
 #, tcl-format
 msgid "SHA1 id %s is not known"
 msgstr "SHA-id:t %s är inte känt"
 
-#: gitk:7148
+#: gitk:7926
 #, 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:8068
 msgid "Children"
 msgstr "Barn"
 
-#: gitk:7347
+#: gitk:8125
 #, tcl-format
 msgid "Reset %s branch to here"
 msgstr "Återställ grenen %s hit"
 
-#: gitk:7349
+#: gitk:8127
 msgid "Detached head: can't reset"
 msgstr "Frånkopplad head: kan inte återställa"
 
-#: gitk:7381
+#: gitk:8236 gitk:8242
+msgid "Skipping merge commit "
+msgstr "Hoppar över sammanslagningsincheckning "
+
+#: gitk:8251 gitk:8256
+msgid "Error getting patch ID for "
+msgstr "Fel vid hämtning av patch-id för "
+
+#: gitk:8252 gitk:8257
+msgid " - stopping\n"
+msgstr " - stannar\n"
+
+#: gitk:8262 gitk:8265 gitk:8273 gitk:8283 gitk:8292
+msgid "Commit "
+msgstr "Incheckning "
+
+#: gitk:8266
+msgid ""
+" is the same patch as\n"
+"       "
+msgstr ""
+" är samma patch som\n"
+"       "
+
+#: gitk:8274
+msgid ""
+" differs from\n"
+"       "
+msgstr ""
+" skiljer sig från\n"
+"       "
+
+#: gitk:8276
+msgid "- stopping\n"
+msgstr "- stannar\n"
+
+#: gitk:8284 gitk:8293
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr " har %s barn - stannar\n"
+
+#: gitk:8324
 msgid "Top"
 msgstr "Topp"
 
-#: gitk:7382
+#: gitk:8325
 msgid "From"
 msgstr "Från"
 
-#: gitk:7387
+#: gitk:8330
 msgid "To"
 msgstr "Till"
 
-#: gitk:7410
+#: gitk:8354
 msgid "Generate patch"
 msgstr "Generera patch"
 
-#: gitk:7412
+#: gitk:8356
 msgid "From:"
 msgstr "Från:"
 
-#: gitk:7421
+#: gitk:8365
 msgid "To:"
 msgstr "Till:"
 
-#: gitk:7430
+#: gitk:8374
 msgid "Reverse"
 msgstr "Vänd"
 
-#: gitk:7432 gitk:7607
+#: gitk:8376 gitk:8561
 msgid "Output file:"
 msgstr "Utdatafil:"
 
-#: gitk:7438
+#: gitk:8382
 msgid "Generate"
 msgstr "Generera"
 
-#: gitk:7474
+#: gitk:8420
 msgid "Error creating patch:"
 msgstr "Fel vid generering av patch:"
 
-#: gitk:7496 gitk:7595 gitk:7649
+#: gitk:8443 gitk:8549 gitk:8606
 msgid "ID:"
 msgstr "Id:"
 
-#: gitk:7505
+#: gitk:8452
 msgid "Tag name:"
 msgstr "Taggnamn:"
 
-#: gitk:7509 gitk:7659
+#: gitk:8456 gitk:8615
 msgid "Create"
 msgstr "Skapa"
 
-#: gitk:7524
+#: gitk:8473
 msgid "No tag name specified"
 msgstr "Inget taggnamn angavs"
 
-#: gitk:7528
+#: gitk:8477
 #, tcl-format
 msgid "Tag \"%s\" already exists"
 msgstr "Taggen \"%s\" finns redan"
 
-#: gitk:7534
+#: gitk:8483
 msgid "Error creating tag:"
 msgstr "Fel vid skapande av tagg:"
 
-#: gitk:7604
+#: gitk:8558
 msgid "Command:"
 msgstr "Kommando:"
 
-#: gitk:7612
+#: gitk:8566
 msgid "Write"
 msgstr "Skriv"
 
-#: gitk:7628
+#: gitk:8584
 msgid "Error writing commit:"
 msgstr "Fel vid skrivning av incheckning:"
 
-#: gitk:7654
+#: gitk:8611
 msgid "Name:"
 msgstr "Namn:"
 
-#: gitk:7674
+#: gitk:8634
 msgid "Please specify a name for the new branch"
 msgstr "Ange ett namn för den nya grenen"
 
-#: gitk:7703
+#: gitk:8639
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "Grenen \"%s\" finns redan. Skriva över?"
+
+#: gitk:8705
 #, 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:8710
 msgid "Cherry-picking"
 msgstr "Plockar"
 
-#: gitk:7720
+#: 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 ""
+"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:8725
+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:8741
 msgid "No changes committed"
 msgstr "Inga ändringar incheckade"
 
-#: gitk:7745
+#: gitk:8767
 msgid "Confirm reset"
 msgstr "Bekräfta återställning"
 
-#: gitk:7747
+#: gitk:8769
 #, tcl-format
 msgid "Reset branch %s to %s?"
 msgstr "Återställa grenen %s till %s?"
 
-#: gitk:7751
+#: gitk:8773
 msgid "Reset type:"
 msgstr "Typ av återställning:"
 
-#: gitk:7755
+#: gitk:8777
 msgid "Soft: Leave working tree and index untouched"
 msgstr "Mjuk: Rör inte utcheckning och index"
 
-#: gitk:7758
+#: gitk:8780
 msgid "Mixed: Leave working tree untouched, reset index"
 msgstr "Blandad: Rör inte utcheckning, återställ index"
 
-#: gitk:7761
+#: gitk:8783
 msgid ""
 "Hard: Reset working tree and index\n"
 "(discard ALL local changes)"
@@ -732,19 +1014,19 @@ msgstr ""
 "Hård: Återställ utcheckning och index\n"
 "(förkastar ALLA lokala ändringar)"
 
-#: gitk:7777
+#: gitk:8800
 msgid "Resetting"
 msgstr "Återställer"
 
-#: gitk:7834
+#: gitk:8857
 msgid "Checking out"
 msgstr "Checkar ut"
 
-#: gitk:7885
+#: gitk:8910
 msgid "Cannot delete the currently checked-out branch"
 msgstr "Kan inte ta bort den just nu utcheckade grenen"
 
-#: gitk:7891
+#: gitk:8916
 #, tcl-format
 msgid ""
 "The commits on branch %s aren't on any other branch.\n"
@@ -753,16 +1035,16 @@ msgstr ""
 "Incheckningarna på grenen %s existerar inte på någon annan gren.\n"
 "Vill du verkligen ta bort grenen %s?"
 
-#: gitk:7922
+#: gitk:8947
 #, tcl-format
 msgid "Tags and heads: %s"
 msgstr "Taggar och huvuden: %s"
 
-#: gitk:7936
+#: gitk:8962
 msgid "Filter"
 msgstr "Filter"
 
-#: gitk:8230
+#: gitk:9257
 msgid ""
 "Error reading commit topology information; branch and preceding/following "
 "tag information will be incomplete."
@@ -770,129 +1052,161 @@ 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:10243
 msgid "Tag"
 msgstr "Tagg"
 
-#: gitk:9216
+#: gitk:10243
 msgid "Id"
 msgstr "Id"
 
-#: gitk:9262
+#: gitk:10291
 msgid "Gitk font chooser"
 msgstr "Teckensnittsväljare för Gitk"
 
-#: gitk:9279
+#: gitk:10308
 msgid "B"
 msgstr "F"
 
-#: gitk:9282
+#: gitk:10311
 msgid "I"
 msgstr "K"
 
-#: gitk:9375
+#: gitk:10407
 msgid "Gitk preferences"
 msgstr "Inställningar för Gitk"
 
-#: gitk:9376
+#: gitk:10409
 msgid "Commit list display options"
 msgstr "Alternativ för incheckningslistvy"
 
-#: gitk:9379
+#: gitk:10412
 msgid "Maximum graph width (lines)"
 msgstr "Maximal grafbredd (rader)"
 
-#: gitk:9383
+#: gitk:10416
 #, tcl-format
 msgid "Maximum graph width (% of pane)"
 msgstr "Maximal grafbredd (% av ruta)"
 
-#: gitk:9388
+#: gitk:10420
 msgid "Show local changes"
 msgstr "Visa lokala ändringar"
 
-#: gitk:9393
+#: gitk:10423
 msgid "Auto-select SHA1"
 msgstr "Välj SHA1 automatiskt"
 
-#: gitk:9398
+#: gitk:10427
 msgid "Diff display options"
 msgstr "Alternativ för diffvy"
 
-#: gitk:9400
+#: gitk:10429
 msgid "Tab spacing"
 msgstr "Blanksteg för tabulatortecken"
 
-#: gitk:9404
+#: gitk:10432
 msgid "Display nearby tags"
 msgstr "Visa närliggande taggar"
 
-#: gitk:9409
+#: gitk:10435
+msgid "Hide remote refs"
+msgstr "Dölj fjärr-referenser"
+
+#: gitk:10438
 msgid "Limit diffs to listed paths"
 msgstr "Begränsa diff till listade sökvägar"
 
-#: gitk:9414
+#: gitk:10441
 msgid "Support per-file encodings"
-msgstr ""
+msgstr "Stöd för filspecifika teckenkodningar"
 
-#: gitk:9421
+#: gitk:10447 gitk:10512
 msgid "External diff tool"
 msgstr "Externt diff-verktyg"
 
-#: gitk:9423
+#: gitk:10449
 msgid "Choose..."
 msgstr "Välj..."
 
-#: gitk:9428
+#: gitk:10454
 msgid "Colors: press to choose"
 msgstr "Färger: tryck för att välja"
 
-#: gitk:9431
+#: gitk:10457
 msgid "Background"
 msgstr "Bakgrund"
 
-#: gitk:9435
+#: gitk:10458 gitk:10488
+msgid "background"
+msgstr "bakgrund"
+
+#: gitk:10461
 msgid "Foreground"
 msgstr "Förgrund"
 
-#: gitk:9439
+#: gitk:10462
+msgid "foreground"
+msgstr "förgrund"
+
+#: gitk:10465
 msgid "Diff: old lines"
 msgstr "Diff: gamla rader"
 
-#: gitk:9444
+#: gitk:10466
+msgid "diff old lines"
+msgstr "diff gamla rader"
+
+#: gitk:10470
 msgid "Diff: new lines"
 msgstr "Diff: nya rader"
 
-#: gitk:9449
+#: gitk:10471
+msgid "diff new lines"
+msgstr "diff nya rader"
+
+#: gitk:10475
 msgid "Diff: hunk header"
 msgstr "Diff: delhuvud"
 
-#: gitk:9455
+#: gitk:10477
+msgid "diff hunk header"
+msgstr "diff delhuvud"
+
+#: gitk:10481
+msgid "Marked line bg"
+msgstr "Markerad rad bakgrund"
+
+#: gitk:10483
+msgid "marked line background"
+msgstr "markerad rad bakgrund"
+
+#: gitk:10487
 msgid "Select bg"
 msgstr "Markerad bakgrund"
 
-#: gitk:9459
+#: gitk:10491
 msgid "Fonts: press to choose"
 msgstr "Teckensnitt: tryck för att välja"
 
-#: gitk:9461
+#: gitk:10493
 msgid "Main font"
 msgstr "Huvudteckensnitt"
 
-#: gitk:9462
+#: gitk:10494
 msgid "Diff display font"
 msgstr "Teckensnitt för diffvisning"
 
-#: gitk:9463
+#: gitk:10495
 msgid "User interface font"
 msgstr "Teckensnitt för användargränssnitt"
 
-#: gitk:9488
+#: gitk:10522
 #, tcl-format
 msgid "Gitk: choose color for %s"
 msgstr "Gitk: välj färg för %s"
 
-#: gitk:9934
+#: gitk:10973
 msgid ""
 "Sorry, gitk cannot run with this version of Tcl/Tk.\n"
 " Gitk requires at least Tcl/Tk 8.4."
@@ -900,24 +1214,33 @@ 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:11101
 msgid "Cannot find a git repository here."
 msgstr "Hittar inget gitk-arkiv här."
 
-#: gitk:10051
+#: gitk:11105
 #, tcl-format
 msgid "Cannot find the git directory \"%s\"."
 msgstr "Hittar inte git-katalogen \"%s\"."
 
-#: gitk:10098
+#: gitk:11152
 #, tcl-format
 msgid "Ambiguous argument '%s': both revision and filename"
 msgstr "Tvetydigt argument \"%s\": både revision och filnamn"
 
-#: gitk:10110
+#: gitk:11164
 msgid "Bad arguments to gitk:"
 msgstr "Felaktiga argument till gitk:"
 
-#: gitk:10170
+#: gitk:11249
 msgid "Command line"
 msgstr "Kommandorad"
+
+#~ 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..b76a0cffff783ba580294560f0ee53131776136b 100644 (file)
@@ -123,6 +123,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
 -------------------
index 9056d1e090b314cfa617ccd8aa03047b3aea23b4..e34ee793ef51584344f8c808454b9ead0c3348c6 100644 (file)
@@ -92,6 +92,11 @@ You can specify the following configuration variables when building GIT:
    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]
+ * GITWEB_JS
+   Points to the localtion 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: gitweb.js (or 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 +170,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).
index de637c0608090162a6ce6b51d5f9bfe512cf8bcf..aae35a70e70351fe6dcb3e905e2e388cf0cb0ac3 100644 (file)
Binary files a/gitweb/git-favicon.png and b/gitweb/git-favicon.png differ
index 16ae8d5382de5ffe63b54139245143513a87446e..f4ede2e944868b9a08401dafeb2b944c7166fd0a 100644 (file)
Binary files a/gitweb/git-logo.png and b/gitweb/git-logo.png differ
index d05bc376463c3412f50f299949fb836aee5c12da..50067f2e0dea2c1e17a41e9a5e8b2a8f6240c13d 100644 (file)
@@ -32,6 +32,10 @@ img.avatar {
        vertical-align: middle;
 }
 
+a.list img.avatar {
+       border-style: none;
+}
+
 div.page_header {
        height: 25px;
        padding: 8px;
@@ -75,6 +79,13 @@ div.page_footer_text {
        font-style: italic;
 }
 
+div#generating_info {
+       margin: 4px;
+       font-size: smaller;
+       text-align: center;
+       color: #505050;
+}
+
 div.page_body {
        padding: 8px;
        font-family: monospace;
@@ -226,22 +237,35 @@ th {
        text-align: left;
 }
 
-tr.light:hover {
-       background-color: #edece6;
-}
-
-tr.dark {
-       background-color: #f6f6f0;
+/* do not change row style on hover for 'blame' view */
+tr.light,
+table.blame .light:hover {
+       background-color: #ffffff;
 }
 
-tr.dark2 {
+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%;
@@ -262,7 +286,7 @@ td.sha1 {
        font-family: monospace;
 }
 
-td.error {
+.error {
        color: red;
        background-color: yellow;
 }
@@ -333,6 +357,23 @@ 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 {
diff --git a/gitweb/gitweb.js b/gitweb/gitweb.js
new file mode 100644 (file)
index 0000000..2a25b7c
--- /dev/null
@@ -0,0 +1,870 @@
+// 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
+       if (xhr.readyState === 3 && xhr.status !== 200) {
+               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
index 7fbd5ff89e82d3c5b1496ff116c7624f538fd66c..7e477af9567cff322d18fc9a1fbfe507732418dc 100755 (executable)
@@ -18,6 +18,12 @@ 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'};
 }
@@ -90,6 +96,8 @@ 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/";
@@ -160,7 +168,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 +185,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 +205,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,
@@ -287,6 +305,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
@@ -394,6 +425,20 @@ our %feature = (
                '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]},
 );
 
 sub gitweb_get_feature {
@@ -405,7 +450,7 @@ sub gitweb_get_feature {
                @{$feature{$name}{'default'}});
        if (!$override) { return @defaults; }
        if (!defined $sub) {
-               warn "feature $name is not overrideable";
+               warn "feature $name is not overridable";
                return @defaults;
        }
        return $sub->(@defaults);
@@ -494,7 +539,8 @@ sub filter_snapshot_fmts {
                exists $known_snapshot_format_aliases{$_} ?
                       $known_snapshot_format_aliases{$_} : $_} @fmts;
        @fmts = grep {
-               exists $known_snapshot_formats{$_} } @fmts;
+               exists $known_snapshot_formats{$_} &&
+               !$known_snapshot_formats{$_}{'disabled'}} @fmts;
 }
 
 our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
@@ -507,6 +553,7 @@ if (-e $GITWEB_CONFIG) {
 
 # version of the core git binary
 our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
+$number_of_git_cmds++;
 
 $projects_list ||= $projectroot;
 
@@ -544,12 +591,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,
@@ -940,10 +991,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'};
@@ -1069,8 +1123,7 @@ 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;
+       $str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg;
        $str =~ s/ /\+/g;
        return $str;
 }
@@ -1510,10 +1563,10 @@ sub format_subject_html {
                $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;
        }
 }
 
@@ -1581,6 +1634,29 @@ sub git_get_avatar {
        }
 }
 
+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).
@@ -1589,8 +1665,10 @@ sub format_author_html {
        my $co = shift;
        my $author = chop_and_escape_str($co->{'author_name'}, @_);
        return "<$tag class=\"author\">" .
-              git_get_avatar($co->{'author_email'}, -pad_after => 1) .
-              $author . "</$tag>";
+              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) ..."
@@ -1955,6 +2033,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;
 }
 
@@ -1969,16 +2048,27 @@ sub quote_command {
 
 # 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;
@@ -2570,7 +2660,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/^([^<]+) <([^>]*)>/) {
@@ -2580,10 +2670,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;
@@ -2751,16 +2840,31 @@ sub parse_ls_tree_line {
        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;
@@ -3205,10 +3309,36 @@ sub git_footer_html {
        }
        print "</div>\n"; # class="page_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 (-f $site_footer) {
                insert_file($site_footer);
        }
 
+       print qq!<script type="text/javascript" src="$javascript"></script>\n!;
+       if ($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>";
 }
@@ -3298,22 +3428,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) {
@@ -3360,10 +3486,11 @@ 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 "<$tag class=\"author_date\">" .
-             esc_html($co->{'author_name'}) .
+             format_search_author($author, "author", esc_html($author)) .
              " [$ad{'rfc2822'}";
        print_local_time(%ad) if ($opts{-localtime});
        print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1)
@@ -3382,8 +3509,12 @@ sub git_print_authorship_rows {
        @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>" . esc_html($co->{$who}) . "</td>" .
-                     "<td rowspan=\"2\">" .
+               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>" .
@@ -3551,6 +3682,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'},
@@ -3596,12 +3730,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 " | " .
@@ -4286,6 +4422,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) = @_;
@@ -4332,7 +4508,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});
@@ -4366,7 +4543,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) {
@@ -4758,7 +4935,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");
@@ -4780,107 +4963,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();
@@ -5036,7 +5332,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",
+                       printf "<div class=\"pre\"><a id=\"l%i\" href=\"" . href(-replay => 1)
+                               . "#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
                               $nr, $nr, $nr, esc_html($line, -nbsp=>1);
                }
        }
@@ -5059,10 +5356,14 @@ sub git_tree {
        }
        die_error(404, "No such tree") unless defined($hash);
 
+       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', $hash
+               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
@@ -5073,7 +5374,6 @@ sub git_tree {
        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) {
@@ -5089,7 +5389,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;
@@ -5122,8 +5423,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";
@@ -5132,7 +5435,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";
@@ -5150,6 +5453,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) {
@@ -5161,32 +5501,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
@@ -5197,22 +5538,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)},
@@ -5221,50 +5597,26 @@ 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";
-                     git_print_authorship(\%co, -tag => 'span');
-                     print "<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)
@@ -5297,7 +5649,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");
@@ -5585,7 +5937,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");
@@ -5793,7 +6145,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 {
@@ -5801,70 +6153,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 {
@@ -6128,44 +6419,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);
 }
 
 ## ......................................................................
diff --git a/graph.c b/graph.c
index e466770208d73d8e1f5bc7f6b12728b6f2b7c6bb..6746d422a98ed010489d4ce74b26a8a4600b183e 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -225,7 +225,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
@@ -286,9 +291,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,
@@ -499,11 +505,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);
diff --git a/graph.h b/graph.h
index bc30d687c0dbfc06d459948788784b3d8ab1b5ff..b82ae87a491454aa119476ac9609ad176e8c9406 100644 (file)
--- a/graph.h
+++ b/graph.h
@@ -6,7 +6,6 @@ struct git_graph;
 
 /*
  * 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);
 
diff --git a/grep.c b/grep.c
index 5d162dae6e43cdfcf6b20627df088a73cbfa98dc..bdadf2c0ccfafc18011beee3bc05754860392523 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -41,6 +41,7 @@ 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;
 
        if (opt->fixed || is_fixed(p->pattern))
                p->fixed = 1;
@@ -262,9 +263,15 @@ static void show_name(struct grep_opt *opt, const char *name)
        printf("%s%c", name, opt->null_following_name ? '\0' : '\n');
 }
 
-static int fixmatch(const char *pattern, char *line, regmatch_t *match)
+
+static int fixmatch(const char *pattern, char *line, int ignore_case, regmatch_t *match)
 {
-       char *hit = strstr(line, pattern);
+       char *hit;
+       if (ignore_case)
+               hit = strcasestr(line, pattern);
+       else
+               hit = strstr(line, pattern);
+
        if (!hit) {
                match->rm_so = match->rm_eo = -1;
                return REG_NOMATCH;
@@ -326,7 +333,7 @@ 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->pattern, bol, p->ignore_case, pmatch);
        else
                hit = !regexec(&p->regexp, bol, 1, pmatch, eflags);
 
diff --git a/grep.h b/grep.h
index f00db0e40273c7cca1695ddc6be00921f9da8ef4..75370f60d7091becd3eaddf3b7069c26fca8c125 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -32,6 +32,7 @@ struct grep_pat {
        enum grep_header_field field;
        regex_t regexp;
        unsigned fixed:1;
+       unsigned ignore_case:1;
        unsigned word_regexp:1;
 };
 
@@ -59,10 +60,12 @@ struct grep_opt {
        struct grep_pat *pattern_list;
        struct grep_pat **pattern_tail;
        struct grep_expr *pattern_expression;
+       const char *prefix;
        int prefix_length;
        regex_t regexp;
        int linenum;
        int invert;
+       int ignore_case;
        int status_only;
        int name_only;
        int unmatch_name_only;
@@ -79,6 +82,7 @@ struct grep_opt {
        int pathname;
        int null_following_name;
        int color;
+       int max_depth;
        int funcname;
        char color_match[COLOR_MAXLEN];
        const char *color_external;
diff --git a/help.c b/help.c
index 6c46d8b4949c4ad70ca6abc7edb4f7926c889a5a..9da97d7462040d3935e7eaa95b1167357b38a943 100644 (file)
--- a/help.c
+++ b/help.c
@@ -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);
@@ -348,7 +352,7 @@ const char *help_unknown_cmd(const char *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/http-backend.c b/http-backend.c
new file mode 100644 (file)
index 0000000..f729488
--- /dev/null
@@ -0,0 +1,655 @@
+#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"
+
+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 int 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 *decode_parameter(const char **query, int is_name)
+{
+       const char *q = *query;
+       struct strbuf out;
+
+       strbuf_init(&out, 16);
+       do {
+               unsigned char c = *q;
+
+               if (!c)
+                       break;
+               if (c == '&' || (is_name && c == '=')) {
+                       q++;
+                       break;
+               }
+
+               if (c == '%') {
+                       int val = decode_char(q + 1);
+                       if (0 <= val) {
+                               strbuf_addch(&out, val);
+                               q += 3;
+                               continue;
+                       }
+               }
+
+               if (c == '+')
+                       strbuf_addch(&out, ' ');
+               else
+                       strbuf_addch(&out, c);
+               q++;
+       } while (1);
+       *query = q;
+       return strbuf_detach(&out, NULL);
+}
+
+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 = decode_parameter(&query, 1);
+                       char *value = decode_parameter(&query, 0);
+                       struct string_list_item *i;
+
+                       i = string_list_lookup(name, query_params);
+                       if (!i)
+                               i = string_list_insert(name, query_params);
+                       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(name, get_parameters());
+       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)
+{
+       char buffer[1000];
+
+       http_status(500, "Internal Server Error");
+       hdr_nocache();
+       end_headers();
+
+       vsnprintf(buffer, sizeof(buffer), err, params);
+       fprintf(stderr, "fatal: %s\n", buffer);
+       exit(0);
+}
+
+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);
+
+       git_config(http_config, NULL);
+       cmd->imp(cmd_arg);
+       return 0;
+}
similarity index 82%
rename from builtin-http-fetch.c
rename to http-fetch.c
index f3e63d7206604029504aaf85b3d2e7731d054d2c..ffd0ad7e295d7341776bb7b6407602cdb2997ef3 100644 (file)
@@ -1,8 +1,13 @@
 #include "cache.h"
+#include "exec_cmd.h"
 #include "walker.h"
 
-int cmd_http_fetch(int argc, const char **argv, const char *prefix)
+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;
@@ -18,7 +23,7 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix)
        int get_verbosely = 0;
        int get_recover = 0;
 
-       git_config(git_default_config, NULL);
+       git_extract_argv0_path(argv[0]);
 
        while (arg < argc && argv[arg][0] == '-') {
                if (argv[arg][1] == 't') {
@@ -34,6 +39,8 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix)
                } 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")) {
@@ -41,10 +48,8 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix)
                }
                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 (argc != arg + 2 - commits_on_stdin)
+               usage(http_fetch_usage);
        if (commits_on_stdin) {
                commits = walker_targets_stdin(&commit_id, &write_ref);
        } else {
@@ -52,6 +57,11 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix)
                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);
index 00e83dcec1d973b069d4c75105aed96634b00994..432b20f2d9a750263d930683e770413ac5328935 100644 (file)
@@ -78,6 +78,7 @@ 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;
 
@@ -407,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)) {
@@ -604,7 +605,7 @@ static void finish_request(struct transfer_request *request)
                        preq = (struct http_pack_request *)request->userData;
 
                        if (preq) {
-                               if (finish_http_pack_request(preq) > 0)
+                               if (finish_http_pack_request(preq) == 0)
                                        fail = 0;
                                release_http_pack_request(preq);
                        }
@@ -1792,8 +1793,6 @@ int main(int argc, char **argv)
 
        git_extract_argv0_path(argv[0]);
 
-       setup_git_directory();
-
        repo = xcalloc(sizeof(*repo), 1);
 
        argv++;
@@ -1813,6 +1812,10 @@ 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;
@@ -1827,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, "//");
@@ -1854,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);
 
        /*
@@ -1911,9 +1918,12 @@ int main(int argc, char **argv)
 
        /* 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;
        }
 
@@ -1925,6 +1935,8 @@ int main(int argc, char **argv)
        }
        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;
        }
@@ -1942,8 +1954,12 @@ int main(int argc, char **argv)
                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;
                }
@@ -1951,6 +1967,8 @@ int main(int argc, char **argv)
                if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
                        if (push_verbosely || 1)
                                fprintf(stderr, "'%s': up-to-date\n", ref->name);
+                       if (helper_status)
+                               printf("ok %s up to date\n", ref->name);
                        continue;
                }
 
@@ -1974,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;
                        }
@@ -1987,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;
                }
@@ -2045,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();
        }
diff --git a/http.c b/http.c
index a2720d576d72e456b038444050f5b8de9d25d792..ed6414a2aaa4e0f6cf7672a089f49060aad62bfb 100644 (file)
--- a/http.c
+++ b/http.c
@@ -1,9 +1,11 @@
 #include "http.h"
 #include "pack.h"
+#include "sideband.h"
 
 int data_received;
 int active_requests;
 int http_is_verbose;
+size_t http_post_buffer = 16 * LARGE_PACKET_MAX;
 
 #ifdef USE_CURL_MULTI
 static int max_requests = -1;
@@ -97,8 +99,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)
 {
@@ -174,6 +174,13 @@ 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;
+       }
+
        /* Fall back on the default ones */
        return git_default_config(var, value, cb);
 }
@@ -638,7 +645,7 @@ void release_active_slot(struct active_request_slot *slot)
 #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);
@@ -719,7 +726,9 @@ void append_remote_object_url(struct strbuf *buf, const char *url,
                              const char *hex,
                              int only_two_digit_prefix)
 {
-       strbuf_addf(buf, "%s/objects/%.*s/", url, 2, hex);
+       end_url_with_slash(buf, url);
+
+       strbuf_addf(buf, "objects/%.*s/", 2, hex);
        if (!only_two_digit_prefix)
                strbuf_addf(buf, "%s", hex+2);
 }
@@ -864,20 +873,9 @@ static int fetch_pack_index(unsigned char *sha1, const char *base_url)
        int ret = 0;
        char *hex = xstrdup(sha1_to_hex(sha1));
        char *filename;
-       char *url;
+       char *url = NULL;
        struct strbuf buf = STRBUF_INIT;
 
-       /* Don't use the index if the pack isn't there */
-       end_url_with_slash(&buf, base_url);
-       strbuf_addf(&buf, "objects/pack/pack-%s.pack", hex);
-       url = strbuf_detach(&buf, 0);
-
-       if (http_get_strbuf(url, NULL, 0)) {
-               ret = error("Unable to verify pack %s is available",
-                           hex);
-               goto cleanup;
-       }
-
        if (has_pack_index(sha1)) {
                ret = 0;
                goto cleanup;
@@ -1004,7 +1002,6 @@ int finish_http_pack_request(struct http_pack_request *preq)
 struct http_pack_request *new_http_pack_request(
        struct packed_git *target, const char *base_url)
 {
-       char *url;
        char *filename;
        long prev_posn = 0;
        char range[RANGE_HEADER_SIZE];
@@ -1018,8 +1015,7 @@ struct http_pack_request *new_http_pack_request(
        end_url_with_slash(&buf, base_url);
        strbuf_addf(&buf, "objects/pack/pack-%s.pack",
                sha1_to_hex(target->sha1));
-       url = strbuf_detach(&buf, NULL);
-       preq->url = xstrdup(url);
+       preq->url = strbuf_detach(&buf, NULL);
 
        filename = sha1_pack_name(target->sha1);
        snprintf(preq->filename, sizeof(preq->filename), "%s", filename);
@@ -1035,7 +1031,7 @@ struct http_pack_request *new_http_pack_request(
        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, url);
+       curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url);
        curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER,
                no_pragma_header);
 
@@ -1059,6 +1055,8 @@ struct http_pack_request *new_http_pack_request(
 
 abort:
        free(filename);
+       free(preq->url);
+       free(preq);
        return NULL;
 }
 
@@ -1098,7 +1096,6 @@ struct http_object_request *new_http_object_request(const char *base_url,
        char *hex = sha1_to_hex(sha1);
        char *filename;
        char prevfile[PATH_MAX];
-       char *url;
        int prevlocal;
        unsigned char prev_buf[PREV_BUF_SIZE];
        ssize_t prev_read = 0;
@@ -1152,8 +1149,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
 
        git_SHA1_Init(&freq->c);
 
-       url = get_remote_object_url(base_url, hex, 0);
-       freq->url = xstrdup(url);
+       freq->url = get_remote_object_url(base_url, hex, 0);
 
        /*
         * If a previous temp file is present, process what was already
@@ -1189,7 +1185,11 @@ struct http_object_request *new_http_object_request(const char *base_url,
                if (prev_posn>0) {
                        prev_posn = 0;
                        lseek(freq->localfile, 0, SEEK_SET);
-                       ftruncate(freq->localfile, 0);
+                       if (ftruncate(freq->localfile, 0) < 0) {
+                               error("Couldn't truncate temporary file %s for %s: %s",
+                                         freq->tmpfile, freq->filename, strerror(errno));
+                               goto abort;
+                       }
                }
        }
 
@@ -1198,7 +1198,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
        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, url);
+       curl_easy_setopt(freq->slot->curl, CURLOPT_URL, freq->url);
        curl_easy_setopt(freq->slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
 
        /*
@@ -1218,9 +1218,9 @@ struct http_object_request *new_http_object_request(const char *base_url,
 
        return freq;
 
-       free(url);
 abort:
        free(filename);
+       free(freq->url);
        free(freq);
        return NULL;
 }
@@ -1285,5 +1285,10 @@ void release_http_object_request(struct http_object_request *freq)
                free(freq->url);
                freq->url = NULL;
        }
-       freq->slot = 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 4c4e99c2f6cae97110efe28986333fc5cbc4c095..f828e1d8067aeff3036c14a1c43e333902e46bd6 100644 (file)
--- a/http.h
+++ b/http.h
@@ -79,6 +79,7 @@ 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);
 
@@ -94,6 +95,7 @@ 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];
 
diff --git a/ident.c b/ident.c
index 99f1c85ea5f5c83247f7affd3d801ff5c14393a6..26409b2a1b191765706265c2aa6d2ae163ba5bab 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -205,7 +205,7 @@ 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" */
+                       env_hint = NULL; /* warn only once */
                }
                if (error_on_no_name)
                        die("empty ident %s <%s> not allowed", name, email);
index 3847fd151df6e9f41fdb0b95573882a7d9ac992a..de8114bac010ef095cf9de17671566e248366dcb 100644 (file)
@@ -24,6 +24,7 @@
 
 #include "cache.h"
 #include "exec_cmd.h"
+#include "run-command.h"
 #ifdef NO_OPENSSL
 typedef void *SSL;
 #endif
@@ -93,6 +94,9 @@ struct msg_data {
        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 +104,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 +130,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;
@@ -154,7 +158,7 @@ struct imap_list {
 };
 
 struct imap_socket {
-       int fd;
+       int fd[2];
        SSL *ssl;
 };
 
@@ -272,8 +276,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 +312,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 +339,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 +357,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 +375,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 +504,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)
@@ -596,6 +565,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 +579,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 +595,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 +886,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 +932,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);
        }
@@ -982,40 +954,35 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
        struct imap_store *ctx;
        struct imap *imap;
        char *arg, *rsp;
-       int s = -1, 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) {
-               imap_info("Starting tunnel '%s'... ", srvc->tunnel);
+               const char *argv[4];
+               struct child_process tunnel = {0};
 
-               if (socketpair(PF_UNIX, SOCK_STREAM, 0, a)) {
-                       perror("socketpair");
-                       exit(1);
-               }
+               imap_info("Starting tunnel '%s'... ", srvc->tunnel);
 
-               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);
-               }
+               argv[0] = "sh";
+               argv[1] = "-c";
+               argv[2] = srvc->tunnel;
+               argv[3] = NULL;
 
-               close(a[0]);
+               tunnel.argv = argv;
+               tunnel.in = -1;
+               tunnel.out = -1;
+               if (start_command(&tunnel))
+                       die("cannot start proxy %s", argv[0]);
 
-               imap->buf.sock.fd = a[1];
+               imap->buf.sock.fd[0] = tunnel.out;
+               imap->buf.sock.fd[1] = tunnel.in;
 
                imap_info("ok\n");
        } else {
@@ -1092,7 +1059,8 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
                        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)) {
@@ -1198,88 +1166,20 @@ static int imap_make_flags(int flags, char *buf)
        return d;
 }
 
-#define TUIDL 8
-
-static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid)
+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];
 
        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) {
@@ -1288,26 +1188,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;
 }
@@ -1483,7 +1371,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;
@@ -1491,8 +1378,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);
@@ -1540,7 +1427,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++;
index 340074fc793e8e7534bb168784a7051af6d81b34..190f372dd81f85d76068b166cc1688487640fdba 100644 (file)
@@ -206,8 +206,8 @@ static void parse_pack_header(void)
        use(sizeof(struct pack_header));
 }
 
-static void bad_object(unsigned long offset, const char *format,
-                      ...) NORETURN __attribute__((format (printf, 2, 3)));
+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, ...)
 {
@@ -882,6 +882,9 @@ int main(int argc, char **argv)
 
        git_extract_argv0_path(argv[0]);
 
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage(index_pack_usage);
+
        /*
         * We wish to read the repository's config file if any, and
         * for that it is necessary to call setup_git_directory_gently().
index 0571564ddfb336247c8268fbdecd8674d8bacd3e..2d6b6d6cb1d2bc2d334bf058feb3444e94b5a781 100644 (file)
@@ -192,10 +192,6 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
 
        args[2] = cmd.buf;
        status = run_command_v_opt(args, 0);
-       if (status < -ERR_RUN_COMMAND_FORK)
-               ; /* failure in run-command */
-       else
-               status = -status;
        fd = open(temp[1], O_RDONLY);
        if (fd < 0)
                goto bad;
index eb931eded5a6ed20f1d80dadf08cbb8009d85767..6851fa55a503bd24dd204934d7433e306996ce09 100644 (file)
@@ -155,18 +155,32 @@ 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));
-       }
+       } else
+               strbuf_addf(&buf, "Unable to create '%s.lock': %s", 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)
index 6f73c17d74bee326a40505b29bba762bade2451e..0fdf159f8098532e3ed77251d36e163b695310ba 100644 (file)
@@ -25,7 +25,8 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in
        struct object *obj = parse_object(sha1);
        if (!obj)
                return 0;
-       refname = prettify_refname(refname);
+       if (!cb_data || *(int *)cb_data == DECORATE_SHORT_REFS)
+               refname = prettify_refname(refname);
        add_name_decoration("", refname, obj);
        while (obj->type == OBJ_TAG) {
                obj = ((struct tag *)obj)->tagged;
@@ -36,12 +37,13 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in
        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);
        }
 }
 
@@ -168,18 +170,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)
 {
@@ -189,8 +179,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);
@@ -287,10 +279,9 @@ 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;
        if (!opt->verbose_header) {
@@ -357,8 +348,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)
@@ -401,7 +392,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;
                }
@@ -413,11 +406,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);
index 20b5caf1aa45aa0ba076ec60320d252c42abfb64..3f7b40027b7203985f018e43ab72a6cfc233e8d7 100644 (file)
@@ -17,7 +17,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 d415c4188d181fbdc765fff90fbbc4897ced53ad..dd4fbd0e6bc22f2e5f5667205b47165f8aebbbd0 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"
@@ -86,6 +87,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;
@@ -181,6 +183,7 @@ static int git_merge_trees(int index_only,
        opts.fn = threeway_merge;
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
+       opts.msgs = get_porcelain_error_msgs();
 
        init_tree_desc_from_tree(t+0, common);
        init_tree_desc_from_tree(t+1, head);
@@ -201,7 +204,8 @@ struct tree *write_tree_from_memory(struct merge_options *o)
                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);
+                               output(o, 0, "%d %.*s", ce_stage(ce),
+                                      (int)ce_namelen(ce), ce->name);
                }
                return NULL;
        }
@@ -952,9 +956,31 @@ 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);
+                               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(ren1_dst, renames2Dst))) {
                                ren2 = item->util;
                                clean_merge = 0;
@@ -1145,6 +1171,28 @@ static int process_entry(struct merge_options *o,
        return clean_merge;
 }
 
+struct unpack_trees_error_msgs get_porcelain_error_msgs(void)
+{
+       struct unpack_trees_error_msgs msgs = {
+               /* would_overwrite */
+               "Your local changes to '%s' would be overwritten by merge.  Aborting.",
+               /* not_uptodate_file */
+               "Your local changes to '%s' would be overwritten by merge.  Aborting.",
+               /* not_uptodate_dir */
+               "Updating '%s' would lose untracked files in it.  Aborting.",
+               /* would_lose_untracked */
+               "Untracked working tree file '%s' would be %s by merge.  Aborting",
+               /* bind_overlap -- will not happen here */
+               NULL,
+       };
+       if (advice_commit_before_merge) {
+               msgs.would_overwrite = msgs.not_uptodate_file =
+                       "Your local changes to '%s' would be overwritten by merge.  Aborting.\n"
+                       "Please, commit your changes or stash them before you can merge.";
+       }
+       return msgs;
+}
+
 int merge_trees(struct merge_options *o,
                struct tree *head,
                struct tree *merge,
@@ -1166,10 +1214,14 @@ int merge_trees(struct merge_options *o,
 
        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;
index fd138ca14006843a7ce0c38cfde4de580d5ce36e..d8bc7299ee3a70484b2436afd3799fe6f7608422 100644 (file)
@@ -17,6 +17,9 @@ struct merge_options {
        struct string_list current_directory_set;
 };
 
+/* Return a list of user-friendly error messages to be used by merge */
+struct unpack_trees_error_msgs get_porcelain_error_msgs(void);
+
 /* merge_trees() but with recursive ancestor consolidation */
 int merge_recursive(struct merge_options *o,
                    struct commit *h1,
diff --git a/mktag.c b/mktag.c
index a609e3ebd106ed985fa2065dc4af6565e5a379a6..a3b4270c18ea78fa36f7243de5a9e05e2066e030 100644 (file)
--- a/mktag.c
+++ b/mktag.c
 /*
  * We refuse to tag something we can't verify. Just because.
  */
-static int verify_object(unsigned char *sha1, const char *expected_type)
+static int verify_object(const 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);
+       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(sha1, buffer, size, expected_type);
+                       ret = check_sha1_signature(repl, buffer, size, expected_type);
                free(buffer);
        }
        return ret;
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.c b/notes.c
new file mode 100644 (file)
index 0000000..023adce
--- /dev/null
+++ b/notes.c
@@ -0,0 +1,431 @@
+#include "cache.h"
+#include "commit.h"
+#include "notes.h"
+#include "refs.h"
+#include "utf8.h"
+#include "strbuf.h"
+#include "tree-walk.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 commit, 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 commit, 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];
+};
+
+#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]))
+
+static struct int_node root_node;
+
+static int initialized;
+
+static void load_subtree(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 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(l, *tree, *n);
+                       free(l);
+                       return note_tree_search(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(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(l, *tree, *n);
+                       free(l);
+                       return note_tree_search(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 int_node *tree, unsigned char n,
+               const unsigned char *key_sha1)
+{
+       void **p = note_tree_search(&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;
+}
+
+/* Create a new blob object by concatenating the two given blob objects */
+static int concatenate_notes(unsigned char *cur_sha1,
+               const unsigned char *new_sha1)
+{
+       char *cur_msg, *new_msg, *buf;
+       unsigned long cur_len, new_len, buf_len;
+       enum object_type cur_type, new_type;
+       int ret;
+
+       /* read in both note blob objects */
+       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;
+       }
+       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", cur_sha1);
+       free(buf);
+       return ret;
+}
+
+/*
+ * 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
+ *   concatenate the two notes.
+ * - 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 int_node *tree, unsigned char n,
+               struct leaf_node *entry, unsigned char type)
+{
+       struct int_node *new_node;
+       struct leaf_node *l;
+       void **p = note_tree_search(&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 (concatenate_notes(l->val_sha1,
+                                               entry->val_sha1))
+                                       die("failed to concatenate note %s "
+                                           "into note %s for commit %s",
+                                           sha1_to_hex(entry->val_sha1),
+                                           sha1_to_hex(l->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(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(l, tree, n);
+                       free(l);
+                       note_tree_insert(tree, n, entry, type);
+                       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(new_node, n + 1, l, GET_PTR_TYPE(*p));
+       *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);
+       note_tree_insert(new_node, n + 1, entry, type);
+}
+
+/* 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 void load_subtree(struct leaf_node *subtree, struct int_node *node,
+               unsigned int n)
+{
+       unsigned char commit_sha1[20];
+       unsigned int prefix_len;
+       void *buf;
+       struct tree_desc desc;
+       struct name_entry entry;
+
+       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(commit_sha1, subtree->key_sha1, prefix_len);
+       while (tree_entry(&desc, &entry)) {
+               int len = get_sha1_hex_segment(entry.path, strlen(entry.path),
+                               commit_sha1 + prefix_len, 20 - prefix_len);
+               if (len < 0)
+                       continue; /* entry.path is not a SHA1 sum. Skip */
+               len += prefix_len;
+
+               /*
+                * If commit SHA1 is complete (len == 20), assume note object
+                * If commit SHA1 is incomplete (len < 20), assume note subtree
+                */
+               if (len <= 20) {
+                       unsigned char type = PTR_TYPE_NOTE;
+                       struct leaf_node *l = (struct leaf_node *)
+                               xcalloc(sizeof(struct leaf_node), 1);
+                       hashcpy(l->key_sha1, commit_sha1);
+                       hashcpy(l->val_sha1, entry.sha1);
+                       if (len < 20) {
+                               if (!S_ISDIR(entry.mode))
+                                       continue; /* entry cannot be subtree */
+                               l->key_sha1[19] = (unsigned char) len;
+                               type = PTR_TYPE_SUBTREE;
+                       }
+                       note_tree_insert(node, n, l, type);
+               }
+       }
+       free(buf);
+}
+
+static void initialize_notes(const char *notes_ref_name)
+{
+       unsigned char sha1[20], commit_sha1[20];
+       unsigned mode;
+       struct leaf_node root_tree;
+
+       if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) ||
+           get_tree_entry(commit_sha1, "", sha1, &mode))
+               return;
+
+       hashclr(root_tree.key_sha1);
+       hashcpy(root_tree.val_sha1, sha1);
+       load_subtree(&root_tree, &root_node, 0);
+}
+
+static unsigned char *lookup_notes(const unsigned char *commit_sha1)
+{
+       struct leaf_node *found = note_tree_find(&root_node, 0, commit_sha1);
+       if (found)
+               return found->val_sha1;
+       return NULL;
+}
+
+void free_notes(void)
+{
+       note_tree_free(&root_node);
+       memset(&root_node, 0, sizeof(struct int_node));
+       initialized = 0;
+}
+
+void get_commit_notes(const struct commit *commit, struct strbuf *sb,
+               const char *output_encoding, int flags)
+{
+       static const char utf8[] = "utf-8";
+       unsigned char *sha1;
+       char *msg, *msg_p;
+       unsigned long linelen, msglen;
+       enum object_type type;
+
+       if (!initialized) {
+               const char *env = getenv(GIT_NOTES_REF_ENVIRONMENT);
+               if (env)
+                       notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT);
+               else if (!notes_ref_name)
+                       notes_ref_name = GIT_NOTES_DEFAULT_REF;
+               initialize_notes(notes_ref_name);
+               initialized = 1;
+       }
+
+       sha1 = lookup_notes(commit->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)
+               strbuf_addstr(sb, "\nNotes:\n");
+
+       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);
+}
diff --git a/notes.h b/notes.h
new file mode 100644 (file)
index 0000000..a1421e3
--- /dev/null
+++ b/notes.h
@@ -0,0 +1,13 @@
+#ifndef NOTES_H
+#define NOTES_H
+
+/* Free (and de-initialize) the internal notes tree structure */
+void free_notes(void);
+
+#define NOTES_SHOW_HEADER 1
+#define NOTES_INDENT 2
+
+void get_commit_notes(const struct commit *commit, struct strbuf *sb,
+               const char *output_encoding, int flags);
+
+#endif
index a6ef439192c1083e367f0a86cb10d93564fc9481..fe8eaaf19f71b48d9acba83594d918fc4875f5c4 100644 (file)
--- a/object.c
+++ b/object.c
@@ -188,17 +188,18 @@ 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;
                }
 
-               obj = parse_object_buffer(sha1, type, size, buffer, &eaten);
+               obj = parse_object_buffer(repl, type, size, buffer, &eaten);
                if (!eaten)
                        free(buffer);
                return obj;
index 48a12bc1352ad53fbc19ec8c5982a91673a098e1..21c61dbbe9bd5e2f9770109bc4a9daaf2a2f6cc4 100644 (file)
@@ -55,16 +55,15 @@ static inline struct llist_item *llist_item_get(void)
        } else {
                int i = 1;
                new = xmalloc(sizeof(struct llist_item) * BLKSIZE);
-               for(;i < BLKSIZE; i++) {
+               for (; i < BLKSIZE; i++)
                        llist_item_put(&new[i]);
-               }
        }
        return new;
 }
 
 static void llist_free(struct llist *list)
 {
-       while((list->back = list->front)) {
+       while ((list->back = list->front)) {
                list->front = list->front->next;
                llist_item_put(list->back);
        }
@@ -146,7 +145,7 @@ static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
                if (cmp > 0) { /* we insert before this entry */
                        return llist_insert(list, prev, sha1);
                }
-               if(!cmp) { /* already exists */
+               if (!cmp) { /* already exists */
                        return l;
                }
                prev = l;
@@ -168,7 +167,7 @@ redo_from_start:
                int cmp = hashcmp(l->sha1, sha1);
                if (cmp > 0) /* not in list, since sorted */
                        return prev;
-               if(!cmp) { /* found */
+               if (!cmp) { /* found */
                        if (prev == NULL) {
                                if (hint != NULL && hint != list->front) {
                                        /* we don't know the previous element */
@@ -218,7 +217,7 @@ static inline struct pack_list * pack_list_insert(struct pack_list **pl,
 static inline size_t pack_list_size(struct pack_list *pl)
 {
        size_t ret = 0;
-       while(pl) {
+       while (pl) {
                ret++;
                pl = pl->next;
        }
@@ -396,7 +395,7 @@ static size_t get_pack_redundancy(struct pack_list *pl)
                return 0;
 
        while ((subset = pl->next)) {
-               while(subset) {
+               while (subset) {
                        ret += sizeof_union(pl->pack, subset->pack);
                        subset = subset->next;
                }
@@ -427,7 +426,7 @@ static void minimize(struct pack_list **min)
 
        pl = local_packs;
        while (pl) {
-               if(pl->unique_objects->size)
+               if (pl->unique_objects->size)
                        pack_list_insert(&unique, pl);
                else
                        pack_list_insert(&non_unique, pl);
@@ -479,7 +478,7 @@ static void minimize(struct pack_list **min)
        *min = min_perm;
        /* add the unique packs to the list */
        pl = unique;
-       while(pl) {
+       while (pl) {
                pack_list_insert(min, pl);
                pl = pl->next;
        }
@@ -516,7 +515,7 @@ static void cmp_local_packs(void)
        struct pack_list *subset, *pl = local_packs;
 
        while ((subset = pl)) {
-               while((subset = subset->next))
+               while ((subset = subset->next))
                        cmp_two_packs(pl, subset);
                pl = pl->next;
        }
@@ -604,27 +603,30 @@ int main(int argc, char **argv)
 
        git_extract_argv0_path(argv[0]);
 
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage(pack_redundant_usage);
+
        setup_git_directory();
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
-               if(!strcmp(arg, "--")) {
+               if (!strcmp(arg, "--")) {
                        i++;
                        break;
                }
-               if(!strcmp(arg, "--all")) {
+               if (!strcmp(arg, "--all")) {
                        load_all_packs = 1;
                        continue;
                }
-               if(!strcmp(arg, "--verbose")) {
+               if (!strcmp(arg, "--verbose")) {
                        verbose = 1;
                        continue;
                }
-               if(!strcmp(arg, "--alt-odb")) {
+               if (!strcmp(arg, "--alt-odb")) {
                        alt_odb = 1;
                        continue;
                }
-               if(*arg == '-')
+               if (*arg == '-')
                        usage(pack_redundant_usage);
                else
                        break;
diff --git a/pager.c b/pager.c
index 4921843577e42b774457a61277b9bc3441d3ab6b..92c03f654abd0333bd0dd48b4aebf9ae42ac4de5 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,8 +25,6 @@ static void pager_preexec(void)
        FD_ZERO(&in);
        FD_SET(0, &in);
        select(1, &in, NULL, &in, NULL);
-
-       setenv("LESS", "FRSX", 0);
 }
 #endif
 
@@ -46,12 +48,14 @@ static void wait_for_pager_signal(int signo)
        raise(signo);
 }
 
-void setup_pager(void)
+const char *git_pager(void)
 {
-       const char *pager = getenv("GIT_PAGER");
+       const char *pager;
 
        if (!isatty(1))
-               return;
+               return NULL;
+
+       pager = getenv("GIT_PAGER");
        if (!pager) {
                if (!pager_program)
                        git_config(git_default_config, NULL);
@@ -60,8 +64,18 @@ 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();
+
+       if (!pager)
                return;
 
        spawned_pager = 1; /* means we are emitting to terminal */
@@ -70,7 +84,11 @@ void setup_pager(void)
        pager_argv[2] = pager;
        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 f7ce523a615b121b8a0825e459c1b75f24253c85..f5594114ede8a50090c8b97987b52a660235fa56 100644 (file)
@@ -230,6 +230,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;
@@ -511,7 +514,7 @@ static int usage_with_options_internal(const char * const *usagestr,
                        continue;
 
                pos = fprintf(stderr, "    ");
-               if (opts->short_name) {
+               if (opts->short_name && !(opts->flags & PARSE_OPT_NEGHELP)) {
                        if (opts->flags & PARSE_OPT_NODASH)
                                pos += fprintf(stderr, "%c", opts->short_name);
                        else
@@ -520,7 +523,9 @@ static int usage_with_options_internal(const char * const *usagestr,
                if (opts->long_name && opts->short_name)
                        pos += fprintf(stderr, ", ");
                if (opts->long_name)
-                       pos += fprintf(stderr, "--%s", opts->long_name);
+                       pos += fprintf(stderr, "--%s%s",
+                               (opts->flags & PARSE_OPT_NEGHELP) ?  "no-" : "",
+                               opts->long_name);
                if (opts->type == OPTION_NUMBER)
                        pos += fprintf(stderr, "-NUM");
 
@@ -547,6 +552,14 @@ void usage_with_options(const char * const *usagestr,
        exit(129);
 }
 
+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);
+}
+
 int parse_options_usage(const char * const *usagestr,
                        const struct option *opts)
 {
index aba30671dccd9e90686363a766df3eb25d4a8e20..f295a2cf858f4dbebc62e956878544ffdb7fbe58 100644 (file)
@@ -36,6 +36,7 @@ enum parse_opt_option_flags {
        PARSE_OPT_LASTARG_DEFAULT = 16,
        PARSE_OPT_NODASH = 32,
        PARSE_OPT_LITERAL_ARGHELP = 64,
+       PARSE_OPT_NEGHELP = 128,
 };
 
 struct option;
@@ -80,6 +81,9 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
  *   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.
@@ -141,6 +145,10 @@ extern int parse_options(int argc, const char **argv, const char *prefix,
 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 {
index ef748ce96d246bb17c6e64b51b882d535d7f7774..e02e13bd4eb2a92626c2d6f9cbf264abb15de9c5 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
diff --git a/path.c b/path.c
index 047fdb0a1fe8151f5f275ca5333365df786a8abd..2ec950b27f1c3e4919ce7d1696360c5a49abb724 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/";
 
@@ -207,43 +208,49 @@ 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");
+                       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);
+               to_copy = first_slash;
        }
-       if (!pw || !pw->pw_dir || sz <= strlen(pw->pw_dir))
-               return NULL;
-       baselen = strlen(pw->pw_dir);
-       memcpy(buf, pw->pw_dir, baselen);
-       while ((1 < baselen) && (buf[baselen-1] == '/')) {
-               buf[baselen-1] = 0;
-               baselen--;
-       }
-       if (slash && slash[1]) {
-               len = strlen(slash);
-               if (sz <= baselen + len)
-                       return NULL;
-               memcpy(buf + baselen, slash, len + 1);
-       }
-       return buf;
+       strbuf_add(&user_path, to_copy, strlen(to_copy));
+       return strbuf_detach(&user_path, NULL);
+return_null:
+       strbuf_release(&user_path);
+       return NULL;
 }
 
 /*
@@ -291,8 +298,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;
                }
@@ -564,3 +581,50 @@ 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 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;
+               }
+       }
+}
index e3dd1a5547c471208c445d77263ee46e64b37451..4ab21d61b808d2e396419fb41419bcea7ab3cde4 100644 (file)
@@ -29,11 +29,11 @@ $(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
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 b691abebd79b40f096b28b5dbcb433f01bc0e949..295ba2b16c8675d8c5c5d0a5ee6e61e044ce99dd 100644 (file)
@@ -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,9 +62,31 @@ 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);
@@ -72,15 +96,11 @@ static void safe_read(int fd, void *buffer, unsigned 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 e5328dab5b2978d90d20f47ed799782e7b2fb63b..8f5bd1ab7f119715564fb13cb74de3937d1a3774 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -6,7 +6,9 @@
 #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;
 
@@ -86,6 +88,18 @@ 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 == '_'));
@@ -430,9 +444,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 +461,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 +587,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 +599,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 +678,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 */
@@ -689,6 +758,26 @@ 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':
+               get_commit_notes(commit, sb, git_log_output_encoding ?
+                            git_log_output_encoding : git_commit_encoding, 0);
+               return 1;
        }
 
        /* For the rest we have to parse the commit header. */
@@ -699,11 +788,11 @@ 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;
@@ -727,16 +816,56 @@ 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,
+       } magic = NO_MAGIC;
+
+       switch (placeholder[0]) {
+       case '-':
+               magic = DEL_LF_BEFORE_EMPTY;
+               break;
+       case '+':
+               magic = ADD_LF_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) && magic == ADD_LF_BEFORE_NON_EMPTY) {
+               strbuf_insert(sb, orig_len, "\n", 1);
+       }
+       return consumed + 1;
+}
+
 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,
@@ -888,18 +1017,18 @@ char *reencode_commit_message(const struct commit *commit, const char **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 +1063,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 +1074,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 +1093,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 (fmt != CMIT_FMT_ONELINE)
+               get_commit_notes(commit, 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);
 }
index 4e3e272ee409de66de2059a8be475fddcaa4dc28..1bbaf1cffba3058667787fc4a267b87e21a6cad3 100644 (file)
@@ -1065,7 +1065,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 +1085,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 +1108,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 +1131,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;
                }
index 5623ea6b48a2f355e8866eabb2805b51b4127a4a..caba4f743f2dcc1cf7046cec294f242b2af19052 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;
@@ -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 e49eaa3089c39028e3c0af90422672db63fff198..3e73a0a36dbea4046e5b7b0550b2f4bb67539439 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -286,6 +286,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,13 +305,13 @@ 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 = { fp, refname, msg_fmt };
        for_each_rawref(warn_if_dangling_symref, &data);
 }
 
@@ -668,6 +669,11 @@ 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_replace_ref(each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data);
+}
+
 int for_each_rawref(each_ref_fn fn, void *cb_data)
 {
        return do_for_each_ref("refs/", fn, 0,
@@ -821,7 +827,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);
 
@@ -967,8 +973,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];
diff --git a/refs.h b/refs.h
index c11f6a6d588d1bd737fa60241b9c67308f4c14cc..e141991851be60334ea23cc6055b329c36cd272f 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -24,11 +24,12 @@ 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 *);
 
 /* 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
diff --git a/remote-curl.c b/remote-curl.c
new file mode 100644 (file)
index 0000000..a331bae
--- /dev/null
@@ -0,0 +1,815 @@
+#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;
+static struct walker *walker;
+
+struct options {
+       int verbosity;
+       unsigned long depth;
+       unsigned progress : 1,
+               followtags : 1,
+               dry_run : 1,
+               thin : 1;
+};
+static struct options options;
+
+static void init_walker(void)
+{
+       if (!walker)
+               walker = get_http_walker(url, remote);
+}
+
+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;
+
+       if (last && !strcmp(service, last->service))
+               return last;
+       free_discovery(last);
+
+       strbuf_addf(&buffer, "%s/info/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);
+
+       init_walker();
+       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);
+       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 && 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 fd, void *data)
+{
+       struct discovery *heads = data;
+       int err = 0;
+       if (write_in_full(fd, heads->buf, heads->len) != heads->len)
+               err = 1;
+       close(fd);
+       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;
+
+       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++;
+       }
+
+       init_walker();
+       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 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;
+};
+
+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) {
+               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;
+}
+
+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");
+               curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
+               curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc);
+               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;
+
+       init_walker();
+       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-response", 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);
+       }
+       strbuf_read(&rpc->result, client.out, 0);
+
+       close(client.in);
+       close(client.out);
+       client.in = -1;
+       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)
+{
+       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));
+
+       init_walker();
+       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);
+
+       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) {
+               url = argv[2];
+       } else {
+               url = remote->url[0];
+       }
+
+       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);
+       return 0;
+}
index c3ada2d72b9b3f7411ffe420030768e73a003923..e3afecdb1069a244493e906b5e16a96dc47535d3 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,17 +111,25 @@ static void add_url(struct remote *remote, const char *url)
        remote->url[remote->url_nr++] = url;
 }
 
-static void add_url_alias(struct remote *remote, const char *url)
-{
-       add_url(remote, alias_url(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, &rewrites));
+       add_pushurl_alias(remote, url);
+}
+
 static struct remote *make_remote(const char *name, int len)
 {
        struct remote *ret;
@@ -169,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;
@@ -355,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));
@@ -379,7 +402,8 @@ 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))
@@ -422,6 +446,8 @@ static int handle_config(const char *key, const char *value, void *cb)
        } 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;
 }
@@ -430,13 +456,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]->url_nr; j++) {
-                       remotes[i]->url[j] = alias_url(remotes[i]->url[j]);
-               }
                for (j = 0; j < remotes[i]->pushurl_nr; j++) {
-                       remotes[i]->pushurl[j] = alias_url(remotes[i]->pushurl[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++) {
+                       if (add_pushurl_aliases)
+                               add_pushurl_alias(remotes[i], remotes[i]->url[j]);
+                       remotes[i]->url[j] = alias_url(remotes[i]->url[j], &rewrites);
                }
        }
 }
@@ -645,6 +675,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))
@@ -667,14 +707,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);
@@ -713,29 +753,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 = { NULL, 0, 0, 0 };
+       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(ref_map->peer_ref->name, &refs);
+               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(ref_map->peer_ref->name, &refs);
+               item->util = ref_map;
        }
+       string_list_clear(&refs, 0);
 }
 
 int remote_has_url(struct remote *remote, const char *url)
@@ -783,6 +827,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;
@@ -1038,7 +1099,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"
@@ -1565,3 +1626,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 = { NULL, 0, 0, 0 };
+       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->name, &ref_names);
+       sort_string_list(&ref_names);
+       for_each_ref(get_stale_heads_cb, &info);
+       string_list_clear(&ref_names, 0);
+       return stale_refs;
+}
index 5db842087da081abbad96c8f9eed5829140714d2..8b7ecf9197f2a9210299c9700cc88a92a796f714 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -11,6 +11,8 @@ struct remote {
        const char *name;
        int origin;
 
+       const char *foreign_vcs;
+
        const char **url;
        int url_nr;
        int url_alloc;
@@ -89,6 +91,11 @@ 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);
 
+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);
 
@@ -154,4 +161,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 87360dc23e54aabf47b3bda160e58058507d4a2f..29f95f657d04300e375d007e9d01832b89311602 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -61,7 +61,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");
        }
index 9f5dac5f1d8a8a654a2ab77e8e26fb134c7e36fa..a8a3c3a4bdf5bd9287cb820330e73e8c28b88564 100644 (file)
@@ -953,21 +953,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)
@@ -994,7 +1032,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        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;
@@ -1052,7 +1091,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;
@@ -1159,8 +1198,10 @@ 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;
+               revs->date_mode_explicit = 1;
        } else if (!strncmp(arg, "--date=", 7)) {
                revs->date_mode = parse_date_format(arg + 7);
+               revs->date_mode_explicit = 1;
        } else if (!strcmp(arg, "--log-size")) {
                revs->show_log_size = 1;
        }
@@ -1216,6 +1257,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(each_ref_fn fn, void *cb_data)
+{
+       return for_each_ref_in("refs/bisect/bad", fn, cb_data);
+}
+
+static int for_each_good_bisect_ref(each_ref_fn fn, void *cb_data)
+{
+       return for_each_ref_in("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.
@@ -1225,7 +1304,8 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
  */
 int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def)
 {
-       int i, flags, left, seen_dashdash;
+       int i, flags, left, seen_dashdash, read_from_stdin;
+       const char **prune_data = NULL;
 
        /* First, search for "--" */
        seen_dashdash = 0;
@@ -1236,13 +1316,14 @@ 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 == '-') {
@@ -1257,6 +1338,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                handle_refs(revs, flags, for_each_branch_ref);
                                continue;
                        }
+                       if (!strcmp(arg, "--bisect")) {
+                               handle_refs(revs, flags, for_each_bad_bisect_ref);
+                               handle_refs(revs, flags ^ UNINTERESTING, for_each_good_bisect_ref);
+                               revs->bisect = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--tags")) {
                                handle_refs(revs, flags, for_each_tag_ref);
                                continue;
@@ -1281,6 +1368,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) {
@@ -1306,12 +1403,14 @@ 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;
                }
        }
 
+       if (prune_data)
+               revs->prune_data = get_pathspec(revs->prefix, prune_data);
+
        if (revs->def == NULL)
                revs->def = def;
        if (revs->show_merge)
@@ -1664,7 +1763,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;
@@ -1692,12 +1791,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 fb74492714b9276d02992dbe1101efff2cbdb5e6..d368003159738570220c0e4713efd91f83303339 100644 (file)
@@ -15,6 +15,9 @@
 #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;
 
@@ -60,6 +63,7 @@ struct rev_info {
                        reverse:1,
                        reverse_output_stage:1,
                        cherry_pick:1,
+                       bisect:1,
                        first_parent_only:1;
 
        /* Diff flags */
@@ -78,7 +82,10 @@ struct rev_info {
                        show_merge: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;
@@ -124,8 +131,6 @@ struct rev_info {
 #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;
 
@@ -165,6 +170,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 ff3d8e2d8bf3208b7ad0f29d7cf76cc84a4ad0e6..cf2d8f7fae1356e50736cb9d599625df79738a2a 100644 (file)
@@ -19,6 +19,7 @@ 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 +29,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 +42,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 +55,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,14 +64,18 @@ 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
        fflush(NULL);
        cmd->pid = fork();
        if (!cmd->pid) {
@@ -122,7 +130,11 @@ int start_command(struct child_process *cmd)
                                strerror(errno));
                exit(127);
        }
+       if (cmd->pid < 0)
+               error("cannot fork() for %s: %s", cmd->argv[0],
+                       strerror(failed_errno = errno));
 #else
+{
        int s0 = -1, s1 = -1, s2 = -1;  /* backups of stdin, stdout, stderr */
        const char **sargv = cmd->argv;
        char **env = environ;
@@ -162,17 +174,17 @@ int start_command(struct child_process *cmd)
 
        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);
        }
 
        cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env);
+       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);
@@ -186,10 +198,10 @@ int start_command(struct child_process *cmd)
                dup2(s1, 1), close(s1);
        if (s2 >= 0)
                dup2(s2, 2), close(s2);
+}
 #endif
 
        if (cmd->pid < 0) {
-               int err = errno;
                if (need_in)
                        close_pair(fdin);
                else if (cmd->in)
@@ -200,9 +212,8 @@ 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;
+               errno = failed_errno;
+               return -1;
        }
 
        if (need_in)
@@ -221,40 +232,51 @@ int start_command(struct child_process *cmd)
        return 0;
 }
 
-static int wait_or_whine(pid_t pid)
+static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
 {
-       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;
+       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);
-               switch (code) {
-               case 127:
-                       return -ERR_RUN_COMMAND_EXEC;
-               case 0:
-                       return 0;
-               default:
-                       return -code;
+               /*
+                * 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 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 +296,7 @@ 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;
 }
 
 int run_command_v_opt(const char **argv, int opt)
@@ -292,8 +315,8 @@ 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)
+#ifdef WIN32
+static unsigned __stdcall run_thread(void *data)
 {
        struct async *async = data;
        return async->proc(async->fd_for_proc, async->data);
@@ -308,7 +331,7 @@ int start_async(struct async *async)
                return error("cannot create pipe: %s", strerror(errno));
        async->out = pipe_out[0];
 
-#ifndef __MINGW32__
+#ifndef WIN32
        /* Flush stdio before fork() to avoid cloning buffers */
        fflush(NULL);
 
@@ -337,11 +360,8 @@ int start_async(struct async *async)
 
 int finish_async(struct async *async)
 {
-#ifndef __MINGW32__
-       int ret = 0;
-
-       if (wait_or_whine(async->pid))
-               ret = error("waitpid (async) failed");
+#ifndef WIN32
+       int ret = wait_or_whine(async->pid, "child process", 0);
 #else
        DWORD ret = 0;
        if (WaitForSingleObject(async->tid, INFINITE) != WAIT_OBJECT_0)
@@ -385,15 +405,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..fb342090e3cac49f41689f9610bfe2d6c87c87c1 100644 (file)
@@ -1,17 +1,6 @@
 #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)
-
 struct child_process {
        const char **argv;
        pid_t pid;
@@ -42,6 +31,7 @@ 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;
        void (*preexec_cb)(void);
 };
@@ -55,6 +45,7 @@ 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
 int run_command_v_opt(const char **argv, int opt);
 
 /*
@@ -79,7 +70,7 @@ struct async {
        int (*proc)(int fd, void *data);
        void *data;
        int out;        /* caller reads from here and closes it */
-#ifndef __MINGW32__
+#ifndef WIN32
        pid_t pid;
 #else
        HANDLE tid;
index 1d7b1b3b4f41177dfd6637fcd98a0fc01f310c09..28141ac913f6558b81ff0b1b21a1e1b5ead43fa0 100644 (file)
@@ -3,11 +3,13 @@
 
 struct send_pack_args {
        unsigned verbose:1,
+               quiet:1,
                send_mirror:1,
                force_update:1,
                use_thin_pack:1,
                use_ofs_delta:1,
-               dry_run:1;
+               dry_run:1,
+               stateless_rpc:1;
 };
 
 int send_pack(struct send_pack_args *args,
diff --git a/setup.c b/setup.c
index e3781b656d77be94e04c5229e6a190c5fc496490..2cf0f1993718fd737cd87e79d2f4950832c59c9f 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -18,9 +18,12 @@ 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 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);
+               total = strlen(sanitized) + 1;
                if (strncmp(sanitized, work_tree, len) ||
                    (sanitized[len] != '\0' && sanitized[len] != '/')) {
                error_out:
@@ -41,7 +44,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 +64,19 @@ 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);
+}
+
 /*
  * Verify a filename that we got as an argument for a pathspec
  * entry. Note that a filename that begins with "-" never verifies
@@ -70,18 +86,12 @@ 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_errno("failed to stat '%s'", arg);
+       die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
+           "Use '--' to separate paths from revisions", arg);
 }
 
 /*
@@ -91,19 +101,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_errno("failed to stat '%s'", arg);
+       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)
index 1d996a199036c115d46c1d630d53edf29b3b78e7..63981fb3fd9cfa6cca4126eba5a964b449c62444 100644 (file)
@@ -1587,13 +1587,15 @@ static void *unpack_compressed_entry(struct packed_git *p,
        buffer[size] = 0;
        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);
@@ -2144,13 +2146,26 @@ 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);
+
+       /* 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 && (has_loose_object(repl) || has_packed_and_bad(repl)))
+               die("object %s is corrupted", sha1_to_hex(repl));
+
+       if (replacement)
+               *replacement = repl;
+
        return data;
 }
 
index 45bb535773fd9c36f73502df9462f7de800009c8..63f9da53237d4233bede66a28e4bcf27d5b44af1 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)) {
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 f03d11702b3f6212ca7305df60f2f9ea6ca49e35..a6153dca278abe957254fa091fdcd8eb13b90b25 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -322,7 +322,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 +332,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 +344,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..fa07ecf094bd3ef8997958fde29199f8fb6421b9 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -117,7 +117,7 @@ struct strbuf_expand_dict_entry {
 };
 extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context);
 
-__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 +126,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);
diff --git a/submodule.c b/submodule.c
new file mode 100644 (file)
index 0000000..86aad65
--- /dev/null
@@ -0,0 +1,114 @@
+#include "cache.h"
+#include "submodule.h"
+#include "dir.h"
+#include "diff.h"
+#include "commit.h"
+#include "revision.h"
+
+int add_submodule_odb(const char *path)
+{
+       struct strbuf objects_directory = STRBUF_INIT;
+       struct alternate_object_database *alt_odb;
+
+       strbuf_addf(&objects_directory, "%s/.git/objects/", path);
+       if (!is_directory(objects_directory.buf))
+               return -1;
+
+       /* 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))
+                       return 0;
+
+       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();
+       return 0;
+}
+
+void show_submodule_summary(FILE *f, const char *path,
+               unsigned char one[20], unsigned char two[20],
+               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)";
+       }
+
+       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);
+}
diff --git a/submodule.h b/submodule.h
new file mode 100644 (file)
index 0000000..4c0269d
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef SUBMODULE_H
+#define SUBMODULE_H
+
+void show_submodule_summary(FILE *f, const char *path,
+               unsigned char one[20], unsigned char two[20],
+               const char *del, const char *add, const char *reset);
+
+#endif
index 4bdded39c5c5fc491189661bfeca045472970f5b..7b0a86d35776e8695423c13403c9f4fa3465017d 100644 (file)
@@ -91,6 +91,10 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len,
                        longest_path_match(name, len, cache->path, cache->len,
                                           &previous_slash);
                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;
                /*
index bf816fc8505508c91999175ad6544a67febabb33..bd09390d3208d7eac362cd9cf45f7dde623c4ae6 100644 (file)
@@ -3,6 +3,8 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
+-include ../config.mak
+
 #GIT_TEST_OPTS=--verbose --debug
 SHELL_PATH ?= $(SHELL)
 TAR ?= $(TAR)
index d8f6c7de6d27e27a33982e82893baa3bb5ffd7fd..4e1d7dd1833f43404a00b41d55800b9fe27d1807 100644 (file)
--- a/t/README
+++ b/t/README
@@ -75,6 +75,19 @@ 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.
 
+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
 --------------
 
diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh
new file mode 100644 (file)
index 0000000..76d8b7b
--- /dev/null
@@ -0,0 +1,87 @@
+#!/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/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" \
+               >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
+       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
diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh
new file mode 100644 (file)
index 0000000..4b3b793
--- /dev/null
@@ -0,0 +1,75 @@
+#!/bin/sh
+
+. ./test-lib.sh
+
+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
+
+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*)
+       ;;
+'')
+       say 'skipping cvsimport tests, cvsps not found'
+       test_done
+       ;;
+*)
+       say 'skipping cvsimport tests, unsupported cvsps version'
+       test_done
+       ;;
+esac
+
+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 56549623430ca6bea2de35bbae7d2d211720da3b..0f7f35ccc9e1315d3ac8e5d37df51c106847d920 100644 (file)
@@ -14,8 +14,9 @@ if ! test_have_prereq PERL; then
 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
+PERL=${PERL:-perl}
 
 svn >/dev/null 2>&1
 if test $? -ne 1
@@ -29,7 +30,7 @@ export svnrepo
 svnconf=$PWD/svnconf
 export svnconf
 
-perl -w -e "
+$PERL -w -e "
 use SVN::Core;
 use SVN::Repos;
 \$SVN::Core::VERSION gt '1.1.0' or exit(42);
@@ -130,7 +131,7 @@ stop_httpd () {
 }
 
 convert_to_rev_db () {
-       perl -w -- - "$@" <<\EOF
+       $PERL -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]";
index 21aa42f1c613538ba26c0b613cc0d1b001bc52b9..0fe3fd0d012159f6abe7f7d8b006d3f8e59cbab3 100644 (file)
@@ -8,6 +8,28 @@ 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}
+</Location>
+ScriptAlias /smart/ ${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 +48,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-patch-mode.sh b/t/lib-patch-mode.sh
new file mode 100755 (executable)
index 0000000..75a3ee2
--- /dev/null
@@ -0,0 +1,41 @@
+. ./test-lib.sh
+
+if ! test_have_prereq PERL; then
+       say 'skipping --patch tests, perl not available'
+       test_done
+fi
+
+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)"
+}
index 260a231933a3cc9ce1f96eeb5952f34d7dc18d72..62f452c8ea2c2718fff300d39531fbc4c2e6a44a 100644 (file)
@@ -9,8 +9,8 @@
 #
 #      "[<lineno1>] [<lineno2>]..."
 #
-#   If a line number is prefixed with "squash" or "edit", the respective line's
-#   command will be replaced with the specified one.
+#   If a line number is prefixed with "squash", "edit", or "reword", the
+#   respective line's command will be replaced with the specified one.
 
 set_fake_editor () {
        echo "#!$SHELL_PATH" >fake-editor.sh
@@ -32,7 +32,7 @@ cat "$1".tmp
 action=pick
 for line in $FAKE_LINES; do
        case $line in
-       squash|edit)
+       squash|edit|reword)
                action="$line";;
        *)
                echo sed -n "${line}s/^pick/$action/p"
index e3d846420dc09e7b24876b291b9e546ac0628ed3..5386504790deea55d127f053f7b714cd121a2d57 100755 (executable)
@@ -208,4 +208,87 @@ 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 'init notices EPERM' '
+       rm -fr newdir &&
+       (
+               mkdir newdir &&
+               chmod -w newdir &&
+               test_must_fail git init newdir/a/b
+       )
+'
+
 test_done
diff --git a/t/t0006-date.sh b/t/t0006-date.sh
new file mode 100755 (executable)
index 0000000..75b02af
--- /dev/null
@@ -0,0 +1,76 @@
+#!/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_${3:-success} "parse date ($1)" "
+       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_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 bbc821ef97de76ef532ec1d1d704da15e8973500..3d450ed379fcd1bf480fc75feb0e6ddb8f05e5be 100755 (executable)
@@ -33,6 +33,8 @@ 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
@@ -315,4 +317,22 @@ test_expect_success 'OPT_NUMBER_CALLBACK() works' '
        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 271bc4e17f0c12cda550ffa4f54f1ad7555b3bed..c2d408b46120ef9d962272b652a68a83ba0852a5 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
@@ -51,7 +51,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 +300,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 &&
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
+
index 67e637b7810190e1b7d12dab70cd41d83db0e442..238c2f1c08d0c27b71cdde8c7ddc2b3240ea1fde 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
+       ! find .git/objects/[0-9a-f][0-9a-f] -type f
 '
 
 test_done
index 8c43dcde8ab8d491cbe047903fa065f6550de4d6..83b7294010cd59d5438b6020868c699c01105595 100755 (executable)
@@ -755,6 +755,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 &&
diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh
new file mode 100755 (executable)
index 0000000..eb45afb
--- /dev/null
@@ -0,0 +1,61 @@
+#!/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'
+
+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 f6a6f839a18de4c3775ea965f164d0d20f2bbe9b..74e6443664010196f2694304179917fdadc53c01 100755 (executable)
@@ -174,4 +174,19 @@ test_expect_success 'git grep' '
        GIT_DIR=../.. GIT_WORK_TREE=.. git grep -l changed | grep dir/tracked)
 '
 
+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 || exit 1
+               git config --unset core.worktree
+               test_must_fail git log HEAD -- /home
+       )
+'
+
 test_done
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
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..4d1c2e9
--- /dev/null
@@ -0,0 +1,107 @@
+#!/bin/sh
+
+test_description='git checkout --patch'
+
+. ./lib-patch-mode.sh
+
+test_expect_success '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 '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 'git checkout -p' '
+       (echo n; echo y) | git checkout -p &&
+       verify_saved_state bar &&
+       verify_state dir/foo head head
+'
+
+test_expect_success '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 '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 '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 '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 '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
+'
+
+# 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 '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 '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 '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 '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 'none of this moved HEAD' '
+       verify_saved_head
+'
+
+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" ]
                )
diff --git a/t/t3003-ls-files-exclude.sh b/t/t3003-ls-files-exclude.sh
new file mode 100755 (executable)
index 0000000..d5ec333
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+test_description='ls-files --exclude does not affect index files'
+. ./test-lib.sh
+
+test_expect_success 'create repo with file' '
+       echo content >file &&
+       git add file &&
+       git commit -m file &&
+       echo modification >file
+'
+
+check_output() {
+test_expect_success "ls-files output contains file ($1)" "
+       echo '$2' >expect &&
+       git ls-files --exclude-standard --$1 >output &&
+       test_cmp expect output
+"
+}
+
+check_all_output() {
+       check_output 'cached' 'file'
+       check_output 'modified' 'file'
+}
+
+check_all_output
+test_expect_success 'add file to gitignore' '
+       echo file >.gitignore
+'
+check_all_output
+
+test_expect_success 'ls-files -i lists only tracked-but-ignored files' '
+       echo content >other-file &&
+       git add other-file &&
+       echo file >expect &&
+       git ls-files -i --exclude-standard >output &&
+       test_cmp expect output
+'
+
+test_done
index 51cb4a30f571772399697bc073153b7e956853bf..8be9fb411276e3c416374a813d691ff0455a8746 100755 (executable)
@@ -39,8 +39,8 @@ 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"
+_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"
 test_output () {
     sed -e "s/ $_x40   / X     /" <current >check
     test_cmp expected check
@@ -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 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
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
new file mode 100755 (executable)
index 0000000..1e34f48
--- /dev/null
@@ -0,0 +1,150 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='Test commit notes'
+
+. ./test-lib.sh
+
+cat > fake_editor.sh << \EOF
+echo "$MSG" > "$1"
+echo "$MSG" >& 2
+EOF
+chmod a+x fake_editor.sh
+VISUAL=./fake_editor.sh
+export VISUAL
+
+test_expect_success 'cannot annotate non-existing HEAD' '
+       (MSG=3 && export MSG && test_must_fail git notes edit)
+'
+
+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 edit) &&
+       (MSG=2 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF &&
+        test_must_fail git notes show)
+'
+
+test_expect_success 'refusing to edit in refs/heads/' '
+       (MSG=1 GIT_NOTES_REF=refs/heads/bogus &&
+        export MSG GIT_NOTES_REF &&
+        test_must_fail git notes edit)
+'
+
+test_expect_success 'refusing to edit 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 'create notes' '
+       git config core.notesRef refs/notes/commits &&
+       MSG=b1 git notes edit &&
+       test ! -f .git/new-notes &&
+       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 edit
+'
+
+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 -m and -F notes (setup)' '
+       : > a4 &&
+       git add a4 &&
+       test_tick &&
+       git commit -m 4th &&
+       echo "xyzzy" > note5 &&
+       git notes edit -m spam -F note5 -m "foo
+bar
+baz"
+'
+
+whitespace="    "
+cat > expect-m-and-F << EOF
+commit 15023535574ded8b1a89052b32673f84cf9582b8
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:16:13 2005 -0700
+
+    4th
+
+Notes:
+    spam
+$whitespace
+    xyzzy
+$whitespace
+    foo
+    bar
+    baz
+EOF
+
+printf "\n" >> expect-m-and-F
+cat expect-multiline >> expect-m-and-F
+
+test_expect_success 'show -m and -F notes' '
+       git log -3 > output &&
+       test_cmp expect-m-and-F output
+'
+
+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..ee84fc4
--- /dev/null
@@ -0,0 +1,118 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='Test commit notes index (expensive!)'
+
+. ./test-lib.sh
+
+test -z "$GIT_NOTES_TIMING_TESTS" && {
+       say Skipping timing tests
+       test_done
+       exit
+}
+
+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 sh ../time_notes $mode $1
+       done
+}
+
+for count in 10 100 1000 10000; do
+
+       mkdir $count
+       (cd $count;
+
+       test_expect_success "setup $count" "create_repo $count"
+
+       test_expect_success 'notes work' "test_notes $count"
+
+       test_expect_success 'notes timing' "time_notes 100"
+       )
+done
+
+test_done
diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh
new file mode 100755 (executable)
index 0000000..edc4bc8
--- /dev/null
@@ -0,0 +1,188 @@
+#!/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 4/36-fanout' 'test_sha1_based "s|^....|&/|"'
+test_expect_success 'verify notes in 4/36-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_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 4/36-fanout and 2/38-fanout' 'test_same_notes "s|^..|&/|" "s|^....|&/|"'
+test_expect_success 'verify same notes in 4/36-fanout and 2/38-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 4/36-fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^....|&/|"'
+test_expect_success 'verify same notes in 4/36-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 4/36-fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" "s|^....|&/|"'
+test_expect_success 'verify notes in 4/36-fanout concatenated with 2/38-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 4/36-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^....|&/|"'
+test_expect_success 'verify notes in 4/36-fanout concatenated with 2/2/36-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..256687f
--- /dev/null
@@ -0,0 +1,172 @@
+#!/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
+
+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
+
+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
+'
+
+test_done
index c5c29ccc4f372723fc77c1af0d7cb7879b92729f..4e6a44b623c456dc85f9daa6c4b5b1f0789c93c5 100755 (executable)
@@ -3,9 +3,10 @@
 # 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
 
@@ -133,4 +134,25 @@ test_expect_success 'rebase -q is quiet' '
      test ! -s output.out
 '
 
+q_to_cr () {
+       tr Q '\015'
+}
+
+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_done
index a973628e8e3767ae017a6b18742bf4b66a68cdaa..3a37793c0ddbdab8820dba033521d5e113bc33ac 100755 (executable)
@@ -10,7 +10,7 @@ that the result still makes sense.
 '
 . ./test-lib.sh
 
-. ../lib-rebase.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
 
 set_fake_editor
 
@@ -470,4 +470,18 @@ 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_done
index e6c832780fbe00afe5c513f0f5e4c87d7020e2a4..8f785e7957519eaa3dd1ef946c905054c4fa0e6c 100755 (executable)
@@ -32,14 +32,14 @@ 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 &&
@@ -71,7 +71,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 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
 
index 80019ee07212a553f97d5fd351f2ee9cfab93d9e..ee0a6cccfda832c62233d358f4933e9cd3ab2c8d 100755 (executable)
@@ -10,7 +10,7 @@ aren'"'"'t on top of $ONTO, even if they are on top of $UPSTREAM.
 '
 . ./test-lib.sh
 
-. ../lib-rebase.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
 
 # Set up branches like this:
 # A1---B1---E1---F1---G1
index fd2a55a5c23247c010a02513af73a93641cfb067..b6eba6a83904a00724b7b550a9bc3b1b35825bee 100755 (executable)
@@ -138,6 +138,20 @@ test_expect_success 'real edit works' '
        test_cmp expected output
 '
 
+test_expect_success '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
+
 if test "$(git config --bool core.filemode)" = false
 then
        say 'skipping filemode tests (filesystem does not properly support modes)'
@@ -163,6 +177,17 @@ test_expect_success FILEMODE 'stage mode but not hunk' '
        git diff          file | grep "+content"
 '
 
+
+test_expect_success 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 'setup again' '
@@ -203,4 +228,41 @@ test_expect_success 'add first line works' '
        test_cmp expected diff
 '
 
+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 '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
+'
+
+cat >expected <<EOF
+diff --git a/empty b/empty
+deleted file mode 100644
+index e69de29..0000000
+EOF
+
+test_expect_success '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
index 7a3fb679571a9fc79135f8b9495462f723b663af..5514f74b30aa74fe2bf214e90f9ad8f4da2876e4 100755 (executable)
@@ -200,4 +200,23 @@ test_expect_success 'drop -q is quiet' '
        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_done
diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh
new file mode 100755 (executable)
index 0000000..f37e3bc
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+test_description='git checkout --patch'
+. ./lib-patch-mode.sh
+
+test_expect_success '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 '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 '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 '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 'none of this moved HEAD' '
+       verify_saved_head
+'
+
+test_done
index 8b33321f8c44dc7f8a08fa144bca4f10444ceaa1..8e3694ed5b80a87602d6533f0aa28307bd7b3d1b 100755 (executable)
@@ -207,6 +207,7 @@ log --root --cc --patch-with-stat --summary master
 log -SF master
 log -SF -p master
 log --decorate --all
+log --decorate=full --all
 
 rev-list --parents HEAD
 rev-list --children HEAD
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 954210ea907cb04f6113d82194c0753019270d8a..fd7c3e64396b4ea57c3b03d2e120580205263462 100644 (file)
@@ -1,5 +1,5 @@
 $ git log --decorate --all
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (master)
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, master)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
index 922a8941ed4720e2ad6dcd500a3759187aace969..3bc1cccf8869aef26e175e207dc2923d3ddb1e65 100755 (executable)
@@ -455,6 +455,27 @@ test_expect_success 'format-patch respects -U' '
 
 '
 
+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
+
+'
+
 test_expect_success 'format-patch from a subdirectory (1)' '
        filename=$(
                rm -rf sub &&
@@ -493,13 +514,12 @@ 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"
@@ -516,4 +536,25 @@ test_expect_success 'format-patch --signoff' '
        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_done
index 6d13da30dad5a78fb17a01e86ef33072ea9e6250..8dd147d78f6ee6a4be4b1af235ed35af19b273df 100755 (executable)
@@ -362,10 +362,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' '
index 84a1fe31151c2af38554eaca8f03e2c1e2e7848f..3a3663fbcb4795a9e3755fe831c89a7e312f659e 100755 (executable)
@@ -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 "${blue_grep}" output | wc -l) &&
+       test $cnt = 2
+'
+
 test_done
index 4ea42e00dad9bf15e221e6eac5d91748cb0a07e7..a7602cf923d95a51643753ce44fa65cf406aba9c 100755 (executable)
@@ -166,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 b61e5169f4e9e8d9f87b9ea16e71dfcf1fb9f340..5ade44c043ca6577b2e331b152515359128dbd32 100755 (executable)
@@ -66,4 +66,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 4508effcaacd3dbc8fadb13c7be0d631f4c946f1..1c21276c55400c594f71bc01eba17b0d3820b420 100755 (executable)
@@ -8,6 +8,7 @@ test_expect_success setup '
 
        git config diff.color.old red
        git config diff.color.new green
+       git config diff.color.func magenta
 
 '
 
@@ -16,6 +17,7 @@ decrypt_color () {
                -e 's/.\[1m/<WHITE>/g' \
                -e 's/.\[31m/<RED>/g' \
                -e 's/.\[32m/<GREEN>/g' \
+               -e 's/.\[35m/<MAGENTA>/g' \
                -e 's/.\[36m/<BROWN>/g' \
                -e 's/.\[m/<RESET>/g'
 }
@@ -49,7 +51,7 @@ cat > expect <<\EOF
 <WHITE>+++ b/post<RESET>
 <BROWN>@@ -1,3 +1,7 @@<RESET>
 <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
-<RESET>
+
 a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
@@ -63,6 +65,26 @@ test_expect_success 'word diff with runs of whitespace' '
 
 '
 
+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 +1 @@<RESET>
+<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
+<BROWN>@@ -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>
@@ -70,7 +92,7 @@ cat > expect <<\EOF
 <WHITE>+++ b/post<RESET>
 <BROWN>@@ -1,3 +1,7 @@<RESET>
 h(4),<GREEN>hh<RESET>[44]
-<RESET>
+
 a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
@@ -106,7 +128,7 @@ cat > expect <<\EOF
 <WHITE>+++ b/post<RESET>
 <BROWN>@@ -1,3 +1,7 @@<RESET>
 h(4)<GREEN>,hh[44]<RESET>
-<RESET>
+
 a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
@@ -148,7 +170,7 @@ cat > expect <<\EOF
 <WHITE>+++ b/post<RESET>
 <BROWN>@@ -1,3 +1,7 @@<RESET>
 h(4),<GREEN>hh[44<RESET>]
-<RESET>
+
 a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
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/t4041-diff-submodule.sh b/t/t4041-diff-submodule.sh
new file mode 100755 (executable)
index 0000000..5bb4fed
--- /dev/null
@@ -0,0 +1,260 @@
+#!/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 &&
+cd sm1 &&
+git reset --hard HEAD~2 >/dev/null &&
+head3=$(git rev-parse --verify HEAD | cut -c1-7) &&
+cd ..
+
+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 &&
+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_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
index fac2093d7f20d502f41f895d2ccdfccd5e639e2b..ca26397590f3d79455c41894203fbff7bb6a9c3c 100755 (executable)
@@ -170,4 +170,95 @@ test_expect_success 'trailing whitespace & no newline at the end of file' '
        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_done
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
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
index a12bf846231d18b2a815151d1a0e1ab3d5f491d1..8296605234ddc82697a51de0a09b703635b4c9f4 100755 (executable)
@@ -77,6 +77,12 @@ test_expect_success setup '
        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 &&
        sed -n -e "3,\$p" msg >file &&
        git add file &&
        test_tick &&
@@ -108,6 +114,15 @@ test_expect_success 'am applies patch correctly' '
        test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
 '
 
+test_expect_success 'am applies patch e-mail not in a mbox' '
+       git checkout first &&
+       git am patch1.eml &&
+       ! test -d .git/rebase-apply &&
+       test -z "$(git diff 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"
index 405b97119175a1c0fa75a9db30c6b1ab076cc44e..a01e55bf6b96246c33332e5112bcb3d6583402ac 100755 (executable)
@@ -52,4 +52,32 @@ GIT_DIR=non-existing git shortlog -w < log > out
 
 test_expect_success 'shortlog from non-git directory' 'test_cmp expect out'
 
+iconvfromutf8toiso88591() {
+       printf "%s" "$*" | iconv -f UTF-8 -t ISO8859-1
+}
+
+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 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 48e0088b4780e133a450aea01092d9238e7122e9..779a5adf554d77464f2a326a2eb919e224993746 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
@@ -320,11 +341,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
index 5f84b18fa5f0f199905bc7fc045672b70035a708..0037f63d91a7c11f6599a99e76840a0cdbf4c779 100755 (executable)
@@ -230,4 +230,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 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..9577238
--- /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 c3074ac5736fe266e901b4da3b060208cd71406e..de1031241d99a66db0653875ea163785691d12d6 100644 (file)
@@ -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
+
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..3c6687abecf6b74839315c5e3dc09f361154a68c 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,18 @@ test_expect_success 'gc --prune=<date>' '
 
 '
 
+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 a8c2ca2a78dd54f69230cb443eff1d6e82336f0a..18376d66081759c6a4959a2d8bc47ca441364660 100755 (executable)
@@ -139,6 +139,36 @@ test_expect_success 'fsck in shallow repo' '
        )
 '
 
+test_expect_success 'simple fetch in shallow repo' '
+       (
+               cd shallow &&
+               git fetch
+       )
+'
+
+test_expect_success 'no changes expected' '
+       (
+               cd shallow &&
+               git count-objects -v
+       ) > count.shallow.2 &&
+       cmp count.shallow count.shallow.2
+'
+
+test_expect_success 'fetch same depth in shallow repo' '
+       (
+               cd shallow &&
+               git fetch --depth=2
+       )
+'
+
+test_expect_success 'no changes expected' '
+       (
+               cd shallow &&
+               git count-objects -v
+       ) > count.shallow.3 &&
+       cmp count.shallow count.shallow.3
+'
+
 test_expect_success 'add two more' '
        add B66 $B65 &&
        add B67 $B66
@@ -201,4 +231,21 @@ test_expect_success 'pull in shallow repo with missing merge base' '
        )
 '
 
+test_expect_success 'additional simple shallow deepenings' '
+       (
+               cd shallow &&
+               git fetch --depth=8 &&
+               git fetch --depth=10 &&
+               git fetch --depth=11
+       )
+'
+
+test_expect_success 'clone shallow object count' '
+       (
+               cd shallow &&
+               git count-objects -v
+       ) > count.shallow &&
+       grep "^count: 52" count.shallow
+'
+
 test_done
index 852ccb5d7d649a71eeab25eadce6940b9c2a6f63..fd166d9de356dafb000504506285a6f77fcc0a37 100755 (executable)
@@ -158,7 +158,7 @@ cat > test/expect << EOF
     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
 
@@ -365,6 +365,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
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..169af1edde557f054ea76b8de681c6dd74e436f2 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 &&
@@ -146,6 +151,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 +162,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 +185,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' '
@@ -336,4 +341,15 @@ test_expect_success 'fetch into the current branch with --update-head-ok' '
 
 '
 
+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
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 6529d97dc007a438f5507cb427a17106113a168f..516127b5390bafbd725564e91fb24ead224cf3a7 100755 (executable)
@@ -123,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 &&
 
@@ -163,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 &&
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 c5a2e66a09b9a871fb97142a58de51097a685ae0..dd2ee842e020c23b49ed4e2070c4e31cdb7ac055 100755 (executable)
@@ -117,6 +117,19 @@ test_expect_success '--rebase with rebased default upstream' '
 
 '
 
+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 &&
@@ -136,4 +149,15 @@ 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_done
index f5102b902a4fa0505fee13aa18d38a211cdb42cb..a696b8791b7caa44ae2bd16d6970a791f3a28d3d 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 &&
+       grep "unable to read" output.err &&
        grep "pack-objects died" output.err
 '
 
@@ -50,10 +51,22 @@ test_expect_success 'fsck fails' '
 '
 test_expect_success 'upload-pack fails due to error in rev-list' '
 
+       ! echo "0032want $(git rev-parse HEAD)
+0034shallow $(git rev-parse HEAD^)00000009done
+0000" | git upload-pack . > /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 fails due to error in pack-objects enumeration' '
+
        ! echo "0032want $(git rev-parse HEAD)
 00000009done
 0000" | git upload-pack . > /dev/null 2> output.err &&
-       grep "waitpid (async) failed" 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 f4a2cf6c171a6356804f61ac6f5707dd587515e5..bb18f8bfc4c9cd7e602633ce4abf5a3cf9ae0e4a 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"
        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,11 +57,15 @@ 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))
@@ -71,7 +75,7 @@ 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/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
@@ -80,14 +84,14 @@ test_expect_success 'http-push fetches unpacked objects' '
         git remote rm origin &&
         git reflog expire --expire=0 --all &&
         git prune &&
-        git push -f -v $HTTPD_URL/test_repo_unpacked.git master)
+        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/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 &&
@@ -100,7 +104,7 @@ test_expect_success 'http-push fetches packed objects' '
         git remote rm origin &&
         git reflog expire --expire=0 --all &&
         git prune &&
-        git push -f -v $HTTPD_URL/test_repo_packed.git master)
+        git push -f -v $HTTPD_URL/dumb/test_repo_packed.git master)
 '
 
 test_expect_success 'create and delete remote branch' '
@@ -111,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
 '
 
diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh
new file mode 100755 (executable)
index 0000000..2a58d0c
--- /dev/null
@@ -0,0 +1,92 @@
+#!/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
+       say '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"
+'
+
+test_expect_success 'clone remote repository' '
+       cd "$ROOT_PATH" &&
+       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
+'
+
+stop_httpd
+test_done
index 0e69324652026bdeeb319c36782fe1130b0510a1..8cfce969bcdac6e2091e635dad9c58ca616e5c3b 100755 (executable)
@@ -1,6 +1,6 @@
 #!/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
@@ -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
 '
 
@@ -58,7 +58,13 @@ test_expect_success 'fetch packed objects' '
        cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
        git --bare repack &&
        git --bare prune-packed &&
-       git clone $HTTPD_URL/repo_pack.git
+       git clone $HTTPD_URL/dumb/repo_pack.git
+'
+
+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
diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh
new file mode 100755 (executable)
index 0000000..c0505ec
--- /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
+       say '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-response
+> 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.sh b/t/t5560-http-backend.sh
new file mode 100755 (executable)
index 0000000..ed034bc
--- /dev/null
@@ -0,0 +1,260 @@
+#!/bin/sh
+
+test_description='test git-http-backend'
+. ./test-lib.sh
+
+if test -n "$NO_CURL"; then
+       say 'skipping test, git built without http support'
+       test_done
+fi
+
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5560'}
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+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
+}
+
+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_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"
+}
+
+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"
+'
+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"
+'
+
+run_backend() {
+       REQUEST_METHOD=GET \
+       GIT_PROJECT_ROOT="$HTTPD_DOCUMENT_ROOT_PATH" \
+       PATH_INFO="$2" \
+       git http-backend >act.out 2>act.err
+}
+
+path_info() {
+       if test $1 = 0; then
+               run_backend "$2"
+       else
+               test_must_fail run_backend "$2" &&
+               echo "fatal: '$2': aliased" >exp.err &&
+               test_cmp exp.err act.err
+       fi
+}
+
+test_expect_success 'http-backend blocks bad PATH_INFO' '
+       config http.getanyfile true &&
+
+       run_backend 0 /repo.git/HEAD &&
+
+       run_backend 1 /repo.git/../HEAD &&
+       run_backend 1 /../etc/passwd &&
+       run_backend 1 ../etc/passwd &&
+       run_backend 1 /etc//passwd &&
+       run_backend 1 /etc/./passwd &&
+       run_backend 1 /etc/.../passwd &&
+       run_backend 1 //domain/data.txt
+'
+
+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
+
+###  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
index 2335d8bc850b4b0010fcb9ac64a25d0c205e8a42..214756731baf199e6a50f9ab2380a8b4bfc0fb18 100755 (executable)
@@ -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
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
index 59d1f6283bec5cf7740d988dfd090c1463389c71..571931588eda5799efbf2b0789abd10b8770a0a7 100755 (executable)
@@ -162,4 +162,44 @@ 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 '"%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 '%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_done
index 04e4b7c5c2aa4f7c6922ebc5c9236962ab5d175c..0144d9e858d2d8bf1720331c52f1809ed36e81b0 100755 (executable)
@@ -110,6 +110,18 @@ test_expect_success 'compute merge-base (all)' \
 
 # 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_expect_success 'merge-base for octopus-step (setup)' '
        test_tick && git commit --allow-empty -m root && git tag MMR &&
@@ -137,6 +149,12 @@ test_expect_success 'merge-base A B C' '
        test "$MM1" = "$MB"
 '
 
+test_expect_success 'merge-base A B C using show-branch' '
+       MB=$(git show-branch --merge-base MMA MMB MMC) &&
+       MMR=$(git rev-parse --verify MMR) &&
+       test "$MMR" = "$MB"
+'
+
 test_expect_success 'criss-cross merge-base for octopus-step (setup)' '
        git reset --hard MMR &&
        test_tick && git commit --allow-empty -m 1 && git tag CC1 &&
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
index a19d49de28c457d1a5726400d25c1f731506a05f..e71c687f2b9473842684fae1a1b4b013c721497f 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_failure '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 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 1315bab595c03f16b89060434588b505faf1c46b..def397c53a76dead449710eaca6333c2e1fb36aa 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" &&
@@ -534,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' '
@@ -552,7 +552,7 @@ 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' '
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..5b96fb0
--- /dev/null
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+test_description='merging when a directory was replaced with a symlink'
+. ./test-lib.sh
+
+if ! test_have_prereq SYMLINKS
+then
+       say 'Symbolic links not supported, skipping tests.'
+       test_done
+fi
+
+test_expect_success '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 '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 '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 '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 '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_failure '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 '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_failure 'merge should not have 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_failure 'merge should not have 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_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/t6050-replace.sh b/t/t6050-replace.sh
new file mode 100755 (executable)
index 0000000..203ffdb
--- /dev/null
@@ -0,0 +1,224 @@
+#!/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 &&
+     cd ..
+'
+
+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 &&
+     cd ..
+'
+
+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" &&
+     cd ..
+'
+
+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"
+     cd ..
+'
+
+test_expect_success 'bisect and replacements' '
+     git bisect start $HASH7 $HASH1 &&
+     test "$S" = "$(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_done
index 8c7e081c53eec31d38844d8efb9b942893107b09..065deadc29eb3838f391ce758c4b188249dc87f9 100755 (executable)
@@ -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) &&
@@ -90,12 +92,18 @@ check_describe A-* HEAD^
 check_describe D-* HEAD^^
 check_describe A-* HEAD^^2
 check_describe B HEAD^^2^
+check_describe D-* 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 +131,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 b13aa7e89ad51566979ab674baa2ac36c7e33da6..abd14bf819f5c60fc1b9dc758c04974bd24b10a4 100755 (executable)
@@ -14,6 +14,7 @@ int main(int argc, const char **argv)
 {
        printf("Hello world.\n");
        return 0;
+       /* char ?? */
 }
 EOF
 
@@ -25,13 +26,17 @@ test_expect_success setup '
                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 &&
-       git add file w x y z t/t hello.c &&
+       echo vvv >t/v &&
+       mkdir t/a &&
+       echo vvv >t/a/v &&
+       git add . &&
        test_tick &&
        git commit -m initial
 '
@@ -132,6 +137,51 @@ do
                ! 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
@@ -164,6 +214,77 @@ test_expect_success 'grep -e A --and --not -e B' '
        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
 --
@@ -279,4 +400,30 @@ test_expect_success 'grep -p -B5' '
        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_done
index 329c851685b1c663ee88d45a5d21d452a293fa8e..9503875e97e09ad94283e63ae298551453e5787a 100755 (executable)
@@ -288,4 +288,22 @@ test_expect_success 'Prune empty commits' '
        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_done
index b647957d75fa0d0ce4d88c7c3c7243f31af38b4a..5257f4d261c2060b881d2649034232f76f4ed9b7 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,19 +26,18 @@ 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 &&
+       git show -s --format=%s > actual &&
        diff 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
@@ -68,7 +91,7 @@ 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
diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh
new file mode 100755 (executable)
index 0000000..1044aa6
--- /dev/null
@@ -0,0 +1,58 @@
+#!/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 reset HEAD <file>..." to unstage)
+#   (use "git add <file>..." 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 status > ../actual
+       ) &&
+       test_cmp expect actual
+'
+
+test_done
index e637c7d4dbdce337b4d005719c4aa86d447a70d9..e85ff02c3e636567a80586e92fdba1c5382fc995 100755 (executable)
@@ -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..68041df5f49e046c2b7ccd1f3a1c71071498da39 100755 (executable)
@@ -11,16 +11,42 @@ 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 'mixed reset is ok' '
+       (cd .git && git reset)
+'
+
+test_expect_success 'soft reset is ok' '
+       (cd .git && git reset --soft)
+'
+
 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 '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..c1f4fc3
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='git reset --patch'
+. ./lib-patch-mode.sh
+
+test_expect_success '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 '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 'git reset -p' '
+       (echo n; echo y) | git reset -p &&
+       verify_state dir/foo work head &&
+       verify_saved_state bar
+'
+
+test_expect_success '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 '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 '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 '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 'none of this moved HEAD' '
+       verify_saved_head
+'
+
+
+test_done
index 929d5d4d3b9d55f570cef1617a0716b17265c988..118c6ebb182b5cd4700e533d6a951b31529149af 100755 (executable)
@@ -380,4 +380,43 @@ test_expect_success 'removal failure' '
 '
 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_done
index 0f2ccc6cf0123951d9bdbb880931868f29de5b4e..a0cc99ab9f5b262851a1075193f5529b5582fd0a 100755 (executable)
@@ -306,4 +306,20 @@ 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" &&
+       cd .. &&
+       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..6cc16c39fe75ee9dab942fc5ae376827d3b7ea44 100755 (executable)
@@ -56,6 +56,15 @@ 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 &&
@@ -114,6 +123,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)' "
@@ -205,4 +223,8 @@ 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_done
index 2d33d9efec3f2f601305e736e31368621acd0584..8e2449d24409bab14558f83617f651c3f7255627 100755 (executable)
@@ -14,8 +14,8 @@ submodule and "git submodule update --rebase/--merge" does not detach the HEAD.
 
 compare_head()
 {
-    sha_master=`git-rev-list --max-count=1 master`
-    sha_head=`git-rev-list --max-count=1 HEAD`
+    sha_master=`git rev-list --max-count=1 master`
+    sha_head=`git rev-list --max-count=1 HEAD`
 
     test "$sha_master" = "$sha_head"
 }
diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh
new file mode 100755 (executable)
index 0000000..2a52775
--- /dev/null
@@ -0,0 +1,237 @@
+#!/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)
+
+cat > expect <<EOF
+Entering 'sub1'
+foo1-sub1-$sub1sha1
+Entering 'sub3'
+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 \$name-\$path-\$sha1" > ../actual
+       ) &&
+       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
index e2ef53228ecc6162afdcce78109d426e154b7caa..a603f6d21a61df197256ea91bfb38c1f4e45c5e8 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" \
@@ -141,10 +141,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
@@ -247,6 +247,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 &&
index 56cd866019dcc871e5dea684f1d879fcf2c1b884..fe94552296bb98ef78dbc51e8b1f7d4a665cf0a4 100755 (executable)
@@ -258,4 +258,13 @@ 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
+
+'
+
 test_done
diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh
new file mode 100755 (executable)
index 0000000..d52c060
--- /dev/null
@@ -0,0 +1,114 @@
+#!/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
+'
+
+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..57f6d2bae7c63f13ee18e11737dbdcc0c080ab10 100755 (executable)
@@ -243,6 +243,16 @@ test_expect_success 'merge c0 with c1' '
 
 test_debug 'gitk --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_expect_success 'merge c1 with c2' '
        git reset --hard c1 &&
        test_tick &&
@@ -263,6 +273,14 @@ test_expect_success 'merge c1 with c2 and c3' '
 
 test_debug 'gitk --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 &&
        git merge --no-commit c1 &&
@@ -303,6 +321,17 @@ test_expect_success 'merge c0 with c1 (squash)' '
 
 test_debug 'gitk --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 &&
+       verify_diff squash.1 .git/SQUASH_MSG "[OOPS] bad squash message"
+'
+
+test_debug 'gitk --all'
+
 test_expect_success 'merge c1 with c2 (squash)' '
        git reset --hard c1 &&
        git merge --squash c2 &&
@@ -314,6 +343,13 @@ test_expect_success 'merge c1 with c2 (squash)' '
 
 test_debug 'gitk --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 'gitk --all'
+
 test_expect_success 'merge c1 with c2 and c3 (squash)' '
        git reset --hard c1 &&
        git merge --squash c2 c3 &&
@@ -432,6 +468,11 @@ test_expect_success 'combining --squash and --no-ff is refused' '
        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" &&
index de977c5e2f0b270c57bbea9daaf8e22eefdf8560..269cfdf267443f67ef8c6c921610bc856f4e506b 100755 (executable)
@@ -22,15 +22,12 @@ test_expect_success 'setup' '
        git tag c2
 '
 
-cat >expected <<\EOF
-custom message
 
-Merge commit 'c2'
-EOF
 test_expect_success 'merge c2 with a custom message' '
        git reset --hard c1 &&
+       echo >expected "custom message" &&
        git merge -m "custom message" c2 &&
-       git cat-file commit HEAD | sed -e "1,/^$/d" > actual &&
+       git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
        test_cmp expected actual
 '
 
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
index ebdccf9a1eb9f9bb4918fc5adee8bdb8fd682608..fff6a6d0eab4b27d5bc7ff4636913cb10296ab7b 100755 (executable)
@@ -136,7 +136,7 @@ test_expect_success 'GIT_DIFFTOOL_PROMPT variable' '
        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
index 13c25f1d528ca1ec90575e42e0393accff5d8f35..ad834f200ac0d6ecf532374311d896557a9b941d 100755 (executable)
@@ -144,4 +144,17 @@ test_expect_success 'blame path that used to be a directory' '
        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_done
diff --git a/t/t8005/iso8859-5.txt b/t/t8005/iso8859-5.txt
deleted file mode 100644 (file)
index 2e4b80c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-ISO8859_5_NAME="¸ÒÐÝ ¿ÕâàÞÒØç ÁØÔÞàÞÒ"
-ISO8859_5_MSG="ÂÕáâÞÒÞÕ áÞÞÑéÕÝØÕ"
index fb606a9f05bd6964c588f1ba9e5422e757d12387..752adaac850862caf50e3573338b9cb742781147 100755 (executable)
@@ -95,6 +95,40 @@ test_expect_success \
     'Verify commandline' \
     'test_cmp expected commandline1'
 
+test_expect_success '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
+'
+
+cat >expected <<\EOF
+!patch@example.com!
+!-i!
+!nobody@example.com!
+!author@example.com!
+!one@example.com!
+!two@example.com!
+EOF
+test_expect_success \
+    'Verify commandline' \
+    'test_cmp expected commandline1'
+
+test_expect_success '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
+'
+
+cat >expected <<\EOF
+!nobody@example.com!
+!-i!
+!nobody@example.com!
+!author@example.com!
+!one@example.com!
+!two@example.com!
+EOF
+test_expect_success \
+    'Verify commandline' \
+    'test_cmp expected commandline1'
+
 cat >expected-show-all-headers <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -103,10 +137,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
@@ -164,7 +206,7 @@ test_expect_success 'cccmd works' '
                --smtp-server="$(pwd)/fake.sendmail" \
                cccmd.patch \
                &&
-       grep ^Cc:.*cccmd@example.com msgtxt1
+       grep "^ cccmd@example.com" msgtxt1
 '
 
 z8=zzzzzzzz
@@ -278,10 +320,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
@@ -318,10 +367,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
@@ -344,10 +398,17 @@ cat >expected-suppress-cccmd <<\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
@@ -392,10 +453,17 @@ cat >expected-suppress-body <<\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>,<cc-cmd@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-cmd@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
@@ -416,10 +484,15 @@ cat >expected-suppress-body-cccmd <<\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
@@ -440,10 +513,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
@@ -466,10 +544,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
@@ -489,10 +574,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
@@ -605,7 +693,7 @@ test_expect_success 'utf8 Cc is rfc2047 encoded' '
        --to=nobody@example.com \
        --smtp-server="$(pwd)/fake.sendmail" \
        outdir/*.patch &&
-       grep "^Cc:" msgtxt1 |
+       grep "^ " msgtxt1 |
        grep "=?UTF-8?q?=C3=A0=C3=A9=C3=AC=C3=B6=C3=BA?= <utf8@example.com>"
 '
 
@@ -715,4 +803,53 @@ test_expect_success 'threading but no chain-reply-to' '
        grep "In-Reply-To: " stdout
 '
 
+test_expect_success '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 '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 '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 '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 '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_done
index 9da4178c94f4bde62f69f32701b16ab3d04524e0..929499e996bc33d96ebab0b01ebcac8240e689b7 100755 (executable)
@@ -142,7 +142,9 @@ test_expect_success 'test show-ignore' "
        touch deeply/nested/directory/.keep &&
        svn_cmd add deeply &&
        svn_cmd up &&
-       svn_cmd propset -R svn:ignore 'no-such-file*' .
+       svn_cmd propset -R svn:ignore '
+no-such-file*
+' .
        svn_cmd commit -m 'propset svn:ignore'
        cd .. &&
        git svn show-ignore > show-ignore.got &&
@@ -171,6 +173,7 @@ test_expect_success 'test create-ignore' "
        "
 
 cat >prop.expect <<\EOF
+
 no-such-file*
 
 EOF
index 78610b61e63596dad7e86a19a7e39deda3b19222..bbfd7f4793ab41118e7060bf7c0512309f81a546 100755 (executable)
@@ -172,11 +172,11 @@ test_expect_success "follow-parent is atomic" '
        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 &&
index 3a9e07768d8e6bebd7a4c4156ace6c5746d7d440..901b8e09fbc6660456311c04b4b88c20e108c313 100755 (executable)
@@ -16,9 +16,7 @@ test_expect_success 'setup old-looking metadata' '
                cd .. &&
        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 +54,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 +85,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 9be7aefaeea0af4d988ca656b3df0008f04a58d3..767799e7a70b91ef6f4e3f4007529f1cb0fd919c 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
index f159ab689bd5f7dfba1b27adb9dc68d8b65b67fe..9d9ebd533cbcbcfc1e23be51bad1e63f4f08c53b 100755 (executable)
@@ -10,6 +10,10 @@ test_expect_success 'setup svnrepo' '
        mkdir project project/trunk project/branches project/tags &&
        echo foo > project/trunk/foo &&
        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
 '
@@ -21,6 +25,54 @@ test_expect_success 'test clone with percent escapes' '
        cd ..
 '
 
+# 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
 
 test_done
index f5abdb3c7fd24033f1538db51c957345ab86e341..134411e0a56142930a418ca15bd0902837c7bdc1 100755 (executable)
@@ -91,4 +91,27 @@ 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)" &&
+               HOME="`pwd`" &&
+               export HOME &&
+               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 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 a4b00f2a3fcecab62aaabeedd0821e38f179019a..83cc5fc9d1a381ecd006fa0a203f22dea0825a82 100755 (executable)
@@ -66,4 +66,18 @@ test_expect_success 'authors-file overrode authors-prog' '
        )
        '
 
+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
index f2ba2d1da34c1f6bf1790acd321ab5aa8aaeeca7..99f69c6a0b8c7d3eea5211db333d0be41f9f5fa4 100755 (executable)
@@ -28,26 +28,26 @@ 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/git-svn/unhandled.log tmp
+        cp .git/svn/refs/remotes/git-svn/unhandled.log tmp
        '
 
-test_expect_success 'create leftover index' '> .git/svn/git-svn/index'
+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/git-svn/index'
+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/git-svn/unhandled.log.gz
+                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/git-svn/unhandled.log tmp/unhandled.log
+        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..5358142
--- /dev/null
@@ -0,0 +1,24 @@
+#!/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 \
+         -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..359eeaa
--- /dev/null
@@ -0,0 +1,41 @@
+#!/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 \
+         -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 '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..d917717
--- /dev/null
@@ -0,0 +1,161 @@
+#!/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
+svn add trunk branches
+i=$(commit $i "Setup trunk and branches")
+
+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 ..
+
+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..9543e31
--- /dev/null
@@ -0,0 +1,1625 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 64142547-0943-4db2-836a-d1e1eb2f9924
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-12-19T16:17:51.232640Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 128
+Content-length: 128
+
+K 7
+svn:log
+V 29
+(r1) Setup trunk and branches
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:51.831965Z
+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: 112
+Content-length: 112
+
+K 7
+svn:log
+V 13
+(r2) ancestor
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:52.300075Z
+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: 120
+Content-length: 120
+
+K 7
+svn:log
+V 21
+(r3) make left branch
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:52.768800Z
+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: 121
+Content-length: 121
+
+K 7
+svn:log
+V 22
+(r4) make right branch
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:53.177879Z
+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: 117
+Content-length: 117
+
+K 7
+svn:log
+V 18
+(r5) left update 1
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:53.604691Z
+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: 118
+Content-length: 118
+
+K 7
+svn:log
+V 19
+(r6) right update 1
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:54.063555Z
+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: 117
+Content-length: 117
+
+K 7
+svn:log
+V 18
+(r7) left update 2
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:54.523904Z
+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: 117
+Content-length: 117
+
+K 7
+svn:log
+V 18
+(r8) left update 3
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:54.975970Z
+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: 124
+Content-length: 124
+
+K 7
+svn:log
+V 25
+(r9) make left sub-branch
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:55.459904Z
+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: 129
+Content-length: 129
+
+K 7
+svn:log
+V 30
+(r10) left sub-branch update 1
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:55.862113Z
+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: 126
+Content-length: 126
+
+K 7
+svn:log
+V 27
+(r11) Merge left to trunk 1
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:56.413416Z
+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: 118
+Content-length: 118
+
+K 7
+svn:log
+V 19
+(r12) left update 4
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:56.831014Z
+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: 119
+Content-length: 119
+
+K 7
+svn:log
+V 20
+(r13) right update 2
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:57.341143Z
+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: 141
+Content-length: 141
+
+K 7
+svn:log
+V 42
+(r14) Cherry-pick right 2 commits to trunk
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:57.841851Z
+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: 127
+Content-length: 127
+
+K 7
+svn:log
+V 28
+(r15) Merge right to trunk 1
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:58.368520Z
+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: 119
+Content-length: 119
+
+K 7
+svn:log
+V 20
+(r16) right update 3
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:58.779056Z
+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: 119
+Content-length: 119
+
+K 7
+svn:log
+V 20
+(r17) trunk update 1
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:59.221851Z
+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: 135
+Content-length: 135
+
+K 7
+svn:log
+V 36
+(r18) Merge right to left sub-branch
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:59.781666Z
+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: 129
+Content-length: 129
+
+K 7
+svn:log
+V 30
+(r19) left sub-branch update 2
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:18:00.200531Z
+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: 118
+Content-length: 118
+
+K 7
+svn:log
+V 19
+(r20) left update 5
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:18:00.659636Z
+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: 147
+Content-length: 147
+
+K 7
+svn:log
+V 48
+(r21) Cherry-pick left sub-branch commit to left
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:18:01.194402Z
+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: 134
+Content-length: 134
+
+K 7
+svn:log
+V 35
+(r22) Merge left sub-branch to left
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:18:01.679218Z
+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: 126
+Content-length: 126
+
+K 7
+svn:log
+V 27
+(r23) Merge left to trunk 2
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:18:02.212349Z
+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: 131
+Content-length: 131
+
+K 7
+svn:log
+V 32
+(r24) non-merge right to trunk 2
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:18:02.672148Z
+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
+
+
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
index 821be7ce8d92f8ead1bcaa946260e8d715784612..b49815d10806a57461bb98ffd89031680f84715a 100755 (executable)
@@ -1088,4 +1088,170 @@ INPUT_END
 test_expect_success 'P: fail on blob mark in gitlink' '
     test_must_fail git fast-import <input'
 
+###
+### series Q (notes)
+###
+
+note1_data="Note for the first commit"
+note2_data="Note for the second commit"
+note3_data="Note for the third 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
+
+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 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 notes tree' \
+       'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]*   / /" >actual &&
+        test_cmp expect actual'
+
+echo "$note1_data" >expect
+test_expect_success \
+       'Q: verify 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 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 note for third commit' \
+       'git cat-file blob refs/notes/foobar:$commit3 >actual && test_cmp expect actual'
+
 test_done
index 64f947d75bc0700fc75e8c7c87d97ec4f3e62e44..c2ec3cb4bd97693ef8f8f25064297f019053e03a 100755 (executable)
@@ -20,7 +20,7 @@ then
     say 'skipping git-cvsserver tests, cvs not found'
     test_done
 fi
-perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
+"$PERL_PATH" -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
     say 'skipping git-cvsserver tests, Perl SQLite interface unavailable'
     test_done
 }
index aca40c1b1ff0f957652f75383625367d85242c14..40637d6782c3ad5ffbff99d352206b8b2ef01b43 100755 (executable)
@@ -57,7 +57,7 @@ then
     say 'skipping git-cvsserver tests, perl not available'
     test_done
 fi
-perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
+"$PERL_PATH" -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
     say 'skipping git-cvsserver tests, Perl SQLite interface unavailable'
     test_done
 }
index 627518108acfbab990fc259412e395150c1fb5b1..2fc7fdb124583f86d5be622510f29ceca1dd3e09 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)
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..0688a57
--- /dev/null
@@ -0,0 +1,117 @@
+#!/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_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'
+test_debug 'cat 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'
+test_debug 'cat 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.output'
+
+
+# ----------------------------------------------------------------------
+# 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.output'
+
+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.output'
+
+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'
+
+
+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..363345faef7b1eb209c548914b94460d9475cb13 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 test_description='git cvsimport basic tests'
-. ./test-lib.sh
+. ./lib-cvs.sh
 
 if ! test_have_prereq PERL; then
        say 'skipping git cvsimport tests, perl not available'
@@ -10,37 +10,13 @@ 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 cvsroot' '$CVS init'
 
 test_expect_success 'setup a cvs module' '
 
        mkdir "$CVSROOT/module" &&
-       cvs co -d module-cvs module &&
+       $CVS co -d module-cvs module &&
        cd module-cvs &&
        cat <<EOF >o_fortuna &&
 O Fortuna
@@ -59,13 +35,13 @@ 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 &&
+       $CVS commit -F message &&
        cd ..
 '
 
@@ -103,7 +79,7 @@ translate to English
 
 My Latin is terrible.
 EOF
-       cvs commit -F message &&
+       $CVS commit -F message &&
        cd ..
 '
 
@@ -121,8 +97,8 @@ test_expect_success 'update cvs module' '
 
        cd module-cvs &&
                echo 1 >tick &&
-               cvs add tick &&
-               cvs commit -m 1
+               $CVS add tick &&
+               $CVS commit -m 1
        cd ..
 
 '
@@ -140,7 +116,7 @@ test_expect_success 'cvsimport.module config works' '
 
 test_expect_success 'import from a CVS working tree' '
 
-       cvs co -d import-from-wt module &&
+       $CVS co -d import-from-wt module &&
        cd import-from-wt &&
                git cvsimport -a -z0 &&
                echo 1 >expect &&
@@ -150,4 +126,6 @@ test_expect_success 'import from a CVS working tree' '
 
 '
 
+test_expect_success '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..3afaf56
--- /dev/null
@@ -0,0 +1,86 @@
+#!/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
+
+CVSROOT="$TEST_DIRECTORY"/t9601/cvsroot
+export CVSROOT
+
+test_expect_success 'import a module with a vendor branch' '
+
+       git cvsimport -C module-git module
+
+'
+
+test_expect_success 'check HEAD out of cvs repository' 'test_cvs_co master'
+
+test_expect_success 'check master out of git repository' 'test_git_co master'
+
+test_expect_success 'check a file that was imported once' '
+
+       test_cmp_branch_file master imported-once.txt
+
+'
+
+test_expect_failure 'check a file that was imported twice' '
+
+       test_cmp_branch_file master imported-twice.txt
+
+'
+
+test_expect_success 'check a file that was imported then modified on HEAD' '
+
+       test_cmp_branch_file master imported-modified.txt
+
+'
+
+test_expect_success 'check a file that was imported, modified, then imported again' '
+
+       test_cmp_branch_file master imported-modified-imported.txt
+
+'
+
+test_expect_success 'check a file that was added to HEAD then imported' '
+
+       test_cmp_branch_file master added-imported.txt
+
+'
+
+test_expect_success '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..67878b2
--- /dev/null
@@ -0,0 +1,79 @@
+#!/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
+
+CVSROOT="$TEST_DIRECTORY"/t9602/cvsroot
+export CVSROOT
+
+test_expect_success 'import module' '
+
+       git cvsimport -C module-git module
+
+'
+
+test_expect_success 'test branch master' '
+
+       test_cmp_branch_tree master
+
+'
+
+test_expect_success 'test branch vendorbranch' '
+
+       test_cmp_branch_tree vendorbranch
+
+'
+
+test_expect_failure 'test branch B_FROM_INITIALS' '
+
+       test_cmp_branch_tree B_FROM_INITIALS
+
+'
+
+test_expect_failure 'test branch B_FROM_INITIALS_BUT_ONE' '
+
+       test_cmp_branch_tree B_FROM_INITIALS_BUT_ONE
+
+'
+
+test_expect_failure 'test branch B_MIXED' '
+
+       test_cmp_branch_tree B_MIXED
+
+'
+
+test_expect_success 'test branch B_SPLIT' '
+
+       test_cmp_branch_tree B_SPLIT
+
+'
+
+test_expect_failure 'test tag vendortag' '
+
+       test_cmp_branch_tree vendortag
+
+'
+
+test_expect_success 'test tag T_ALL_INITIAL_FILES' '
+
+       test_cmp_branch_tree T_ALL_INITIAL_FILES
+
+'
+
+test_expect_failure 'test tag T_ALL_INITIAL_FILES_BUT_ONE' '
+
+       test_cmp_branch_tree T_ALL_INITIAL_FILES_BUT_ONE
+
+'
+
+test_expect_failure '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..958bdce
--- /dev/null
@@ -0,0 +1,40 @@
+#!/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
+
+CVSROOT="$TEST_DIRECTORY"/t9603/cvsroot
+export CVSROOT
+
+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 &&
+    cd .. &&
+    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 4eb7d3f7f042f896106f208a7f8bb91925352b2a..8686086dde9dba742bd805c31f3e28c658b03a9b 100755 (executable)
@@ -11,7 +11,7 @@ if ! test_have_prereq PERL; then
        test_done
 fi
 
-perl -MTest::More -e 0 2>/dev/null || {
+"$PERL_PATH" -MTest::More -e 0 2>/dev/null || {
        say "Perl Test::More unavailable, skipping test"
        test_done
 }
@@ -48,6 +48,6 @@ test_expect_success \
 
 test_external_without_stderr \
     'Perl API' \
-    perl "$TEST_DIRECTORY"/t9700/test.pl
+    "$PERL_PATH" "$TEST_DIRECTORY"/t9700/test.pl
 
 test_done
index 6c70aec020c9c6486ae984dc264edb1dd8b270cf..666722d9bf1050522a687f4af95792a8e0ec5d64 100755 (executable)
@@ -13,7 +13,7 @@ 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
index 5fdc5d94a20cfcf231bf23590784c5146a3e44bc..2d523fe0f08dbbc1971f4601f24664cdbce95415 100644 (file)
@@ -30,7 +30,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
@@ -58,7 +58,7 @@ 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
+export EDITOR
 GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u}
 
 # Protect ourselves from common misconfiguration to export
@@ -114,6 +114,9 @@ do
                valgrind=t; verbose=t; shift ;;
        --tee)
                shift ;; # was handled already
+       --root=*)
+               root=$(expr "z$1" : 'z[^=]*=\(.*\)')
+               shift ;;
        *)
                echo "error: unknown test option '$1'" >&2; exit 1 ;;
        esac
@@ -204,8 +207,8 @@ trap 'die' EXIT
 test_set_editor () {
        FAKE_EDITOR="$1"
        export FAKE_EDITOR
-       VISUAL='"$FAKE_EDITOR"'
-       export VISUAL
+       EDITOR='"$FAKE_EDITOR"'
+       export EDITOR
 }
 
 test_tick () {
@@ -629,23 +632,37 @@ GIT_CONFIG_NOSYSTEM=1
 GIT_CONFIG_NOGLOBAL=1
 export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL
 
+. ../GIT-BUILD-OPTIONS
+
 GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
 export GITPERLLIB
 test -d ../templates/blt || {
        error "You haven't built things yet, have you?"
 }
 
+if test -z "$GIT_TEST_INSTALLED" && test -z "$NO_PYTHON"
+then
+       GITPYTHONLIB="$(pwd)/../git_remote_helpers/build/lib"
+       export GITPYTHONLIB
+       test -d ../git_remote_helpers/build || {
+               error "You haven't built git_remote_helpers yet, have you?"
+       }
+fi
+
 if ! test -x ../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" || {
        GIT_EXIT_OK=t
        echo >&5 "FATAL: Cannot prepare test area"
@@ -677,6 +694,21 @@ do
        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*)
@@ -706,6 +738,7 @@ 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
index a12c6e214e65d39136b1ed41a8ff0ea25e28f91b..408f0137a8342414eedba3a02372ea1ad6050117 100644 (file)
@@ -50,4 +50,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 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 b11ad6a6fbab9242a2dac157ee1a55875954b95a..439eefda510ca8de9f55c63616f2113ac36c8b6b 100755 (executable)
@@ -7,6 +7,14 @@
 #
 # To enable this hook, rename this file to "pre-commit".
 
+if git-rev-parse --verify HEAD >/dev/null 2>&1
+then
+       against=HEAD
+else
+       # Initial commit: diff against an empty tree object
+       against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+fi
+
 # If you want to allow non-ascii filenames set this variable to true.
 allownonascii=$(git config hooks.allownonascii)
 
@@ -14,15 +22,18 @@ allownonascii=$(git config hooks.allownonascii)
 # 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" ] &&
-       test "$(git diff --cached --name-only --diff-filter=A -z |
+       # 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 filename."
+       echo "Error: Attempt to add a non-ascii file name."
        echo
-       echo "This can cause problems if you want to work together"
-       echo "with people on other platforms than you."
+       echo "This can cause problems if you want to work"
+       echo "with people on other platforms."
        echo
-       echo "To be portable it is adviseable to rename the file ..."
+       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:"
@@ -32,12 +43,4 @@ then
        exit 1
 fi
 
-if git-rev-parse --verify HEAD >/dev/null 2>&1
-then
-       against=HEAD
-else
-       # Initial commit: diff against an empty tree object
-       against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
-fi
-
 exec git diff-index --check --cached $against --
index 62e8f2387a1cab97ec1c71d1993d082274e17bf5..a9e705f79a148790f4de55c7918dc9c669382a4f 100644 (file)
@@ -1,20 +1,67 @@
 #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];
                time_t t;
 
-               memcpy(result, "bad", 4);
-               parse_date(argv[i], result, sizeof(result));
+               result[0] = 0;
+               parse_date(*argv, result, sizeof(result));
                t = strtoul(result, NULL, 0);
-               printf("%s -> %s -> %s", argv[i], result, ctime(&t));
+               printf("%s -> %s\n", *argv,
+                       t ? show_date(t, 0, DATE_ISO8601) : "bad");
+       }
+}
 
-               t = approxidate(argv[i]);
-               printf("%s -> %s\n", argv[i], ctime(&t));
+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));
+       }
+}
+
+int main(int argc, char **argv)
+{
+       struct timeval now;
+       const char *x;
+
+       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[])
 {
index efa734b42e38d7f5a98393cd69ac0ffe10160ffc..acd1a2ba70fc2ffb38cd8fb0784aa6e5e693183f 100644 (file)
@@ -8,6 +8,7 @@ static int abbrev = 7;
 static int verbose = 0, dry_run = 0, quiet = 0;
 static char *string = NULL;
 static char *file = NULL;
+static int ambiguous;
 
 static int length_callback(const struct option *opt, const char *arg, int unset)
 {
@@ -59,6 +60,10 @@ int main(int argc, const char **argv)
                        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),
index 55e7e2904eb5f95cedaec2520ddd1d158ee93c7a..4f9c829c2df319e386b6b5d4f5c23818cc21c979 100644 (file)
@@ -1,9 +1,6 @@
 #include "cache.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
 
diff --git a/transport-helper.c b/transport-helper.c
new file mode 100644 (file)
index 0000000..11f3d7e
--- /dev/null
@@ -0,0 +1,520 @@
+#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"
+
+struct helper_data
+{
+       const char *name;
+       struct child_process *helper;
+       FILE *out;
+       unsigned fetch : 1,
+               import : 1,
+               option : 1,
+               push : 1;
+       /* These go from remote name (as in "list") to private name */
+       struct refspec *refspecs;
+       int refspec_nr;
+};
+
+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;
+
+       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, "remote-%s", data->name);
+       helper->argv[0] = strbuf_detach(&buf, NULL);
+       helper->argv[1] = transport->remote->name;
+       helper->argv[2] = transport->url;
+       helper->git_cmd = 1;
+       if (start_command(helper))
+               die("Unable to run helper: git %s", helper->argv[0]);
+       data->helper = helper;
+
+       write_str_in_full(helper->in, "capabilities\n");
+
+       data->out = xfdopen(helper->out, "r");
+       while (1) {
+               if (strbuf_getline(&buf, data->out, '\n') == EOF)
+                       exit(128); /* child died, message supplied already */
+
+               if (!*buf.buf)
+                       break;
+               if (!strcmp(buf.buf, "fetch"))
+                       data->fetch = 1;
+               if (!strcmp(buf.buf, "option"))
+                       data->option = 1;
+               if (!strcmp(buf.buf, "push"))
+                       data->push = 1;
+               if (!strcmp(buf.buf, "import"))
+                       data->import = 1;
+               if (!data->refspecs && !prefixcmp(buf.buf, "refspec ")) {
+                       ALLOC_GROW(refspecs,
+                                  refspec_nr + 1,
+                                  refspec_alloc);
+                       refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec "));
+               }
+       }
+       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);
+       return data->helper;
+}
+
+static int disconnect_helper(struct transport *transport)
+{
+       struct helper_data *data = transport->data;
+       if (data->helper) {
+               write_str_in_full(data->helper->in, "\n");
+               close(data->helper->in);
+               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 child_process *helper = get_helper(transport);
+       struct strbuf buf = STRBUF_INIT;
+       int i, ret, is_bool = 0;
+
+       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');
+
+       if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
+               die_errno("cannot send option to %s", data->name);
+
+       strbuf_reset(&buf);
+       if (strbuf_getline(&buf, data->out, '\n') == EOF)
+               exit(128); /* child died, message supplied already */
+
+       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;
+       int no_progress = v < 0 || (!t->progress && !isatty(1));
+
+       set_helper_option(t, "progress", !no_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');
+       if (write_in_full(data->helper->in, buf.buf, buf.len) != buf.len)
+               die_errno("cannot send fetch to %s", data->name);
+
+       while (1) {
+               strbuf_reset(&buf);
+               if (strbuf_getline(&buf, data->out, '\n') == EOF)
+                       exit(128); /* child died, message supplied already */
+
+               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 fetch_with_import(struct transport *transport,
+                            int nr_heads, struct ref **to_fetch)
+{
+       struct child_process fastimport;
+       struct child_process *helper = get_helper(transport);
+       struct helper_data *data = transport->data;
+       int i;
+       struct ref *posn;
+       struct strbuf buf = STRBUF_INIT;
+
+       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);
+               write_in_full(helper->in, buf.buf, buf.len);
+               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 fetch(struct transport *transport,
+                int nr_heads, struct ref **to_fetch)
+{
+       struct helper_data *data = transport->data;
+       int i, count;
+
+       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(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;
+
+       if (!remote_refs)
+               return 0;
+
+       helper = get_helper(transport);
+       if (!data->push)
+               return 1;
+
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (ref->peer_ref)
+                       hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
+               else if (!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;
+               }
+
+               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;
+
+       transport->verbose = flags & TRANSPORT_PUSH_VERBOSE ? 1 : 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');
+       if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
+               exit(128);
+
+       ref = remote_refs;
+       while (1) {
+               char *refname, *msg;
+               int status;
+
+               strbuf_reset(&buf);
+               if (strbuf_getline(&buf, data->out, '\n') == EOF)
+                       exit(128); /* child died, message supplied already */
+               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;
+               }
+
+               ref->status = status;
+               ref->remote_status = msg;
+       }
+       strbuf_release(&buf);
+       return 0;
+}
+
+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 (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;
+               if (strbuf_getline(&buf, data->out, '\n') == EOF)
+                       exit(128); /* child died, message supplied already */
+
+               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);
+       }
+       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;
+
+       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;
+       return 0;
+}
index de0d5874a3d867d71eaec3cd1dde1bf2f09cfe4d..3eea836a33a56aaa99eec78bb4850b02888e377b 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"
@@ -207,7 +204,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;
@@ -352,178 +349,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;
-       int http_ret;
-
-       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);
-
-       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);
-       default:
-               http_error(refs_url, http_ret);
-               die("HTTP request failed");
-       }
-
-       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);
-       }
-
-       strbuf_release(&buffer);
-       free(refs_url);
-       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;
@@ -554,7 +379,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);
@@ -632,7 +457,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
 }
 
 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));
@@ -681,6 +506,21 @@ static int fetch_refs_via_pack(struct transport *transport,
        return (refs ? 0 : -1);
 }
 
+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;
+}
+
 static int refs_pushed(struct ref *ref)
 {
        for (; ref; ref = ref->next) {
@@ -799,7 +639,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
                break;
        case REF_STATUS_REJECT_NONFASTFORWARD:
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
-                                                "non-fast forward", porcelain);
+                                                "non-fast-forward", porcelain);
                break;
        case REF_STATUS_REMOTE_REJECT:
                print_ref_status('!', "[remote rejected]", ref,
@@ -820,7 +660,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
 }
 
 static void print_push_status(const char *dest, struct ref *refs,
-                                                         int verbose, int porcelain)
+                             int verbose, int porcelain, int * nonfastforward)
 {
        struct ref *ref;
        int n = 0;
@@ -835,11 +675,14 @@ static void print_push_status(const char *dest, struct ref *refs,
                if (ref->status == REF_STATUS_OK)
                        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, porcelain);
+               if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD)
+                       *nonfastforward = 1;
        }
 }
 
@@ -888,10 +731,12 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
                                 NULL);
        }
 
+       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.quiet = !!(flags & TRANSPORT_PUSH_QUIET);
        args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
 
        ret = send_pack(&args, data->fd, data->conn, remote_refs,
@@ -939,9 +784,30 @@ struct transport *transport_get(struct remote *remote, const char *url)
 {
        struct transport *ret = xcalloc(1, sizeof(*ret));
 
+       if (!remote)
+               die("No remote provided to transport_get()");
+
        ret->remote = remote;
+
+       if (!url && remote && remote->url)
+               url = remote->url[0];
        ret->url = url;
 
+       /* maybe it is a foreign URL? */
+       if (url) {
+               const char *p = url;
+
+               while (isalnum(*p))
+                       p++;
+               if (!prefixcmp(p, "::"))
+                       remote->foreign_vcs = xstrndup(url, p - url);
+       }
+
+       if (remote && remote->foreign_vcs) {
+               transport_helper_init(ret, remote->foreign_vcs);
+               return ret;
+       }
+
        if (!prefixcmp(url, "rsync:")) {
                ret->get_refs_list = get_refs_via_rsync;
                ret->fetch = fetch_objs_via_rsync;
@@ -950,14 +816,10 @@ struct transport *transport_get(struct remote *remote, const char *url)
        } else if (!prefixcmp(url, "http://")
                || !prefixcmp(url, "https://")
                || !prefixcmp(url, "ftp://")) {
+               transport_helper_init(ret, "curl");
 #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;
 
        } else if (is_local(url) && is_file(url)) {
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
@@ -978,10 +840,10 @@ struct transport *transport_get(struct remote *remote, const char *url)
                data->thin = 1;
                data->conn = NULL;
                data->uploadpack = "git-upload-pack";
-               if (remote && remote->uploadpack)
+               if (remote->uploadpack)
                        data->uploadpack = remote->uploadpack;
                data->receivepack = "git-receive-pack";
-               if (remote && remote->receivepack)
+               if (remote->receivepack)
                        data->receivepack = remote->receivepack;
        }
 
@@ -997,8 +859,10 @@ int transport_set_option(struct transport *transport,
 }
 
 int transport_push(struct transport *transport,
-                  int refspec_nr, const char **refspec, int flags)
+                  int refspec_nr, const char **refspec, int flags,
+                  int *nonfastforward)
 {
+       *nonfastforward = 0;
        verify_remote_names(refspec_nr, refspec);
 
        if (transport->push)
@@ -1009,6 +873,7 @@ int transport_push(struct transport *transport,
                struct ref *local_refs = get_local_heads();
                int match_flags = MATCH_REFS_NONE;
                int verbose = flags & TRANSPORT_PUSH_VERBOSE;
+               int quiet = flags & TRANSPORT_PUSH_QUIET;
                int porcelain = flags & TRANSPORT_PUSH_PORCELAIN;
                int ret;
 
@@ -1024,7 +889,10 @@ int transport_push(struct transport *transport,
 
                ret = transport->push_refs(transport, remote_refs, flags);
 
-               print_push_status(transport->url, remote_refs, verbose | porcelain, porcelain);
+               if (!quiet || push_had_errors(remote_refs))
+                       print_push_status(transport->url, remote_refs,
+                                       verbose | porcelain, porcelain,
+                                       nonfastforward);
 
                if (!(flags & TRANSPORT_PUSH_DRY_RUN)) {
                        struct ref *ref;
@@ -1032,7 +900,7 @@ int transport_push(struct transport *transport,
                                update_tracking_ref(transport->remote, ref, verbose);
                }
 
-               if (!ret && !refs_pushed(remote_refs))
+               if (!quiet && !ret && !refs_pushed(remote_refs))
                        fprintf(stderr, "Everything up-to-date\n");
                return ret;
        }
@@ -1046,21 +914,36 @@ const struct ref *transport_get_remote_refs(struct transport *transport)
        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;
index 51b539778c2f63591c4f032e311372b2e9975a37..9e74406fa203a59920cb24884d7893b45d876a70 100644 (file)
@@ -18,14 +18,51 @@ 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);
 
+       /** 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;
+       signed verbose : 3;
        /* Force progress even if the output is not a tty */
        unsigned progress : 1;
 };
@@ -36,6 +73,7 @@ struct transport {
 #define TRANSPORT_PUSH_MIRROR 8
 #define TRANSPORT_PUSH_VERBOSE 16
 #define TRANSPORT_PUSH_PORCELAIN 32
+#define TRANSPORT_PUSH_QUIET 64
 
 /* Returns a transport suitable for the url */
 struct transport *transport_get(struct remote *, const char *);
@@ -68,13 +106,17 @@ int transport_set_option(struct transport *transport, const char *name,
                         const char *value);
 
 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);
 
+/* Transport methods defined outside transport.c */
+int transport_helper_init(struct transport *transport, const char *name);
+
 #endif
index ac9cbf7cd8ed1367151de0e8b96668f498b7f1a1..e9d8934691822ad81a4c32481d82790e9d7ca5b0 100644 (file)
@@ -28,7 +28,7 @@ int main(int argc, char **argv)
 
        git_extract_argv0_path(argv[0]);
 
-       if (argc != 2)
+       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]);
index 720f7a161651126872b7357173c10d69d1db7636..dd5999c3562219b7993420b22257f4088ab82b8d 100644 (file)
@@ -277,6 +277,17 @@ 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;
+}
+
 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, };
@@ -294,7 +305,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
                        int 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) {
@@ -352,17 +363,6 @@ 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.
@@ -617,7 +617,7 @@ 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)
@@ -895,7 +895,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>.
  *
index 1e0e2325f150e943dd22b16c1392c136570732bc..d19df44f4070ca1bda29382e56fbced53aacd994 100644 (file)
@@ -17,18 +17,18 @@ struct unpack_trees_error_msgs {
 };
 
 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,
-                    diff_index_cached: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,
+                    gently;
        const char *prefix;
        int pos;
        struct dir_struct *dir;
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 841ebb534ac3393f5ee80692f1402c82a1b4a5f6..df151813f9c12a681dcac85608f5ff2262c12879 100644 (file)
@@ -29,14 +29,18 @@ 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, 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)
 {
@@ -106,9 +110,7 @@ static int do_rev_list(int fd, void *create_full_pack)
        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(fd, "w");
        init_revisions(&revs, NULL);
        revs.tag_objects = 1;
        revs.tree_objects = 1;
@@ -136,6 +138,10 @@ static int do_rev_list(int fd, void *create_full_pack)
        if (prepare_revision_walk(&revs))
                die("revision walk setup failed");
        mark_edges_uninteresting(revs.commits, &revs, show_edge);
+       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 +161,21 @@ 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) {
+               rev_list.proc = do_rev_list;
+               rev_list.data = 0;
+               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 +186,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 +195,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 +250,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 +286,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 +304,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 +432,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;
@@ -451,8 +490,9 @@ static void receive_needs(void)
        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 +506,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 +517,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 +526,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;
@@ -520,7 +560,7 @@ static void receive_needs(void)
                }
        }
        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;
@@ -538,6 +578,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;
                }
@@ -560,6 +601,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);
@@ -571,6 +613,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);
 }
 
@@ -578,7 +622,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)
@@ -602,12 +646,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();
@@ -622,12 +686,21 @@ 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;
diff --git a/usage.c b/usage.c
index b6aea45280bf2aa4c06a0130f912a61377165016..79856a2b9f5bc4603252cb10b471a0815416a617 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -7,14 +7,14 @@
 
 static void report(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);
+       report("usage: ", err, params);
        exit(129);
 }
 
@@ -36,19 +36,28 @@ static void warn_builtin(const char *warn, va_list 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, ...)
diff --git a/utf8.c b/utf8.c
index db706ac4ed6f033091cfaeb7b23c07ca7fd71675..7ddff23fa77fbadf7723bca03d24ad5b8f2baca2 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/ */
@@ -279,14 +280,52 @@ int is_utf8(const char *text)
        return 1;
 }
 
-static void print_spaces(int count)
+static inline void strbuf_write(struct strbuf *sb, const char *buf, int len)
+{
+       if (sb)
+               strbuf_insert(sb, sb->len, buf, len);
+       else
+               fwrite(buf, len, 1, stdout);
+}
+
+static void print_spaces(struct strbuf *buf, int count)
 {
        static const char s[] = "                    ";
        while (count >= sizeof(s)) {
-               fwrite(s, sizeof(s) - 1, 1, stdout);
+               strbuf_write(buf, s, sizeof(s) - 1);
                count -= sizeof(s) - 1;
        }
-       fwrite(s, count, 1, stdout);
+       strbuf_write(buf, s, count);
+}
+
+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++;
+               print_spaces(buf, indent);
+               strbuf_write(buf, text, eol - text);
+               text = eol;
+               indent = indent2;
+       }
+}
+
+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,36 +334,62 @@ 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 indent, int indent2, int width)
 {
        int w = indent, assume_utf8 = is_utf8(text);
        const char *bol = text, *space = NULL;
 
+       if (width <= 0) {
+               strbuf_add_indented_text(buf, text, indent, indent2);
+               return 1;
+       }
+
        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);
+                                       print_spaces(buf, indent);
+                               strbuf_write(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_write(buf, "\n", 1);
+                                               goto new_line;
+                                       }
+                                       else if (!isalnum(*space))
+                                               goto new_line;
+                                       else
+                                               strbuf_write(buf, " ", 1);
+                               }
                                w++;
                                text++;
                        }
                        else {
-                               putchar('\n');
+new_line:
+                               strbuf_write(buf, "\n", 1);
                                text = bol = space + isspace(*space);
                                space = NULL;
                                w = indent = indent2;
@@ -340,6 +405,11 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width)
        }
 }
 
+int print_wrapped_text(const char *text, int indent, int indent2, int width)
+{
+       return strbuf_add_wrapped_text(NULL, text, indent, indent2, width);
+}
+
 int is_encoding_utf8(const char *name)
 {
        if (!name)
diff --git a/utf8.h b/utf8.h
index 2f1b14ff49ef3c73bee6f298ba396b96120c34b7..ae30ae4c6e501e4766db93c94253fd404cd29357 100644 (file)
--- a/utf8.h
+++ b/utf8.h
@@ -10,6 +10,8 @@ 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
index 7362ed87354a4bea98c28f9a8c315b7209c67fa2..d9892f85ce954883427ac4f61d9de9d591c0827c 100644 (file)
--- a/var.c
+++ b/var.c
@@ -8,6 +8,25 @@
 
 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();
+
+       if (!pgm)
+               pgm = "cat";
+       return pgm;
+}
+
 struct git_var {
        const char *name;
        const char *(*read)(int);
@@ -15,15 +34,19 @@ struct git_var {
 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;
-       for(ptr = git_vars; ptr->read; ptr++) {
-               printf("%s=%s\n", ptr->name, ptr->read(IDENT_WARN_ON_NO_NAME));
-       }
+       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)
@@ -31,7 +54,7 @@ static const char *read_var(const char *var)
        struct git_var *ptr;
        const char *val;
        val = NULL;
-       for(ptr = git_vars; ptr->read; ptr++) {
+       for (ptr = git_vars; ptr->read; ptr++) {
                if (strcmp(var, ptr->name) == 0) {
                        val = ptr->read(IDENT_ERROR_ON_NO_NAME);
                        break;
diff --git a/ws.c b/ws.c
index 59d0883c1f3a46edcbb2e45b172c9eac3b9d5e35..760b5743fa11f25dd4facf8beeb02e7aa28d09e1 100644 (file)
--- a/ws.c
+++ b/ws.c
@@ -16,6 +16,8 @@ static struct whitespace_rule {
        { "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 },
 };
 
 unsigned parse_whitespace_rule(const char *string)
@@ -102,8 +104,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, ", ");
@@ -141,11 +152,11 @@ 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;
@@ -261,7 +272,7 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro
        /*
         * Strip trailing whitespace
         */
-       if (ws_rule & WS_TRAILING_SPACE) {
+       if (ws_rule & WS_BLANK_AT_EOL) {
                if (0 < len && src[len - 1] == '\n') {
                        add_nl_to_tail = 1;
                        len--;
index 47735d8129fd3632b85d54b451e30752f251a949..38eb24536b34e40af4c43eb1631e08624e8993d6 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 "run-command.h"
 #include "remote.h"
 
-int wt_status_relative_paths = 1;
-int wt_status_use_color = -1;
-static 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 */
 };
 
-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,17 +30,40 @@ 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;
+}
+
+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->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 <file>...\" 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 (!advice_status_hints)
+               return;
        if (!s->is_initial) {
                color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
        } else {
@@ -73,8 +75,10 @@ static void wt_status_print_cached_header(struct wt_status *s)
 static void wt_status_print_dirty_header(struct wt_status *s,
                                         int has_deleted)
 {
-       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
@@ -85,31 +89,73 @@ static void wt_status_print_dirty_header(struct wt_status *s,
 
 static void wt_status_print_untracked_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, "# Untracked files:");
+       if (!advice_status_hints)
+               return;
        color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to include in what will be committed)");
        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;
 
-       one = quote_path(p->one->path, -1, &onebuf, s->prefix);
-       two = quote_path(p->two->path, -1, &twobuf, s->prefix);
+       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:
+               status = d->worktree_status;
+               break;
+       }
+
+       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,64 +181,114 @@ 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);
        }
        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(p->one->path, &s->change);
+               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;
        }
-       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(p->two->path, &s->change);
+               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, NULL);
+       rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = wt_status_collect_changed_cb;
+       rev.diffopt.format_callback_data = s;
+       run_diff_files(&rev, 0);
+}
+
+static void wt_status_collect_changes_index(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);
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
-       rev.diffopt.format_callback = wt_status_print_updated_cb;
+       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;
@@ -200,15 +296,155 @@ static void wt_status_print_updated(struct wt_status *s)
        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];
+
+               it = string_list_insert(ce->name, &s->change);
+               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, 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;
+               s->workdir_untracked = 1;
+               string_list_insert(ent->name, &s->untracked);
+       }
+}
+
+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 i;
+       int changes = 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;
+               changes = 1;
+               if (d->worktree_status == DIFF_STATUS_DELETED)
+                       return -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;
+       int worktree_changes = wt_status_check_worktree_changes(s);
+
+       if (!worktree_changes)
+               return;
+
+       wt_status_print_dirty_header(s, worktree_changes < 0);
+
+       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)
@@ -228,7 +464,7 @@ static void wt_status_print_submodule_summary(struct wt_status *s)
                NULL
        };
 
-       sprintf(summary_limit, "%d", wt_status_submodule_summary);
+       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));
@@ -243,32 +479,20 @@ static void wt_status_print_submodule_summary(struct wt_status *s)
 
 static void wt_status_print_untracked(struct wt_status *s)
 {
-       struct dir_struct dir;
        int i;
-       int shown_header = 0;
        struct strbuf buf = STRBUF_INIT;
 
-       memset(&dir, 0, sizeof(dir));
-
-       if (!s->untracked)
-               dir.flags |=
-                       DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
-       setup_standard_excludes(&dir);
+       if (!s->untracked.nr)
+               return;
 
-       fill_directory(&dir, 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));
+       wt_status_print_untracked_header(s);
+       for (i = 0; i < s->untracked.nr; i++) {
+               struct string_list_item *it;
+               it = &(s->untracked.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);
 }
@@ -310,15 +534,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) {
@@ -328,26 +552,29 @@ 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);
        }
 
+       wt_status_collect(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)
+       if (s->submodule_summary)
                wt_status_print_submodule_summary(s);
-       if (show_untracked_files)
+       if (s->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");
@@ -361,53 +588,13 @@ void wt_status_print(struct wt_status *s)
                        ; /* 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)
+               else if (s->untracked.nr)
                        printf("nothing added to commit but untracked files present (use \"git add\" to track)\n");
                else if (s->is_initial)
                        printf("nothing to commit (create/copy files and use \"git add\" to track)\n");
-               else if (!show_untracked_files)
+               else if (!s->show_untracked_files)
                        printf("nothing to commit (use -u to show untracked files)\n");
                else
                        printf("nothing to commit (working directory clean)\n");
        }
 }
-
-int git_status_config(const char *k, const char *v, void *cb)
-{
-       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;
-       }
-       if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
-               wt_status_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 (!v)
-                       return config_error_nonbool(k);
-               color_parse(v, k, wt_status_colors[slot]);
-               return 0;
-       }
-       if (!strcmp(k, "status.relativepaths")) {
-               wt_status_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"))
-                       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;
-               else
-                       return error("Invalid untracked files mode '%s'", v);
-               return 0;
-       }
-       return git_diff_ui_config(k, v, cb);
-}
index 78add09bd67c727babb61cd1eaa773bcd0c6e55e..a0e75177bec925e95b4257811aae0bdee93c41f1 100644 (file)
@@ -2,13 +2,16 @@
 #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,
 };
 
 enum untracked_status_type {
@@ -16,7 +19,13 @@ 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;
+};
 
 struct wt_status {
        int is_initial;
@@ -24,8 +33,13 @@ struct wt_status {
        const char *reference;
        int verbose;
        int amend;
-       int untracked;
        int nowarn;
+       int use_color;
+       int relative_paths;
+       int submodule_summary;
+       enum untracked_status_type show_untracked_files;
+       char color_palette[WT_STATUS_UNMERGED+1][COLOR_MAXLEN];
+
        /* These are computed during processing of the individual sections */
        int commitable;
        int workdir_dirty;
@@ -33,12 +47,12 @@ struct wt_status {
        const char *index_file;
        FILE *fp;
        const char *prefix;
+       struct string_list change;
+       struct string_list untracked;
 };
 
-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);
 
 #endif /* STATUS_H */
index 04ad468702209b77427e635370d41001986042ce..bc12f298953a4e72b323f73607278028ec4a2805 100644 (file)
@@ -190,48 +190,66 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
 {
        int i1, i2;
 
+       if (!(flags & XDF_WHITESPACE_FLAGS))
+               return s1 == s2 && !memcmp(l1, l2, s1);
+
+       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 +260,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;