Code

Merge branch 'ms/maint-config-error-at-eol-linecount' into maint
authorJunio C Hamano <gitster@pobox.com>
Mon, 26 Mar 2012 19:10:05 +0000 (12:10 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 26 Mar 2012 19:10:05 +0000 (12:10 -0700)
* ms/maint-config-error-at-eol-linecount:
  config: report errors at the EOL with correct line number

622 files changed:
.gitignore
Documentation/CodingGuidelines
Documentation/Makefile
Documentation/RelNotes/1.7.8.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.8.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.8.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.8.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.8.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.8.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.9.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.9.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.9.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.9.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.9.txt [new file with mode: 0644]
Documentation/blame-options.txt
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-branch.txt
Documentation/git-check-attr.txt
Documentation/git-check-ref-format.txt
Documentation/git-cherry-pick.txt
Documentation/git-commit-tree.txt
Documentation/git-config.txt
Documentation/git-credential-cache--daemon.txt [new file with mode: 0644]
Documentation/git-credential-cache.txt [new file with mode: 0644]
Documentation/git-credential-store.txt [new file with mode: 0644]
Documentation/git-daemon.txt
Documentation/git-fmt-merge-msg.txt
Documentation/git-for-each-ref.txt
Documentation/git-fsck.txt
Documentation/git-grep.txt
Documentation/git-instaweb.txt
Documentation/git-mailinfo.txt
Documentation/git-mv.txt
Documentation/git-p4.txt [new file with mode: 0644]
Documentation/git-pull.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-remote.txt
Documentation/git-reset.txt
Documentation/git-rev-parse.txt
Documentation/git-revert.txt
Documentation/git-sh-setup.txt
Documentation/git-show-ref.txt
Documentation/git-stripspace.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git-tag.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitcore-tutorial.txt
Documentation/gitcredentials.txt [new file with mode: 0644]
Documentation/gittutorial-2.txt
Documentation/gitweb.conf.txt [new file with mode: 0644]
Documentation/gitweb.txt [new file with mode: 0644]
Documentation/howto/using-signed-tag-in-pull-request.txt [new file with mode: 0644]
Documentation/merge-options.txt
Documentation/pretty-formats.txt
Documentation/rev-list-options.txt
Documentation/revisions.txt
Documentation/sequencer.txt [new file with mode: 0644]
Documentation/technical/api-credentials.txt [new file with mode: 0644]
Documentation/technical/api-parse-options.txt
Documentation/technical/api-strbuf.txt
Documentation/technical/pack-protocol.txt
GIT-VERSION-GEN
INSTALL
Makefile
README
RelNotes
advice.c
advice.h
archive.c
bisect.c
bisect.h
branch.c
branch.h
builtin.h
builtin/add.c
builtin/apply.c
builtin/blame.c
builtin/branch.c
builtin/check-attr.c
builtin/check-ref-format.c
builtin/checkout.c
builtin/clone.c
builtin/commit-tree.c
builtin/commit.c
builtin/config.c
builtin/diff.c
builtin/fast-export.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/for-each-ref.c
builtin/fsck.c
builtin/gc.c
builtin/grep.c
builtin/index-pack.c
builtin/init-db.c
builtin/log.c
builtin/ls-files.c
builtin/ls-remote.c
builtin/merge.c
builtin/mktree.c
builtin/mv.c
builtin/name-rev.c
builtin/notes.c
builtin/pack-objects.c
builtin/prune.c
builtin/push.c
builtin/receive-pack.c
builtin/reflog.c
builtin/remote.c
builtin/replace.c
builtin/reset.c
builtin/rev-list.c
builtin/rev-parse.c
builtin/revert.c
builtin/send-pack.c
builtin/show-branch.c
builtin/show-ref.c
builtin/stripspace.c
builtin/symbolic-ref.c
builtin/tag.c
builtin/upload-archive.c
builtin/verify-tag.c
bulk-checkin.c [new file with mode: 0644]
bulk-checkin.h [new file with mode: 0644]
bundle.c
bundle.h
cache-tree.c
cache-tree.h
cache.h
combine-diff.c
command-list.txt
commit.c
commit.h
compat/inet_ntop.c
compat/inet_pton.c
compat/mingw.c
compat/mingw.h
compat/msvc.h
compat/obstack.c
compat/setenv.c
compat/snprintf.c
compat/strtoimax.c [new file with mode: 0644]
compat/terminal.c [new file with mode: 0644]
compat/terminal.h [new file with mode: 0644]
compat/vcbuild/include/arpa/inet.h [deleted file]
compat/vcbuild/include/grp.h [deleted file]
compat/vcbuild/include/inttypes.h [deleted file]
compat/vcbuild/include/netdb.h [deleted file]
compat/vcbuild/include/netinet/in.h [deleted file]
compat/vcbuild/include/netinet/tcp.h [deleted file]
compat/vcbuild/include/pwd.h [deleted file]
compat/vcbuild/include/sys/ioctl.h [deleted file]
compat/vcbuild/include/sys/select.h [deleted file]
compat/vcbuild/include/sys/socket.h [deleted file]
compat/vcbuild/include/sys/wait.h [deleted file]
compat/vcbuild/include/termios.h [deleted file]
compat/win32/poll.c [new file with mode: 0644]
compat/win32/poll.h [new file with mode: 0644]
compat/win32/sys/poll.c [deleted file]
compat/win32/sys/poll.h [deleted file]
compat/win32/syslog.c
config.c
config.mak.in
configure.ac
connect.c
connected.c [new file with mode: 0644]
connected.h [new file with mode: 0644]
contrib/completion/git-completion.bash
contrib/credential/osxkeychain/.gitignore [new file with mode: 0644]
contrib/credential/osxkeychain/Makefile [new file with mode: 0644]
contrib/credential/osxkeychain/git-credential-osxkeychain.c [new file with mode: 0644]
contrib/diff-highlight/README [new file with mode: 0644]
contrib/diff-highlight/diff-highlight [new file with mode: 0755]
contrib/fast-import/git-p4
contrib/fast-import/git-p4.txt [deleted file]
contrib/git-jump/README [new file with mode: 0644]
contrib/git-jump/git-jump [new file with mode: 0755]
contrib/hooks/post-receive-email
contrib/mw-to-git/git-remote-mediawiki [new file with mode: 0755]
contrib/mw-to-git/git-remote-mediawiki.txt [new file with mode: 0644]
convert.c
convert.h
credential-cache--daemon.c [new file with mode: 0644]
credential-cache.c [new file with mode: 0644]
credential-store.c [new file with mode: 0644]
credential.c [new file with mode: 0644]
credential.h [new file with mode: 0644]
csum-file.c
csum-file.h
ctype.c
daemon.c
date.c
diff.c
diff.h
diffcore-pickaxe.c
dir.c
dir.h
environment.c
fast-import.c
fmt-merge-msg.h [new file with mode: 0644]
gettext.c
gettext.h
git-am.sh
git-compat-util.h
git-cvsexportcommit.perl
git-cvsserver.perl
git-difftool--helper.sh
git-difftool.perl
git-gui/.gitattributes
git-gui/GIT-VERSION-GEN
git-gui/git-gui.sh
git-gui/lib/blame.tcl
git-gui/lib/browser.tcl
git-gui/lib/choose_rev.tcl
git-gui/lib/class.tcl
git-gui/lib/commit.tcl
git-gui/lib/diff.tcl
git-gui/lib/index.tcl
git-gui/lib/line.tcl [new file with mode: 0644]
git-gui/lib/option.tcl
git-gui/lib/search.tcl
git-gui/lib/sshkey.tcl
git-gui/lib/themed.tcl
git-gui/lib/tools.tcl
git-gui/lib/transport.tcl
git-gui/po/README
git-gui/po/sv.po
git-mergetool.sh
git-pull.sh
git-rebase--interactive.sh
git-rebase--merge.sh
git-request-pull.sh
git-send-email.perl
git-sh-i18n.sh
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/git/git.py
gitk-git/gitk
gitweb/INSTALL
gitweb/Makefile
gitweb/README
gitweb/gitweb.perl
gitweb/static/gitweb.css
gpg-interface.c [new file with mode: 0644]
gpg-interface.h [new file with mode: 0644]
grep.c
grep.h
help.c
hex.c
http-backend.c
http-fetch.c
http-push.c
http.c
http.h
ident.c
imap-send.c
kwset.c
list-objects.c
list-objects.h
log-tree.c
mailmap.c
merge-recursive.c
mergetools/bc3
mergetools/meld
name-hash.c
notes-cache.c
notes-merge.c
notes-merge.h
object.c
pack-check.c
pack-refs.c
pack-write.c
pack.h
pager.c
parse-options-cb.c
parse-options.c
parse-options.h
path.c
perl/Git.pm
perl/Git/I18N.pm [new file with mode: 0644]
perl/Makefile
perl/Makefile.PL
po/.gitignore
po/README [new file with mode: 0644]
po/is.po [new file with mode: 0644]
pretty.c
prompt.c [new file with mode: 0644]
prompt.h [new file with mode: 0644]
reachable.c
reachable.h
read-cache.c
reflog-walk.c
reflog-walk.h
refs.c
refs.h
remote-curl.c
remote.c
remote.h
revision.c
revision.h
run-command.c
run-command.h
sequencer.c [new file with mode: 0644]
sequencer.h [new file with mode: 0644]
setup.c
sha1_file.c
sha1_name.c
shell.c
show-index.c
strbuf.c
strbuf.h
submodule.c
submodule.h
t/Makefile
t/README
t/gitweb-lib.sh
t/harness [deleted file]
t/lib-credential.sh [new file with mode: 0755]
t/lib-gettext.sh [new file with mode: 0644]
t/lib-git-p4.sh [new file with mode: 0644]
t/lib-gpg.sh [new file with mode: 0755]
t/lib-gpg/pubring.gpg [new file with mode: 0644]
t/lib-gpg/random_seed [new file with mode: 0644]
t/lib-gpg/secring.gpg [new file with mode: 0644]
t/lib-gpg/trustdb.gpg [new file with mode: 0644]
t/lib-httpd.sh
t/lib-httpd/apache.conf
t/t0003-attributes.sh
t/t0040-parse-options.sh
t/t0090-cache-tree.sh [new file with mode: 0755]
t/t0200-gettext-basic.sh [new file with mode: 0755]
t/t0200/test.c [new file with mode: 0644]
t/t0200/test.perl [new file with mode: 0644]
t/t0200/test.sh [new file with mode: 0644]
t/t0201-gettext-fallbacks.sh
t/t0202-gettext-perl.sh [new file with mode: 0755]
t/t0202/test.pl [new file with mode: 0644]
t/t0203-gettext-setlocale-sanity.sh [new file with mode: 0755]
t/t0204-gettext-reencode-sanity.sh [new file with mode: 0755]
t/t0205-gettext-poison.sh [new file with mode: 0755]
t/t0300-credentials.sh [new file with mode: 0755]
t/t0301-credential-cache.sh [new file with mode: 0755]
t/t0302-credential-store.sh [new file with mode: 0755]
t/t0303-credential-external.sh [new file with mode: 0755]
t/t1007-hash-object.sh
t/t1011-read-tree-sparse-checkout.sh
t/t1013-loose-object-format.sh
t/t1020-subdirectory.sh
t/t1050-large.sh
t/t1051-large-conversion.sh [new file with mode: 0755]
t/t1200-tutorial.sh
t/t1300-repo-config.sh
t/t1402-check-ref-format.sh
t/t1412-reflog-loop.sh
t/t1450-fsck.sh
t/t1501-worktree.sh
t/t1510-repo-setup.sh
t/t1511-rev-parse-caret.sh
t/t2015-checkout-unborn.sh
t/t2018-checkout-branch.sh
t/t2020-checkout-detach.sh
t/t2023-checkout-m.sh [new file with mode: 0755]
t/t2203-add-intent.sh
t/t3000-ls-files-others.sh
t/t3030-merge-recursive.sh
t/t3040-subprojects-basic.sh
t/t3200-branch.sh
t/t3203-branch-output.sh
t/t3300-funny-names.sh
t/t3310-notes-merge-manual-resolve.sh
t/t3400-rebase.sh
t/t3401-rebase-partial.sh
t/t3418-rebase-continue.sh
t/t3419-rebase-patch-id.sh
t/t3502-cherry-pick-merge.sh
t/t3507-cherry-pick-conflict.sh
t/t3508-cherry-pick-many-commits.sh
t/t3510-cherry-pick-sequence.sh [new file with mode: 0755]
t/t3700-add.sh
t/t3900-i18n-commit.sh
t/t3903-stash.sh
t/t3904-stash-patch.sh
t/t3905-stash-include-untracked.sh
t/t4011-diff-symlink.sh
t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master
t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side
t/t4013/diff.diff-tree_--cc_--patch-with-stat_master
t/t4013/diff.diff-tree_--cc_--stat_--summary_master
t/t4013/diff.diff-tree_--cc_--stat_--summary_side
t/t4013/diff.diff-tree_--cc_--stat_master
t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-stat_initial
t/t4013/diff.diff-tree_--pretty_--patch-with-stat_side
t/t4013/diff.diff-tree_--pretty_--root_--patch-with-stat_initial
t/t4013/diff.diff-tree_--pretty_--root_--stat_--summary_initial
t/t4013/diff.diff-tree_--pretty_--root_--stat_initial
t/t4013/diff.diff-tree_--root_--patch-with-stat_initial
t/t4013/diff.diff-tree_-c_--stat_--summary_master
t/t4013/diff.diff-tree_-c_--stat_--summary_side
t/t4013/diff.diff-tree_-c_--stat_master
t/t4013/diff.diff_--patch-with-stat_-r_initial..side
t/t4013/diff.diff_--patch-with-stat_initial..side
t/t4013/diff.diff_--stat_initial..side
t/t4013/diff.diff_-r_--stat_initial..side
t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side
t/t4013/diff.format-patch_--attach_--stdout_initial..master
t/t4013/diff.format-patch_--attach_--stdout_initial..master^
t/t4013/diff.format-patch_--attach_--stdout_initial..side
t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master
t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master
t/t4013/diff.format-patch_--inline_--stdout_initial..master
t/t4013/diff.format-patch_--inline_--stdout_initial..master^
t/t4013/diff.format-patch_--inline_--stdout_initial..side
t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
t/t4013/diff.format-patch_--stdout_--no-numbered_initial..master
t/t4013/diff.format-patch_--stdout_--numbered_initial..master
t/t4013/diff.format-patch_--stdout_initial..master
t/t4013/diff.format-patch_--stdout_initial..master^
t/t4013/diff.format-patch_--stdout_initial..side
t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
t/t4013/diff.log_--patch-with-stat_master
t/t4013/diff.log_--patch-with-stat_master_--_dir_
t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_--patch-with-stat_master
t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
t/t4013/diff.show_--patch-with-stat_--summary_side
t/t4013/diff.show_--patch-with-stat_side
t/t4013/diff.show_--stat_--summary_side
t/t4013/diff.show_--stat_side
t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_
t/t4013/diff.whatchanged_--patch-with-stat_master
t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_
t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master
t/t4013/diff.whatchanged_--root_--patch-with-stat_master
t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
t/t4014-format-patch.sh
t/t4018-diff-funcname.sh
t/t4030-diff-textconv.sh
t/t4034-diff-words.sh
t/t4034/matlab/expect [new file with mode: 0644]
t/t4034/matlab/post [new file with mode: 0644]
t/t4034/matlab/pre [new file with mode: 0644]
t/t4045-diff-relative.sh
t/t4049-diff-stat-count.sh
t/t4051-diff-function-context.sh [new file with mode: 0755]
t/t4100/t-apply-8.expect
t/t4100/t-apply-9.expect
t/t4131-apply-fake-ancestor.sh
t/t4136-apply-check.sh [new file with mode: 0755]
t/t4150-am.sh
t/t4202-log.sh
t/t5000-tar-tree.sh
t/t5001-archive-attr.sh
t/t5150-request-pull.sh
t/t5403-post-checkout-hook.sh
t/t5500-fetch-pack.sh
t/t5501-fetch-push-alternates.sh
t/t5504-fetch-receive-strict.sh [new file with mode: 0755]
t/t5510-fetch.sh
t/t5512-ls-remote.sh
t/t5515/fetch.br-branches-default
t/t5515/fetch.br-branches-default-merge
t/t5515/fetch.br-branches-default-merge_branches-default
t/t5515/fetch.br-branches-default-octopus
t/t5515/fetch.br-branches-default-octopus_branches-default
t/t5515/fetch.br-branches-default_branches-default
t/t5515/fetch.br-branches-one
t/t5515/fetch.br-branches-one-merge
t/t5515/fetch.br-branches-one-merge_branches-one
t/t5515/fetch.br-branches-one-octopus
t/t5515/fetch.br-branches-one-octopus_branches-one
t/t5515/fetch.br-branches-one_branches-one
t/t5515/fetch.br-config-explicit
t/t5515/fetch.br-config-explicit-merge
t/t5515/fetch.br-config-explicit-merge_config-explicit
t/t5515/fetch.br-config-explicit-octopus
t/t5515/fetch.br-config-explicit-octopus_config-explicit
t/t5515/fetch.br-config-explicit_config-explicit
t/t5515/fetch.br-config-glob
t/t5515/fetch.br-config-glob-merge
t/t5515/fetch.br-config-glob-merge_config-glob
t/t5515/fetch.br-config-glob-octopus
t/t5515/fetch.br-config-glob-octopus_config-glob
t/t5515/fetch.br-config-glob_config-glob
t/t5515/fetch.br-remote-explicit
t/t5515/fetch.br-remote-explicit-merge
t/t5515/fetch.br-remote-explicit-merge_remote-explicit
t/t5515/fetch.br-remote-explicit-octopus
t/t5515/fetch.br-remote-explicit-octopus_remote-explicit
t/t5515/fetch.br-remote-explicit_remote-explicit
t/t5515/fetch.br-remote-glob
t/t5515/fetch.br-remote-glob-merge
t/t5515/fetch.br-remote-glob-merge_remote-glob
t/t5515/fetch.br-remote-glob-octopus
t/t5515/fetch.br-remote-glob-octopus_remote-glob
t/t5515/fetch.br-remote-glob_remote-glob
t/t5515/fetch.br-unconfig
t/t5515/fetch.br-unconfig_--tags_.._.git
t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file
t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file
t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three
t/t5515/fetch.br-unconfig_branches-default
t/t5515/fetch.br-unconfig_branches-one
t/t5515/fetch.br-unconfig_config-explicit
t/t5515/fetch.br-unconfig_config-glob
t/t5515/fetch.br-unconfig_remote-explicit
t/t5515/fetch.br-unconfig_remote-glob
t/t5515/fetch.master
t/t5515/fetch.master_--tags_.._.git
t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file
t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file
t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three
t/t5515/fetch.master_branches-default
t/t5515/fetch.master_branches-one
t/t5515/fetch.master_config-explicit
t/t5515/fetch.master_config-glob
t/t5515/fetch.master_remote-explicit
t/t5515/fetch.master_remote-glob
t/t5516-fetch-push.sh
t/t5520-pull.sh
t/t5523-push-upstream.sh
t/t5527-fetch-odd-refs.sh [new file with mode: 0755]
t/t5540-http-push.sh
t/t5541-http-push.sh
t/t5550-http-fetch.sh
t/t5560-http-backend-noserver.sh
t/t5601-clone.sh
t/t5700-clone-reference.sh
t/t5704-bundle.sh
t/t5900-repo-selection.sh [new file with mode: 0755]
t/t6006-rev-list-format.sh
t/t6012-rev-list-simplify.sh
t/t6030-bisect-porcelain.sh
t/t6032-merge-large-rename.sh
t/t6040-tracking-info.sh
t/t6300-for-each-ref.sh
t/t7004-tag.sh
t/t7004/pubring.gpg [deleted file]
t/t7004/random_seed [deleted file]
t/t7004/secring.gpg [deleted file]
t/t7004/trustdb.gpg [deleted file]
t/t7006-pager.sh
t/t7008-grep-binary.sh
t/t7400-submodule-basic.sh
t/t7403-submodule-sync.sh
t/t7405-submodule-merge.sh
t/t7406-submodule-update.sh
t/t7407-submodule-foreach.sh
t/t7408-submodule-reference.sh
t/t7501-commit.sh
t/t7508-status.sh
t/t7510-signed-commit.sh [new file with mode: 0755]
t/t7600-merge.sh
t/t7602-merge-octopus-many.sh
t/t7604-merge-custom-message.sh
t/t7608-merge-messages.sh
t/t7610-mergetool.sh
t/t7800-difftool.sh
t/t7810-grep.sh
t/t8006-blame-textconv.sh
t/t9001-send-email.sh
t/t9130-git-svn-authors-file.sh
t/t9162-git-svn-dcommit-interactive.sh [new file with mode: 0755]
t/t9200-git-cvsexportcommit.sh
t/t9300-fast-import.sh
t/t9301-fast-import-notes.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9501-gitweb-standalone-http-status.sh
t/t9700-perl-git.sh
t/t9700/test.pl
t/t9800-git-p4-basic.sh [new file with mode: 0755]
t/t9800-git-p4.sh [deleted file]
t/t9801-git-p4-branch.sh [new file with mode: 0755]
t/t9802-git-p4-filetype.sh [new file with mode: 0755]
t/t9803-git-p4-shell-metachars.sh [new file with mode: 0755]
t/t9805-git-p4-skip-submit-edit.sh [new file with mode: 0755]
t/t9806-git-p4-options.sh [new file with mode: 0755]
t/t9807-git-p4-submit.sh [new file with mode: 0755]
t/t9808-git-p4-chdir.sh [new file with mode: 0755]
t/t9809-git-p4-client-view.sh [new file with mode: 0755]
t/t9901-git-web--browse.sh [new file with mode: 0755]
t/test-lib.sh
t/test-terminal.perl
tag.c
test-credential.c [new file with mode: 0644]
test-ctype.c
test-dump-cache-tree.c
test-parse-options.c
test-scrap-cache-tree.c [new file with mode: 0644]
test-treap.c
transport-helper.c
transport.c
tree-diff.c
tree-walk.c
tree-walk.h
tree.c
unix-socket.c [new file with mode: 0644]
unix-socket.h [new file with mode: 0644]
unpack-trees.c
upload-pack.c
url.c
url.h
userdiff.c
vcs-svn/repo_tree.c
vcs-svn/string_pool.c
walker.c
wrap-for-bin.sh
wt-status.c
xdiff/xdiff.h
xdiff/xemit.c
zlib.c

index 8572c8c0b0199589a8c1875825f5b2e7e4dc4a86..3b7680ea1e5baa58430798a9836481975dbb6234 100644 (file)
@@ -30,6 +30,9 @@
 /git-commit-tree
 /git-config
 /git-count-objects
+/git-credential-cache
+/git-credential-cache--daemon
+/git-credential-store
 /git-cvsexportcommit
 /git-cvsimport
 /git-cvsserver
 /gitweb/static/gitweb.js
 /gitweb/static/gitweb.min.*
 /test-chmtime
+/test-credential
 /test-ctype
 /test-date
 /test-delta
 /test-dump-cache-tree
+/test-scrap-cache-tree
 /test-genrandom
 /test-index-version
 /test-line-buffer
index fe1c1e5bc26e683540cb9fe5f43320192be9185d..45577117c2a02dd4a4f9e63e78139b3df665b8f2 100644 (file)
@@ -35,10 +35,22 @@ For shell scripts specifically (not exhaustive):
 
  - Case arms are indented at the same depth as case and esac lines.
 
+ - Redirection operators should be written with space before, but no
+   space after them.  In other words, write 'echo test >"$file"'
+   instead of 'echo test> $file' or 'echo test > $file'.  Note that
+   even though it is not required by POSIX to double-quote the
+   redirection target in a variable (as shown above), our code does so
+   because some versions of bash issue a warning without the quotes.
+
  - We prefer $( ... ) for command substitution; unlike ``, it
    properly nests.  It should have been the way Bourne spelled
    it from day one, but unfortunately isn't.
 
+ - If you want to find out if a command is available on the user's
+   $PATH, you should use 'type <command>', instead of 'which <command>'.
+   The output of 'which' is not machine parseable and its exit code
+   is not reliable across platforms.
+
  - We use POSIX compliant parameter substitutions and avoid bashisms;
    namely:
 
@@ -81,6 +93,10 @@ For shell scripts specifically (not exhaustive):
      are ERE elements not BRE (note that \? and \+ are not even part
      of BRE -- making them accessible from BRE is a GNU extension).
 
+ - Use Git's gettext wrappers in git-sh-i18n to make the user
+   interface translatable. See "Marking strings for translation" in
+   po/README.
+
 For C programs:
 
  - We use tabs to indent, and interpret tabs as taking up to
@@ -144,6 +160,9 @@ For C programs:
  - When we pass <string, length> pair to functions, we should try to
    pass them in that order.
 
+ - Use Git's gettext wrappers to make the user interface
+   translatable. See "Marking strings for translation" in po/README.
+
 Writing Documentation:
 
  Every user-visible change should be reflected in the documentation.
index 5a340fd492b41f183978ee273f64b01aedebddd4..d40e211f22dbe6c102a102041ac9f0779c7cb837 100644 (file)
@@ -1,12 +1,13 @@
 MAN1_TXT= \
        $(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
                $(wildcard git-*.txt)) \
-       gitk.txt git.txt
+       gitk.txt gitweb.txt git.txt
 MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \
-       gitrepository-layout.txt
+       gitrepository-layout.txt gitweb.conf.txt
 MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \
        gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \
        gitdiffcore.txt gitnamespaces.txt gitrevisions.txt gitworkflows.txt
+MAN7_TXT += gitcredentials.txt
 
 MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
 MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
@@ -19,7 +20,10 @@ 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
+SP_ARTICLES = user-manual
+SP_ARTICLES += howto/revert-branch-rebase
+SP_ARTICLES += howto/using-merge-subtree
+SP_ARTICLES += howto/using-signed-tag-in-pull-request
 API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
 SP_ARTICLES += $(API_DOCS)
 SP_ARTICLES += technical/api-index
diff --git a/Documentation/RelNotes/1.7.8.1.txt b/Documentation/RelNotes/1.7.8.1.txt
new file mode 100644 (file)
index 0000000..33dc948
--- /dev/null
@@ -0,0 +1,38 @@
+Git v1.7.8.1 Release Notes
+==========================
+
+Fixes since v1.7.8
+------------------
+
+ * In some codepaths (notably, checkout and merge), the ignore patterns
+   recorded in $GIT_DIR/info/exclude were not honored. They now are.
+
+ * "git apply --check" did not error out when given an empty input
+   without any patch.
+
+ * "git archive" mistakenly allowed remote clients to ask for commits
+   that are not at the tip of any ref.
+
+ * "git checkout" and "git merge" treated in-tree .gitignore and exclude
+   file in $GIT_DIR/info/ directory inconsistently when deciding which
+   untracked files are ignored and expendable.
+
+ * LF-to-CRLF streaming filter used when checking out a large-ish blob
+   fell into an infinite loop with a rare input.
+
+ * The function header pattern for files with "diff=cpp" attribute did
+   not consider "type *funcname(type param1,..." as the beginning of a
+   function.
+
+ * The error message from "git diff" and "git status" when they fail
+   to inspect changes in submodules did not report which submodule they
+   had trouble with.
+
+ * After fetching from a remote that has very long refname, the reporting
+   output could have corrupted by overrunning a static buffer.
+
+ * "git pack-objects" avoids creating cyclic dependencies among deltas
+   when seeing a broken packfile that records the same object in both
+   the deflated form and as a delta.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.2.txt b/Documentation/RelNotes/1.7.8.2.txt
new file mode 100644 (file)
index 0000000..e74f4ef
--- /dev/null
@@ -0,0 +1,71 @@
+Git v1.7.8.2 Release Notes
+==========================
+
+Fixes since v1.7.8.1
+--------------------
+
+ * Porcelain commands like "git reset" did not distinguish deletions
+   and type-changes from ordinary modification, and reported them with
+   the same 'M' moniker. They now use 'D' (for deletion) and 'T' (for
+   type-change) to match "git status -s" and "git diff --name-status".
+
+ * The configuration file parser used for sizes (e.g. bigFileThreshold)
+   did not correctly interpret 'g' suffix.
+
+ * The replacement implemention for snprintf used on platforms with
+   native snprintf that is broken did not use va_copy correctly.
+
+ * LF-to-CRLF streaming filter replaced all LF with CRLF, which might
+   be techinically correct but not friendly to people who are trying
+   to recover from earlier mistakes of using CRLF in the repository
+   data in the first place. It now refrains from doing so for LF that
+   follows a CR.
+
+ * git native connection going over TCP (not over SSH) did not set
+   SO_KEEPALIVE option which failed to receive link layer errors.
+
+ * "git branch -m <current branch> HEAD" is an obvious no-op but was not
+   allowed.
+
+ * "git checkout -m" did not recreate the conflicted state in a "both
+   sides added, without any common ancestor version" conflict
+   situation.
+
+ * "git cherry-pick $commit" (not a range) created an unnecessary
+   sequencer state and interfered with valid workflow to use the
+   command during a session to cherry-pick multiple commits.
+
+ * You could make "git commit" segfault by giving the "--no-message"
+   option.
+
+ * "fast-import" did not correctly update an existing notes tree,
+   possibly corrupting the fan-out.
+
+ * "git fetch-pack" accepted unqualified refs that do not begin with
+   refs/ by mistake and compensated it by matching the refspec with
+   tail-match, which was doubly wrong. This broke fetching from a
+   repository with a funny named ref "refs/foo/refs/heads/master" and a
+   'master' branch with "git fetch-pack refs/heads/master", as the
+   command incorrectly considered the former a "match".
+
+ * "git log --follow" did not honor the rename threshold score given
+   with the -M option (e.g. "-M50%").
+
+ * "git mv" gave suboptimal error/warning messages when it overwrites
+   target files. It also did not pay attention to "-v" option.
+
+ * Authenticated "git push" over dumb HTTP were broken with a recent
+   change and failed without asking for password when username is
+   given.
+
+ * "git push" to an empty repository over HTTP were broken with a
+   recent change to the ref handling.
+
+ * "git push -v" forgot how to be verbose by mistake. It now properly
+   becomes verbose when asked to.
+
+ * When a "reword" action in "git rebase -i" failed to run "commit --amend",
+   we did not give the control back to the user to resolve the situation, and
+   instead kept the original commit log message.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.3.txt b/Documentation/RelNotes/1.7.8.3.txt
new file mode 100644 (file)
index 0000000..a92714c
--- /dev/null
@@ -0,0 +1,16 @@
+Git v1.7.8.3 Release Notes
+==========================
+
+Fixes since v1.7.8.2
+--------------------
+
+ * Attempt to fetch from an empty file pretending it to be a bundle did
+   not error out correctly.
+
+ * gitweb did not correctly fall back to configured $fallback_encoding
+   that is not 'latin1'.
+
+ * "git clone --depth $n" did not catch a non-number given as $n as an
+   error.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.4.txt b/Documentation/RelNotes/1.7.8.4.txt
new file mode 100644 (file)
index 0000000..9bebdbf
--- /dev/null
@@ -0,0 +1,23 @@
+Git v1.7.8.4 Release Notes
+==========================
+
+Fixes since v1.7.8.3
+--------------------
+
+ * The code to look up attributes for paths reused entries from a wrong
+   directory when two paths in question are in adjacent directories and
+   the name of the one directory is a prefix of the other.
+
+ * A wildcard that matches deeper hierarchy given to the "diff-index" command,
+   e.g. "git diff-index HEAD -- '*.txt'", incorrectly reported additions of
+   matching files even when there is no change.
+
+ * When producing a "thin pack" (primarily used in bundles and smart
+   HTTP transfers) out of a fully packed repository, we unnecessarily
+   avoided sending recent objects as a delta against objects we know
+   the other side has.
+
+ * "git send-email" did not properly treat sendemail.multiedit as a
+   boolean (e.g. setting it to "false" did not turn it off).
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.5.txt b/Documentation/RelNotes/1.7.8.5.txt
new file mode 100644 (file)
index 0000000..011fd2a
--- /dev/null
@@ -0,0 +1,19 @@
+Git v1.7.8.5 Release Notes
+==========================
+
+Fixes since v1.7.8.4
+--------------------
+
+ * Dependency on our thread-utils.h header file was missing for
+   objects that depend on it in the Makefile.
+
+ * "git am" when fed an empty file did not correctly finish reading it
+   when it attempts to guess the input format.
+
+ * "git grep -P" (when PCRE is enabled in the build) did not match the
+   beginning and the end of the line correctly with ^ and $.
+
+ * "git rebase -m" tried to run "git notes copy" needlessly when
+   nothing was rewritten.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.txt b/Documentation/RelNotes/1.7.8.txt
new file mode 100644 (file)
index 0000000..b4d90bb
--- /dev/null
@@ -0,0 +1,161 @@
+Git v1.7.8 Release Notes
+========================
+
+Updates since v1.7.7
+--------------------
+
+ * Some git-svn, git-gui, git-p4 (in contrib) and msysgit updates.
+
+ * Updates to bash completion scripts.
+
+ * The build procedure has been taught to take advantage of computed
+   dependency automatically when the complier supports it.
+
+ * The date parser now accepts timezone designators that lack minutes
+   part and also has a colon between "hh:mm".
+
+ * The contents of the /etc/mailname file, if exists, is used as the
+   default value of the hostname part of the committer/author e-mail.
+
+ * "git am" learned how to read from patches generated by Hg.
+
+ * "git archive" talking with a remote repository can report errors
+   from the remote side in a more informative way.
+
+ * "git branch" learned an explicit --list option to ask for branches
+   listed, optionally with a glob matching pattern to limit its output.
+
+ * "git check-attr" learned "--cached" option to look at .gitattributes
+   files from the index, not from the working tree.
+
+ * Variants of "git cherry-pick" and "git revert" that take multiple
+   commits learned to "--continue" and "--abort".
+
+ * "git daemon" gives more human readble error messages to clients
+   using ERR packets when appropriate.
+
+ * Errors at the network layer is logged by "git daemon".
+
+ * "git diff" learned "--minimal" option to spend extra cycles to come
+   up with a minimal patch output.
+
+ * "git diff" learned "--function-context" option to show the whole
+   function as context that was affected by a change.
+
+ * "git difftool" can be told to skip launching the tool for a path by
+   answering 'n' to its prompt.
+
+ * "git fetch" learned to honor transfer.fsckobjects configuration to
+   validate the objects that were received from the other end, just like
+   "git receive-pack" (the receiving end of "git push") does.
+
+ * "git fetch" makes sure that the set of objects it received from the
+   other end actually completes the history before updating the refs.
+   "git receive-pack" (the receiving end of "git push") learned to do the
+   same.
+
+ * "git fetch" learned that fetching/cloning from a regular file on the
+   filesystem is not necessarily a request to unpack a bundle file; the
+   file could be ".git" with "gitdir: <path>" in it.
+
+ * "git for-each-ref" learned "%(contents:subject)", "%(contents:body)"
+   and "%(contents:signature)". The last one is useful for signed tags.
+
+ * "git grep" used to incorrectly pay attention to .gitignore files
+   scattered in the directory it was working in even when "--no-index"
+   option was used. It no longer does this. The "--exclude-standard"
+   option needs to be given to explicitly activate the ignore
+   mechanism.
+
+ * "git grep" learned "--untracked" option, where given patterns are
+    searched in untracked (but not ignored) files as well as tracked
+    files in the working tree, so that matches in new but not yet
+    added files do not get missed.
+
+ * The recursive merge backend no longer looks for meaningless
+   existing merges in submodules unless in the outermost merge.
+
+ * "git log" and friends learned "--children" option.
+
+ * "git ls-remote" learned to respond to "-h"(elp) requests.
+
+ * "mediawiki" remote helper can interact with (surprise!) MediaWiki
+   with "git fetch" & "git push".
+
+ * "git merge" learned the "--edit" option to allow users to edit the
+   merge commit log message.
+
+ * "git rebase -i" can be told to use special purpose editor suitable
+   only for its insn sheet via sequence.editor configuration variable.
+
+ * "git send-email" learned to respond to "-h"(elp) requests.
+
+ * "git send-email" allows the value given to sendemail.aliasfile to begin
+   with "~/" to refer to the $HOME directory.
+
+ * "git send-email" forces use of Authen::SASL::Perl to work around
+   issues between Authen::SASL::Cyrus and AUTH PLAIN/LOGIN.
+
+ * "git stash" learned "--include-untracked" option to stash away
+   untracked/ignored cruft from the working tree.
+
+ * "git submodule clone" does not leak an error message to the UI
+   level unnecessarily anymore.
+
+ * "git submodule update" learned to honor "none" as the value for
+   submodule.<name>.update to specify that the named submodule should
+   not be checked out by default.
+
+ * When populating a new submodule directory with "git submodule init",
+   the $GIT_DIR metainformation directory for submodules is created inside
+   $GIT_DIR/modules/<name>/ directory of the superproject and referenced
+   via the gitfile mechanism. This is to make it possible to switch
+   between commits in the superproject that has and does not have the
+   submodule in the tree without re-cloning.
+
+ * "gitweb" leaked unescaped control characters from syntax hiliter
+   outputs.
+
+ * "gitweb" can be told to give custom string at the end of the HTML
+   HEAD element.
+
+ * "gitweb" now has its own manual pages.
+
+
+Also contains other documentation updates and minor code cleanups.
+
+
+Fixes since v1.7.7
+------------------
+
+Unless otherwise noted, all fixes in the 1.7.7.X maintenance track are
+included in this release.
+
+ * HTTP transport did not use pushurl correctly, and also did not tell
+   what host it is trying to authenticate with when asking for
+   credentials.
+   (merge deba493 jk/http-auth later to maint).
+
+ * "git blame" was aborted if started from an uncommitted content and
+   the path had the textconv filter in effect.
+   (merge 8518088 ss/blame-textconv-fake-working-tree later to maint).
+
+ * Adding many refs to the local repository in one go (e.g. "git fetch"
+   that fetches many tags) and looking up a ref by name in a repository
+   with too many refs were unnecessarily slow.
+   (merge 17d68a54d jp/get-ref-dir-unsorted later to maint).
+
+ * Report from "git commit" on untracked files was confused under
+   core.ignorecase option.
+   (merge 395c7356 jk/name-hash-dirent later to maint).
+
+ * "git merge" did not understand ":/<pattern>" as a way to name a commit.
+
+ " "git push" on the receiving end used to call post-receive and post-update
+   hooks for attempted removal of non-existing refs.
+   (merge 160b81ed ph/push-to-delete-nothing later to maint).
+
+ * Help text for "git remote set-url" and "git remote set-branches"
+   were misspelled.
+   (merge c49904e fc/remote-seturl-usage-fix later to maint).
+   (merge 656cdf0 jc/remote-setbranches-usage-fix later to maint).
diff --git a/Documentation/RelNotes/1.7.9.1.txt b/Documentation/RelNotes/1.7.9.1.txt
new file mode 100644 (file)
index 0000000..6957183
--- /dev/null
@@ -0,0 +1,63 @@
+Git v1.7.9.1 Release Notes
+==========================
+
+Fixes since v1.7.9
+------------------
+
+ * The makefile allowed environment variable X seep into it result in
+   command names suffixed with unnecessary strings.
+
+ * The set of included header files in compat/inet-{ntop,pton}
+   wrappers was updated for Windows some time ago, but in a way that
+   broke Solaris build.
+
+ * rpmbuild noticed an unpackaged but installed *.mo file and failed.
+
+ * Subprocesses spawned from various git programs were often left running
+   to completion even when the top-level process was killed.
+
+ * "git add -e" learned not to show a diff for an otherwise unmodified
+   submodule that only has uncommitted local changes in the patch
+   prepared by for the user to edit.
+
+ * Typo in "git branch --edit-description my-tpoic" was not diagnosed.
+
+ * Using "git grep -l/-L" together with options -W or --break may not
+   make much sense as the output is to only count the number of hits
+   and there is no place for file breaks, but the latter options made
+   "-l/-L" to miscount the hits.
+
+ * "git log --first-parent $pathspec" did not stay on the first parent
+   chain and veered into side branch from which the whole change to the
+   specified paths came.
+
+ * "git merge --no-edit $tag" failed to honor the --no-edit option.
+
+ * "git merge --ff-only $tag" failed because it cannot record the
+   required mergetag without creating a merge, but this is so common
+   operation for branch that is used _only_ to follow the upstream, so
+   it was changed to allow fast-forwarding without recording the mergetag.
+
+ * "git mergetool" now gives an empty file as the common base version
+   to the backend when dealing with the "both sides added, differently"
+   case.
+
+ * "git push -q" was not sufficiently quiet.
+
+ * When "git push" fails to update any refs, the client side did not
+   report an error correctly to the end user.
+
+ * "rebase" and "commit --amend" failed to work on commits with ancient
+   timestamps near year 1970.
+
+ * When asking for a tag to be pulled, "request-pull" did not show the
+   name of the tag prefixed with "tags/", which would have helped older
+   clients.
+
+ * "git submodule add $path" forgot to recompute the name to be stored
+   in .gitmodules when the submodule at $path was once added to the
+   superproject and already initialized.
+
+ * Many small corner case bugs on "git tag -n" was corrected.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.9.2.txt b/Documentation/RelNotes/1.7.9.2.txt
new file mode 100644 (file)
index 0000000..e500da7
--- /dev/null
@@ -0,0 +1,69 @@
+Git v1.7.9.2 Release Notes
+==========================
+
+Fixes since v1.7.9.1
+--------------------
+
+ * Bash completion script (in contrib/) did not like a pattern that
+   begins with a dash to be passed to __git_ps1 helper function.
+
+ * Adaptation of the bash completion script (in contrib/) for zsh
+   incorrectly listed all subcommands when "git <TAB><TAB>" was given
+   to ask for list of porcelain subcommands.
+
+ * The build procedure for profile-directed optimized binary was not
+   working very well.
+
+ * Some systems need to explicitly link -lcharset to get locale_charset().
+
+ * t5541 ignored user-supplied port number used for HTTP server testing.
+
+ * The error message emitted when we see an empty loose object was
+   not phrased correctly.
+
+ * The code to ask for password did not fall back to the terminal
+   input when GIT_ASKPASS is set but does not work (e.g. lack of X
+   with GUI askpass helper).
+
+ * We failed to give the true terminal width to any subcommand when
+   they are invoked with the pager, i.e. "git -p cmd".
+
+ * map_user() was not rewriting its output correctly, which resulted
+   in the user visible symptom that "git blame -e" sometimes showed
+   excess '>' at the end of email addresses.
+
+ * "git checkout -b" did not allow switching out of an unborn branch.
+
+ * When you have both .../foo and .../foo.git, "git clone .../foo" did not
+   favor the former but the latter.
+
+ * "git commit" refused to create a commit when entries added with
+   "add -N" remained in the index, without telling Git what their content
+   in the next commit should be. We should have created the commit without
+   these paths.
+
+ * "git diff --stat" said "files", "insertions", and "deletions" even
+   when it is showing one "file", one "insertion" or one "deletion".
+
+ * The output from "git diff --stat" for two paths that have the same
+   amount of changes showed graph bars of different length due to the
+   way we handled rounding errors.
+
+ * "git grep" did not pay attention to -diff (hence -binary) attribute.
+
+ * The transport programs (fetch, push, clone)ignored --no-progress
+   and showed progress when sending their output to a terminal.
+
+ * Sometimes error status detected by a check in an earlier phase of
+   "git receive-pack" (the other end of "git push") was lost by later
+   checks, resulting in false indication of success.
+
+ * "git rev-list --verify" sometimes skipped verification depending on
+   the phase of the moon, which dates back to 1.7.8.x series.
+
+ * Search box in "gitweb" did not accept non-ASCII characters correctly.
+
+ * Search interface of "gitweb" did not show multiple matches in the same file
+   correctly.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.9.3.txt b/Documentation/RelNotes/1.7.9.3.txt
new file mode 100644 (file)
index 0000000..91c6501
--- /dev/null
@@ -0,0 +1,51 @@
+Git v1.7.9.3 Release Notes
+==========================
+
+Fixes since v1.7.9.2
+--------------------
+
+ * "git p4" (in contrib/) submit the changes to a wrong place when the
+   "--use-client-spec" option is set.
+
+ * The config.mak.autogen generated by optional autoconf support tried
+   to link the binary with -lintl even when libintl.h is missing from
+   the system.
+
+ * When the filter driver exits before reading the content before the
+   main git process writes the contents to be filtered to the pipe to
+   it, the latter could be killed with SIGPIPE instead of ignoring
+   such an event as an error.
+
+ * "git add --refresh <pathspec>" used to warn about unmerged paths
+   outside the given pathspec.
+
+ * The bulk check-in codepath in "git add" streamed contents that
+   needs smudge/clean filters without running them, instead of punting
+   and delegating to the codepath to run filters after slurping
+   everything to core.
+
+ * "git branch --with $that" assumed incorrectly that the user will never
+   ask the question with nonsense value in $that.
+
+ * "git bundle create" produced a corrupt bundle file upon seeing
+   commits with excessively long subject line.
+
+ * When a remote helper exits before reading the blank line from the
+   main git process to signal the end of commands, the latter could be
+   killed with SIGPIPE. Instead we should ignore such event as a
+   non-error.
+
+ * The commit log template given with "git merge --edit" did not have
+   a short instructive text like what "git commit" gives.
+
+ * "git rev-list --verify-objects -q" omitted the extra verification
+   it needs to do over "git rev-list --objects -q" by mistake.
+
+ * "gitweb" used to drop warnings in the log file when "heads" view is
+   accessed in a repository whose HEAD does not point at a valid
+   branch.
+
+ * An invalid regular expression pattern given by an end user made
+   "gitweb" to return garbled response.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.9.4.txt b/Documentation/RelNotes/1.7.9.4.txt
new file mode 100644 (file)
index 0000000..e5217a1
--- /dev/null
@@ -0,0 +1,24 @@
+Git v1.7.9.4 Release Notes
+==========================
+
+Fixes since v1.7.9.3
+--------------------
+
+ * The code to synthesize the fake ancestor tree used by 3-way merge
+   fallback in "git am" was not prepared to read a patch created with
+   a non-standard -p<num> value.
+
+ * "git bundle" did not record boundary commits correctly when there
+   are many of them.
+
+ * "git diff-index" and its friends at the plumbing level showed the
+   "diff --git" header and nothing else for a path whose cached stat
+   info is dirty without actual difference when asked to produce a
+   patch. This was a longstanding bug that we could have fixed long
+   time ago.
+
+ * "gitweb" did use quotemeta() to prepare search string when asked to
+   do a fixed-string project search, but did not use it by mistake and
+   used the user-supplied string instead.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.9.txt b/Documentation/RelNotes/1.7.9.txt
new file mode 100644 (file)
index 0000000..95320aa
--- /dev/null
@@ -0,0 +1,112 @@
+Git v1.7.9 Release Notes
+========================
+
+Updates since v1.7.8
+--------------------
+
+ * gitk updates accumulated since early 2011.
+
+ * git-gui updated to 0.16.0.
+
+ * git-p4 (in contrib/) updates.
+
+ * Git uses gettext to translate its most common interface messages
+   into the user's language if translations are available and the
+   locale is appropriately set. Distributors can drop new PO files
+   in po/ to add new translations.
+
+ * The code to handle username/password for HTTP transactions used in
+   "git push" & "git fetch" learned to talk "credential API" to
+   external programs to cache or store them, to allow integration with
+   platform native keychain mechanisms.
+
+ * The input prompts in the terminal use our own getpass() replacement
+   when possible. HTTP transactions used to ask for the username without
+   echoing back what was typed, but with this change you will see it as
+   you type.
+
+ * The internals of "revert/cherry-pick" have been tweaked to prepare
+   building more generic "sequencer" on top of the implementation that
+   drives them.
+
+ * "git rev-parse FETCH_HEAD" after "git fetch" without specifying
+   what to fetch from the command line will now show the commit that
+   would be merged if the command were "git pull".
+
+ * "git add" learned to stream large files directly into a packfile
+   instead of writing them into individual loose object files.
+
+ * "git checkout -B <current branch> <elsewhere>" is a more intuitive
+   way to spell "git reset --keep <elsewhere>".
+
+ * "git checkout" and "git merge" learned "--no-overwrite-ignore" option
+   to tell Git that untracked and ignored files are not expendable.
+
+ * "git commit --amend" learned "--no-edit" option to say that the
+   user is amending the tree being recorded, without updating the
+   commit log message.
+
+ * "git commit" and "git reset" re-learned the optimization to prime
+   the cache-tree information in the index, which makes it faster to
+   write a tree object out after the index entries are updated.
+
+ * "git commit" detects and rejects an attempt to stuff NUL byte in
+   the commit log message.
+
+ * "git commit" learned "-S" to GPG-sign the commit; this can be shown
+   with the "--show-signature" option to "git log".
+
+ * fsck and prune are relatively lengthy operations that still go
+   silent while making the end-user wait. They learned to give progress
+   output like other slow operations.
+
+ * The set of built-in function-header patterns for various languages
+   knows MATLAB.
+
+ * "git log --format='<format>'" learned new %g[nNeE] specifiers to
+   show information from the reflog entries when walking the reflog
+   (i.e. with "-g").
+
+ * "git pull" can be used to fetch and merge an annotated/signed tag,
+   instead of the tip of a topic branch. The GPG signature from the
+   signed tag is recorded in the resulting merge commit for later
+   auditing.
+
+ * "git log" learned "--show-signature" option to show the signed tag
+   that was merged that is embedded in the merge commit. It also can
+   show the signature made on the commit with "git commit -S".
+
+ * "git branch --edit-description" can be used to add descriptive text
+   to explain what a topic branch is about.
+
+ * "git fmt-merge-msg" learned to take the branch description into
+   account when preparing a merge summary that "git merge" records
+   when merging a local branch.
+
+ * "git request-pull" has been updated to convey more information
+   useful for integrators to decide if a topic is worth merging and
+   what is pulled is indeed what the requestor asked to pull,
+   including:
+
+   - the tip of the branch being requested to be merged;
+   - the branch description describing what the topic is about;
+   - the contents of the annotated tag, when requesting to pull a tag.
+
+ * "git pull" learned to notice 'pull.rebase' configuration variable,
+   which serves as a global fallback for setting 'branch.<name>.rebase'
+   configuration variable per branch.
+
+ * "git tag" learned "--cleanup" option to control how the whitespaces
+   and empty lines in tag message are cleaned up.
+
+ * "gitweb" learned to show side-by-side diff.
+
+Also contains minor documentation updates and code clean-ups.
+
+
+Fixes since v1.7.8
+------------------
+
+Unless otherwise noted, all the fixes since v1.7.8 in the maintenance
+releases are contained in this release (see release notes to them for
+details).
index e76195ac9721277e9ef270be8eb97dba2462bc37..d4a51da464f5cefea9a5feb4c048b0b0f970b169 100644 (file)
@@ -117,5 +117,4 @@ commit. And the default value is 40. If there are more than one
 take effect.
 
 -h::
---help::
        Show help message.
index 3bcf660e0256b3706d8d78e3cb152dcf3b1b740a..0e1168c06661448d0757ceb3a7c2155a3dd42809 100644 (file)
@@ -12,8 +12,9 @@ The configuration variables are used by both the git plumbing
 and the porcelains. The variables are divided into sections, wherein
 the fully qualified variable name of the variable itself is the last
 dot-separated segment and the section name is everything before the last
-dot. The variable names are case-insensitive and only alphanumeric
-characters are allowed. Some variables may appear multiple times.
+dot. The variable names are case-insensitive, allow only alphanumeric
+characters and `-`, and must start with an alphabetic character.  Some
+variables may appear multiple times.
 
 Syntax
 ~~~~~~
@@ -45,17 +46,19 @@ lines.  Variables may belong directly to a section or to a given subsection.
 You can have `[section]` if you have `[section "subsection"]`, but you
 don't need to.
 
-There is also a case insensitive alternative `[section.subsection]` syntax.
-In this syntax, subsection names follow the same restrictions as for section
-names.
+There is also a deprecated `[section.subsection]` syntax. With this
+syntax, the subsection name is converted to lower-case and is also
+compared case sensitively. These subsection names follow the same
+restrictions as section names.
 
 All the other lines (and the remainder of the line after the section
 header) are recognized as setting variables, in the form
 'name = value'.  If there is no equal sign on the line, the entire line
 is taken as 'name' and the variable is recognized as boolean "true".
-The variable names are case-insensitive and only alphanumeric
-characters and `-` are allowed.  There can be more than one value
-for a given variable; we say then that variable is multivalued.
+The variable names are case-insensitive, allow only alphanumeric characters
+and `-`, and must start with an alphabetic character.  There can be more
+than one value for a given variable; we say then that the variable is
+multivalued.
 
 Leading and trailing whitespace in a variable value is discarded.
 Internal whitespace within a variable value is retained verbatim.
@@ -114,35 +117,32 @@ 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:
+       These variables control various optional help messages designed to
+       aid new users. All 'advice.*' variables default to 'true', and you
+       can tell Git that you do not need help by setting these to 'false':
 +
 --
        pushNonFastForward::
                Advice shown when linkgit:git-push[1] refuses
-               non-fast-forward refs. Default: true.
+               non-fast-forward refs.
        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.
+               when writing commit messages.
        commitBeforeMerge::
                Advice shown when linkgit:git-merge[1] refuses to
                merge to avoid overwriting local changes.
-               Default: true.
        resolveConflict::
                Advices shown by various commands when conflicts
                prevent the operation from being performed.
-               Default: true.
        implicitIdentity::
                Advice on how to set your identity configuration when
                your information is guessed from the system username and
-               domain name. Default: true.
-
+               domain name.
        detachedHead::
-               Advice shown when you used linkgit::git-checkout[1] to
+               Advice shown when you used linkgit:git-checkout[1] to
                move to the detach HEAD state, to instruct how to create
-               a local branch after the fact.  Default: true.
+               a local branch after the fact.
 --
 
 core.fileMode::
@@ -473,6 +473,12 @@ core.editor::
        variable when it is set, and the environment variable
        `GIT_EDITOR` is not set.  See linkgit:git-var[1].
 
+sequence.editor::
+       Text editor used by `git rebase -i` for editing the rebase insn file.
+       The value is meant to be interpreted by the shell when it is used.
+       It can be overridden by the `GIT_SEQUENCE_EDITOR` environment variable.
+       When not configured the default commit message editor is used instead.
+
 core.pager::
        The command that git will use to paginate output.  Can
        be overridden with the `GIT_PAGER` environment
@@ -670,10 +676,12 @@ branch.<name>.mergeoptions::
 branch.<name>.rebase::
        When true, rebase the branch <name> on top of the fetched branch,
        instead of merging the default branch from the default remote when
-       "git pull" is run.
-       *NOTE*: this is a possibly dangerous operation; do *not* use
-       it unless you understand the implications (see linkgit:git-rebase[1]
-       for details).
+       "git pull" is run. See "pull.rebase" for doing this in a non
+       branch-specific manner.
++
+*NOTE*: this is a possibly dangerous operation; do *not* use
+it unless you understand the implications (see linkgit:git-rebase[1]
+for details).
 
 browser.<tool>.cmd::
        Specify the command to invoke the specified browser. The
@@ -825,6 +833,29 @@ commit.template::
        "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
        specified user's home directory.
 
+credential.helper::
+       Specify an external helper to be called when a username or
+       password credential is needed; the helper may consult external
+       storage to avoid prompting the user for the credentials. See
+       linkgit:gitcredentials[7] for details.
+
+credential.useHttpPath::
+       When acquiring credentials, consider the "path" component of an http
+       or https URL to be important. Defaults to false. See
+       linkgit:gitcredentials[7] for more information.
+
+credential.username::
+       If no username is set for a network authentication, use this username
+       by default. See credential.<context>.* below, and
+       linkgit:gitcredentials[7].
+
+credential.<url>.*::
+       Any of the credential.* options above can be applied selectively to
+       some credentials. For example "credential.https://example.com.username"
+       would set the default username only for https connections to
+       example.com. See linkgit:gitcredentials[7] for details on how URLs are
+       matched.
+
 include::diff-config.txt[]
 
 difftool.<tool>.path::
@@ -857,6 +888,13 @@ fetch.recurseSubmodules::
        when its superproject retrieves a commit that updates the submodule's
        reference.
 
+fetch.fsckObjects::
+       If it is set to true, git-fetch-pack will check all fetched
+       objects. It will abort in the case of a malformed object or a
+       broken link. The result of an abort are only dangling objects.
+       Defaults to false. If not set, the value of `transfer.fsckObjects`
+       is used instead.
+
 fetch.unpackLimit::
        If the number of objects fetched over the git native
        transfer is below this
@@ -1064,12 +1102,40 @@ All gitcvs variables except for 'gitcvs.usecrlfattr' and
 is one of "ext" and "pserver") to make them apply only for the given
 access method.
 
+gitweb.category::
+gitweb.description::
+gitweb.owner::
+gitweb.url::
+       See linkgit:gitweb[1] for description.
+
+gitweb.avatar::
+gitweb.blame::
+gitweb.grep::
+gitweb.highlight::
+gitweb.patches::
+gitweb.pickaxe::
+gitweb.remote_heads::
+gitweb.showsizes::
+gitweb.snapshot::
+       See linkgit:gitweb.conf[5] for description.
+
 grep.lineNumber::
        If set to true, enable '-n' option by default.
 
 grep.extendedRegexp::
        If set to true, enable '--extended-regexp' option by default.
 
+gpg.program::
+       Use this custom program instead of "gpg" found on $PATH when
+       making or verifying a PGP signature. The program must support the
+       same command line interface as GPG, namely, to verify a detached
+       signature, "gpg --verify $file - <$signature" is run, and the
+       program is expected to signal a good signature by exiting with
+       code 0, and to generate an ascii-armored detached signature, the
+       standard input of "gpg -bsau $key" is fed with the contents to be
+       signed, and the program is expected to send the result to its
+       standard output.
+
 gui.commitmsgwidth::
        Defines how wide the commit message window is in the
        linkgit:git-gui[1]. "75" is the default.
@@ -1194,9 +1260,10 @@ help.autocorrect::
        This is the default.
 
 http.proxy::
-       Override the HTTP proxy, normally configured using the 'http_proxy'
-       environment variable (see linkgit:curl[1]).  This can be overridden
-       on a per-remote basis; see remote.<name>.proxy
+       Override the HTTP proxy, normally configured using the 'http_proxy',
+       'https_proxy', and 'all_proxy' environment variables (see
+       `curl(1)`).  This can be overridden on a per-remote basis; see
+       remote.<name>.proxy
 
 http.cookiefile::
        File containing previously stored cookie lines which should be used
@@ -1559,6 +1626,16 @@ pretty.<name>::
        Note that an alias with the same name as a built-in format
        will be silently ignored.
 
+pull.rebase::
+       When true, rebase branches on top of the fetched branch, instead
+       of merging the default branch from the default remote when "git
+       pull" is run. See "branch.<name>.rebase" for setting this on a
+       per-branch basis.
++
+*NOTE*: this is a possibly dangerous operation; do *not* use
+it unless you understand the implications (see linkgit:git-rebase[1]
+for details).
+
 pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
@@ -1596,7 +1673,8 @@ 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
        broken link. The result of an abort are only dangling objects.
-       Defaults to false.
+       Defaults to false. If not set, the value of `transfer.fsckObjects`
+       is used instead.
 
 receive.unpackLimit::
        If the number of objects received in a push is below this
@@ -1832,6 +1910,11 @@ tar.umask::
        archiving user's umask will be used instead.  See umask(2) and
        linkgit:git-archive[1].
 
+transfer.fsckObjects::
+       When `fetch.fsckObjects` or `receive.fsckObjects` are
+       not set, the value of this variable is used instead.
+       Defaults to false.
+
 transfer.unpackLimit::
        When `fetch.unpackLimit` or `receive.unpackLimit` are
        not set, the value of this variable is used instead.
index de2206bd5ccf936e97cfde5def4388cb3439300c..ba7cd13483194399762b0f48e218ddb4011dfcf1 100644 (file)
@@ -411,6 +411,10 @@ endif::git-format-patch[]
        Show the context between diff hunks, up to the specified number
        of lines, thereby fusing hunks that are close to each other.
 
+-W::
+--function-context::
+       Show whole surrounding functions of changes.
+
 ifndef::git-format-patch[]
 ifndef::git-log[]
 --exit-code::
index 507b8d0ab2a2d524a607c5b91e6d17f6d2f36392..0427e80a35601e689a47299dce9c5517959bddf8 100644 (file)
@@ -9,18 +9,23 @@ SYNOPSIS
 --------
 [verse]
 'git branch' [--color[=<when>] | --no-color] [-r | -a]
-       [-v [--abbrev=<length> | --no-abbrev]]
-       [(--merged | --no-merged | --contains) [<commit>]]
+       [--list] [-v [--abbrev=<length> | --no-abbrev]]
+       [(--merged | --no-merged | --contains) [<commit>]] [<pattern>...]
 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
 'git branch' (-d | -D) [-r] <branchname>...
+'git branch' --edit-description [<branchname>]
 
 DESCRIPTION
 -----------
 
 With no arguments, existing branches are listed and the current branch will
 be highlighted with an asterisk.  Option `-r` causes the remote-tracking
-branches to be listed, and option `-a` shows both.
+branches to be listed, and option `-a` shows both. This list mode is also
+activated by the `--list` option (see below).
+<pattern> restricts the output to matching branches, the pattern is a shell
+wildcard (i.e., matched using fnmatch(3))
+Multiple patterns may be given; if any of them matches, the tag is shown.
 
 With `--contains`, shows only the branches that contain the named commit
 (in other words, the branches whose tip commits are descendants of the
@@ -64,6 +69,7 @@ way to clean up all obsolete remote-tracking branches.
 OPTIONS
 -------
 -d::
+--delete::
        Delete a branch. The branch must be fully merged in its
        upstream branch, or in `HEAD` if no upstream was set with
        `--track` or `--set-upstream`.
@@ -72,6 +78,7 @@ OPTIONS
        Delete a branch irrespective of its merged status.
 
 -l::
+--create-reflog::
        Create the branch's reflog.  This activates recording of
        all changes made to the branch ref, enabling use of date
        based sha1 expressions such as "<branchname>@\{yesterday}".
@@ -84,6 +91,7 @@ OPTIONS
        already. Without `-f` 'git branch' refuses to change an existing branch.
 
 -m::
+--move::
        Move/rename a branch and the corresponding reflog.
 
 -M::
@@ -100,14 +108,21 @@ OPTIONS
        Same as `--color=never`.
 
 -r::
+--remotes::
        List or delete (if used with -d) the remote-tracking branches.
 
 -a::
+--all::
        List both remote-tracking branches and local branches.
 
+--list::
+       Activate the list mode. `git branch <pattern>` would try to create a branch,
+       use `git branch --list <pattern>` to list matching branches.
+
 -v::
 --verbose::
-       Show sha1 and commit subject line for each head, along with
+       When in list mode,
+       show sha1 and commit subject line for each head, along with
        relationship to upstream branch (if any). If given twice, print
        the name of the upstream branch, as well.
 
@@ -144,6 +159,10 @@ start-point is either a local or remote-tracking branch.
        like '--track' would when creating the branch, except that where
        branch points to is not changed.
 
+--edit-description::
+       Open an editor and edit the text to explain what the branch is
+       for, to be used by various other commands (e.g. `request-pull`).
+
 --contains <commit>::
        Only list branches which contain the specified commit.
 
index 1f7312a1895ae66901e5472132406d01303da1f8..5abdbaa51cf58e216bbc63d28039a5abfba01669 100644 (file)
@@ -24,6 +24,9 @@ OPTIONS
        paths.  If this option is used, then 'unspecified' attributes
        will not be included in the output.
 
+--cached::
+       Consider `.gitattributes` in the index only, ignoring the working tree.
+
 --stdin::
        Read file names from stdin instead of from the command-line.
 
index c9fdf84a08847bd8cb8c9ce1e2f50241975e267c..103e7b128d3a0d2b1c06da89d4a577f9548ba4f4 100644 (file)
@@ -8,8 +8,9 @@ git-check-ref-format - Ensures that a reference name is well formed
 SYNOPSIS
 --------
 [verse]
-'git check-ref-format' <refname>
-'git check-ref-format' --print <refname>
+'git check-ref-format' [--normalize]
+       [--[no-]allow-onelevel] [--refspec-pattern]
+       <refname>
 'git check-ref-format' --branch <branchname-shorthand>
 
 DESCRIPTION
@@ -28,22 +29,28 @@ git imposes the following rules on how references are named:
 
 . They can include slash `/` for hierarchical (directory)
   grouping, but no slash-separated component can begin with a
-  dot `.`.
+  dot `.` or end with the sequence `.lock`.
 
 . They must contain at least one `/`. This enforces the presence of a
   category like `heads/`, `tags/` etc. but the actual names are not
-  restricted.
+  restricted.  If the `--allow-onelevel` option is used, this rule
+  is waived.
 
 . 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.
+  caret `{caret}`, or colon `:` anywhere.
 
-. They cannot end with a slash `/` nor a dot `.`.
+. They cannot have question-mark `?`, asterisk `{asterisk}`, or open
+  bracket `[` anywhere.  See the `--refspec-pattern` option below for
+  an exception to this rule.
 
-. They cannot end with the sequence `.lock`.
+. They cannot begin or end with a slash `/` or contain multiple
+  consecutive slashes (see the `--normalize` option below for an
+  exception to this rule)
+
+. They cannot end with a dot `.`.
 
 . They cannot contain a sequence `@{`.
 
@@ -68,16 +75,36 @@ reference name expressions (see linkgit:gitrevisions[7]):
 
 . at-open-brace `@{` is used as a notation to access a reflog entry.
 
-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.
-
 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.
 
+OPTIONS
+-------
+--allow-onelevel::
+--no-allow-onelevel::
+       Controls whether one-level refnames are accepted (i.e.,
+       refnames that do not contain multiple `/`-separated
+       components).  The default is `--no-allow-onelevel`.
+
+--refspec-pattern::
+       Interpret <refname> as a reference name pattern for a refspec
+       (as used with remote repositories).  If this option is
+       enabled, <refname> is allowed to contain a single `{asterisk}`
+       in place of a one full pathname component (e.g.,
+       `foo/{asterisk}/bar` but not `foo/bar{asterisk}`).
+
+--normalize::
+       Normalize 'refname' by removing any leading slash (`/`)
+       characters and collapsing runs of adjacent slashes between
+       name components into a single slash.  Iff the normalized
+       refname is valid then print it to standard output and exit
+       with a status of 0.  (`--print` is a deprecated way to spell
+       `--normalize`.)
+
+
 EXAMPLES
 --------
 
@@ -90,7 +117,7 @@ $ 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") ||
+$ ref=$(git check-ref-format --normalize "refs/heads/$newbranch") ||
 die "we do not like '$newbranch' as a branch name."
 ------------
 
index 7cfa3d92ac8dc7a90068311c0047c667f98515a8..fed5097e00b4a031c2992ac3d421f6df975e6152 100644 (file)
@@ -9,6 +9,9 @@ SYNOPSIS
 --------
 [verse]
 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...
+'git cherry-pick' --continue
+'git cherry-pick' --quit
+'git cherry-pick' --abort
 
 DESCRIPTION
 -----------
@@ -110,6 +113,10 @@ effect to your index in a row.
        Pass the merge strategy-specific option through to the
        merge strategy.  See linkgit:git-merge[1] for details.
 
+SEQUENCER SUBCOMMANDS
+---------------------
+include::sequencer.txt[]
+
 EXAMPLES
 --------
 `git cherry-pick master`::
index 0fdb82ee8697a53beab6defd4db9eca8840b1dd0..cfb9906bb52f5bd81301e4859759b17ab124cada 100644 (file)
@@ -9,7 +9,8 @@ git-commit-tree - Create a new commit object
 SYNOPSIS
 --------
 [verse]
-'git commit-tree' <tree> [(-p <parent commit>)...] < changelog
+'git commit-tree' <tree> [(-p <parent>)...] < changelog
+'git commit-tree' [(-p <parent>)...] [(-m <message>)...] [(-F <file>)...] <tree>
 
 DESCRIPTION
 -----------
@@ -17,7 +18,8 @@ This is usually not what an end user wants to run directly.  See
 linkgit:git-commit[1] instead.
 
 Creates a new commit object based on the provided tree object and
-emits the new commit object id on stdout.
+emits the new commit object id on stdout. The log message is read
+from the standard input, unless `-m` or `-F` options are given.
 
 A commit object may have any number of parents. With exactly one
 parent, it is an ordinary commit. Having more than one parent makes
@@ -39,9 +41,17 @@ OPTIONS
 <tree>::
        An existing tree object
 
--p <parent commit>::
+-p <parent>::
        Each '-p' indicates the id of a parent commit object.
 
+-m <message>::
+       A paragraph in the commig log message. This can be given more than
+       once and each <message> becomes its own paragraph.
+
+-F <file>::
+       Read the commit log message from the given file. Use `-` to read
+       from the standard input.
+
 
 Commit Information
 ------------------
@@ -68,7 +78,9 @@ if set:
 
 In case (some of) these environment variables are not set, the information
 is taken from the configuration items user.name and user.email, or, if not
-present, system user name and fully qualified hostname.
+present, system user name and the hostname used for outgoing mail (taken
+from `/etc/mailname` and falling back to the fully qualified hostname when
+that file does not exist).
 
 A commit comment is read from stdin. If a changelog
 entry is not provided via "<" redirection, 'git commit-tree' will just wait
@@ -90,6 +102,10 @@ Discussion
 
 include::i18n.txt[]
 
+FILES
+-----
+/etc/mailname
+
 SEE ALSO
 --------
 linkgit:git-write-tree[1]
index e7ecf5d803e14dfa452671cf01e7730dce48b984..7617d9eb2431a1dd52516b22b5e2ac4be2962f97 100644 (file)
@@ -85,8 +85,11 @@ OPTIONS
        is not exactly one.
 
 --get-regexp::
-       Like --get-all, but interprets the name as a regular expression.
-       Also outputs the key names.
+       Like --get-all, but interprets the name as a regular expression and
+       writes out the key names.  Regular expression matching is currently
+       case-sensitive and done against a canonicalized version of the key
+       in which section and variable names are lowercased, but subsection
+       names are not.
 
 --global::
        For writing options: write to global ~/.gitconfig file rather than
diff --git a/Documentation/git-credential-cache--daemon.txt b/Documentation/git-credential-cache--daemon.txt
new file mode 100644 (file)
index 0000000..11edc5a
--- /dev/null
@@ -0,0 +1,26 @@
+git-credential-cache--daemon(1)
+===============================
+
+NAME
+----
+git-credential-cache--daemon - temporarily store user credentials in memory
+
+SYNOPSIS
+--------
+[verse]
+git credential-cache--daemon <socket>
+
+DESCRIPTION
+-----------
+
+NOTE: You probably don't want to invoke this command yourself; it is
+started automatically when you use linkgit:git-credential-cache[1].
+
+This command listens on the Unix domain socket specified by `<socket>`
+for `git-credential-cache` clients. Clients may store and retrieve
+credentials. Each credential is held for a timeout specified by the
+client; once no credentials are held, the daemon exits.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-credential-cache.txt b/Documentation/git-credential-cache.txt
new file mode 100644 (file)
index 0000000..f3d09c5
--- /dev/null
@@ -0,0 +1,77 @@
+git-credential-cache(1)
+=======================
+
+NAME
+----
+git-credential-cache - helper to temporarily store passwords in memory
+
+SYNOPSIS
+--------
+-----------------------------
+git config credential.helper 'cache [options]'
+-----------------------------
+
+DESCRIPTION
+-----------
+
+This command caches credentials in memory for use by future git
+programs. The stored credentials never touch the disk, and are forgotten
+after a configurable timeout.  The cache is accessible over a Unix
+domain socket, restricted to the current user by filesystem permissions.
+
+You probably don't want to invoke this command directly; it is meant to
+be used as a credential helper by other parts of git. See
+linkgit:gitcredentials[7] or `EXAMPLES` below.
+
+OPTIONS
+-------
+
+--timeout <seconds>::
+
+       Number of seconds to cache credentials (default: 900).
+
+--socket <path>::
+
+       Use `<path>` to contact a running cache daemon (or start a new
+       cache daemon if one is not started). Defaults to
+       `~/.git-credential-cache/socket`. If your home directory is on a
+       network-mounted filesystem, you may need to change this to a
+       local filesystem.
+
+CONTROLLING THE DAEMON
+----------------------
+
+If you would like the daemon to exit early, forgetting all cached
+credentials before their timeout, you can issue an `exit` action:
+
+--------------------------------------
+git credential-cache exit
+--------------------------------------
+
+EXAMPLES
+--------
+
+The point of this helper is to reduce the number of times you must type
+your username or password. For example:
+
+------------------------------------
+$ git config credential.helper cache
+$ git push http://example.com/repo.git
+Username: <type your username>
+Password: <type your password>
+
+[work for 5 more minutes]
+$ git push http://example.com/repo.git
+[your credentials are used automatically]
+------------------------------------
+
+You can provide options via the credential.helper configuration
+variable (this example drops the cache time to 5 minutes):
+
+-------------------------------------------------------
+$ git config credential.helper 'cache --timeout=300'
+-------------------------------------------------------
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-credential-store.txt b/Documentation/git-credential-store.txt
new file mode 100644 (file)
index 0000000..3109346
--- /dev/null
@@ -0,0 +1,75 @@
+git-credential-store(1)
+=======================
+
+NAME
+----
+git-credential-store - helper to store credentials on disk
+
+SYNOPSIS
+--------
+-------------------
+git config credential.helper 'store [options]'
+-------------------
+
+DESCRIPTION
+-----------
+
+NOTE: Using this helper will store your passwords unencrypted on disk,
+protected only by filesystem permissions. If this is not an acceptable
+security tradeoff, try linkgit:git-credential-cache[1], or find a helper
+that integrates with secure storage provided by your operating system.
+
+This command stores credentials indefinitely on disk for use by future
+git programs.
+
+You probably don't want to invoke this command directly; it is meant to
+be used as a credential helper by other parts of git. See
+linkgit:gitcredentials[7] or `EXAMPLES` below.
+
+OPTIONS
+-------
+
+--store=<path>::
+
+       Use `<path>` to store credentials. The file will have its
+       filesystem permissions set to prevent other users on the system
+       from reading it, but will not be encrypted or otherwise
+       protected. Defaults to `~/.git-credentials`.
+
+EXAMPLES
+--------
+
+The point of this helper is to reduce the number of times you must type
+your username or password. For example:
+
+------------------------------------------
+$ git config credential.helper store
+$ git push http://example.com/repo.git
+Username: <type your username>
+Password: <type your password>
+
+[several days later]
+$ git push http://example.com/repo.git
+[your credentials are used automatically]
+------------------------------------------
+
+STORAGE FORMAT
+--------------
+
+The `.git-credentials` file is stored in plaintext. Each credential is
+stored on its own line as a URL like:
+
+------------------------------
+https://user:pass@example.com
+------------------------------
+
+When git needs authentication for a particular URL context,
+credential-store will consider that context a pattern to match against
+each entry in the credentials file.  If the protocol, hostname, and
+username (if we already have one) match, then the password is returned
+to git. See the discussion of configuration in linkgit:gitcredentials[7]
+for more information.
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 69a1e4af9ec007a6cc59cce07298a6a825369ef1..31b28fc29fc6e01e8505f2179b08082cf734377c 100644 (file)
@@ -161,6 +161,16 @@ the facility of inet daemon to achieve the same before spawning
        repository configuration.  By default, all the services
        are overridable.
 
+--informative-errors::
+--no-informative-errors::
+       When informative errors are turned on, git-daemon will report
+       more verbose errors to the client, differentiating conditions
+       like "no such repository" from "repository not exported". This
+       is more convenient for clients, but may leak information about
+       the existence of unexported repositories.  When informative
+       errors are not enabled, all errors report "access denied" to the
+       client. The default is --no-informative-errors.
+
 <directory>::
        A directory to add to the whitelist of allowed directories. Unless
        --strict-paths is specified this will also include subdirectories
index 32aff954a2b2f95a61b39b8d08fb0482724400bb..3a0f55ec8e273545af3a92a9b886511ac79ba73c 100644 (file)
@@ -53,6 +53,11 @@ OPTIONS
 CONFIGURATION
 -------------
 
+merge.branchdesc::
+       In addition to branch names, populate the log message with
+       the branch description text associated with them.  Defaults
+       to false.
+
 merge.log::
        In addition to branch names, populate the log message with at
        most the specified number of one-line descriptions from the
index 152e695c8178fee9daa478cdf67eae58a2f1af00..c872b883ba25144457eccb9967bc68003a3332ea 100644 (file)
@@ -101,9 +101,10 @@ Fields that have name-email-date tuple as its value (`author`,
 `committer`, and `tagger`) can be suffixed with `name`, `email`,
 and `date` to extract the named component.
 
-The first line of the message in a commit and tag object is
-`subject`, the remaining lines are `body`.  The whole message
-is `contents`.
+The complete message in a commit and tag object is `contents`.
+Its first line is `contents:subject`, the remaining lines
+are `contents:body` and the optional GPG signature
+is `contents:signature`.
 
 For sorting purposes, fields with numeric values sort in numeric
 order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
index a2a508dc2829b88e143ceb5c8e5edf0880263b2b..6c47395ad2d22f54ed76dda914efbc28b0dca083 100644 (file)
@@ -10,7 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
-        [--[no-]full] [--strict] [--verbose] [--lost-found] [<object>*]
+        [--[no-]full] [--strict] [--verbose] [--lost-found]
+        [--[no-]progress] [<object>*]
 
 DESCRIPTION
 -----------
@@ -72,30 +73,28 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless
        a blob, the contents are written into the file, rather than
        its object name.
 
-It tests SHA1 and general object sanity, and it does full tracking of
-the resulting reachability and everything else. It prints out any
-corruption it finds (missing or bad objects), and if you use the
-'--unreachable' flag it will also print out objects that exist but
-that aren't reachable from any of the specified head nodes.
-
-So for example
+--progress::
+--no-progress::
+       Progress status is reported on the standard error stream by
+       default when it is attached to a terminal, unless
+       --no-progress or --verbose is specified. --progress forces
+       progress status even if the standard error stream is not
+       directed to a terminal.
 
-       git fsck --unreachable HEAD \
-               $(git for-each-ref --format="%(objectname)" refs/heads)
+DISCUSSION
+----------
 
-will do quite a _lot_ of verification on the tree. There are a few
-extra validity tests to be added (make sure that tree objects are
-sorted properly etc), but on the whole if 'git fsck' is happy, you
-do have a valid tree.
+git-fsck tests SHA1 and general object sanity, and it does full tracking
+of the resulting reachability and everything else. It prints out any
+corruption it finds (missing or bad objects), and if you use the
+'--unreachable' flag it will also print out objects that exist but that
+aren't reachable from any of the specified head nodes (or the default
+set, as mentioned above).
 
 Any corrupt objects you will have to find in backups or other archives
 (i.e., you can just remove them and do an 'rsync' with some other site in
 the hopes that somebody else has the object you have corrupted).
 
-Of course, "valid tree" doesn't mean that it wasn't generated by some
-evil person, and the end result might be crap. git is a revision
-tracking system, not a quality assurance system ;)
-
 Extracted Diagnostics
 ---------------------
 
index e44a4988b7a7ed7100da6e2ba098d7fda17a2f83..343eadd4076664cc66c9d22db22d800e92cc6dc6 100644 (file)
@@ -20,10 +20,12 @@ SYNOPSIS
           [-c | --count] [--all-match] [-q | --quiet]
           [--max-depth <depth>]
           [--color[=<when>] | --no-color]
+          [--break] [--heading] [-p | --show-function]
           [-A <post-context>] [-B <pre-context>] [-C <context>]
+          [-W | --function-context]
           [-f <file>] [-e] <pattern>
           [--and|--or|--not|(|)|-e <pattern>...]
-          [--cached | --no-index | <tree>...]
+          [ [--exclude-standard] [--cached | --no-index | --untracked] | <tree>...]
           [--] [<pathspec>...]
 
 DESCRIPTION
@@ -49,7 +51,20 @@ OPTIONS
        blobs registered in the index file.
 
 --no-index::
-       Search files in the current directory, not just those tracked by git.
+       Search files in the current directory that is not managed by git.
+
+--untracked::
+       In addition to searching in the tracked files in the working
+       tree, search also in untracked files.
+
+--no-exclude-standard::
+       Also search in ignored files by not honoring the `.gitignore`
+       mechanism. Only useful with `--untracked`.
+
+--exclude-standard::
+       Do not pay attention to ignored files specified via the `.gitignore`
+       mechanism.  Only useful when searching files in the current directory
+       with `--no-index`.
 
 -a::
 --text::
@@ -66,6 +81,9 @@ OPTIONS
 --max-depth <depth>::
        For each <pathspec> given on command line, descend at most <depth>
        levels of directories. A negative value means no limit.
+       This option is ignored if <pathspec> contains active wildcards.
+       In other words if "a*" matches a directory named "a*",
+       "*" is matched literally so --max-depth is still effective.
 
 -w::
 --word-regexp::
index ea95c90460b976ee187833c248366af579065f83..f3eef510f2ba906f64e802a4ea9e1d671530aca4 100644 (file)
@@ -84,6 +84,10 @@ If the configuration variable 'instaweb.browser' is not set,
 'web.browser' will be used instead if it is defined. See
 linkgit:git-web{litdd}browse[1] for more information about this.
 
+SEE ALSO
+--------
+linkgit:gitweb[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 51dc3257486b07edaec71fe98fed6533e17e1a77..97e7a8e9e7cc01bc350d57ca66e08f5ed96221de 100644 (file)
@@ -25,13 +25,24 @@ command directly.  See linkgit:git-am[1] instead.
 OPTIONS
 -------
 -k::
-       Usually the program 'cleans up' the Subject: header line
-       to extract the title line for the commit log message,
-       among which (1) remove 'Re:' or 're:', (2) leading
-       whitespaces, (3) '[' up to ']', typically '[PATCH]', and
-       then prepends "[PATCH] ".  This flag forbids this
-       munging, and is most useful when used to read back
-       'git format-patch -k' output.
+       Usually the program removes email cruft from the Subject:
+       header line to extract the title line for the commit log
+       message.  This option prevents this munging, and is most
+       useful when used to read back 'git format-patch -k' output.
++
+Specifically, the following are removed until none of them remain:
++
+--
+*      Leading and trailing whitespace.
+
+*      Leading `Re:`, `re:`, and `:`.
+
+*      Leading bracketed strings (between `[` and `]`, usually
+       `[PATCH]`).
+--
++
+Finally, runs of whitespace are normalized to a single ASCII space
+character.
 
 -b::
        When -k is not in effect, all leading strings bracketed with '['
index b8db3739640491566dee6e381bae319b7e7be8c6..e3c84486141685e2f128f64f46d24c36cc45e97f 100644 (file)
@@ -15,8 +15,8 @@ DESCRIPTION
 -----------
 This script is used to move or rename a file, directory or symlink.
 
- git mv [-f] [-n] <source> <destination>
- git mv [-f] [-n] [-k] <source> ... <destination directory>
+ git mv [-v] [-f] [-n] [-k] <source> <destination>
+ git mv [-v] [-f] [-n] [-k] <source> ... <destination directory>
 
 In the first form, it renames <source>, which must exist and be either
 a file, symlink or directory, to <destination>.
@@ -40,6 +40,10 @@ OPTIONS
 --dry-run::
        Do nothing; only show what would happen
 
+-v::
+--verbose::
+       Report the names of files as they are moved.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt
new file mode 100644 (file)
index 0000000..ed82790
--- /dev/null
@@ -0,0 +1,497 @@
+git-p4(1)
+=========
+
+NAME
+----
+git-p4 - Import from and submit to Perforce repositories
+
+
+SYNOPSIS
+--------
+[verse]
+'git p4 clone' [<sync options>] [<clone options>] <p4 depot path>...
+'git p4 sync' [<sync options>] [<p4 depot path>...]
+'git p4 rebase'
+'git p4 submit' [<submit options>] [<master branch name>]
+
+
+DESCRIPTION
+-----------
+This command provides a way to interact with p4 repositories
+using git.
+
+Create a new git repository from an existing p4 repository using
+'git p4 clone', giving it one or more p4 depot paths.  Incorporate
+new commits from p4 changes with 'git p4 sync'.  The 'sync' command
+is also used to include new branches from other p4 depot paths.
+Submit git changes back to p4 using 'git p4 submit'.  The command
+'git p4 rebase' does a sync plus rebases the current branch onto
+the updated p4 remote branch.
+
+
+EXAMPLE
+-------
+* Create an alias for 'git p4', using the full path to the 'git-p4'
+  script if needed:
++
+------------
+$ git config --global alias.p4 '!git-p4'
+------------
+
+* Clone a repository:
++
+------------
+$ git p4 clone //depot/path/project
+------------
+
+* Do some work in the newly created git repository:
++
+------------
+$ cd project
+$ vi foo.h
+$ git commit -a -m "edited foo.h"
+------------
+
+* Update the git repository with recent changes from p4, rebasing your
+  work on top:
++
+------------
+$ git p4 rebase
+------------
+
+* Submit your commits back to p4:
++
+------------
+$ git p4 submit
+------------
+
+
+COMMANDS
+--------
+
+Clone
+~~~~~
+Generally, 'git p4 clone' is used to create a new git directory
+from an existing p4 repository:
+------------
+$ git p4 clone //depot/path/project
+------------
+This:
+
+1.   Creates an empty git repository in a subdirectory called 'project'.
++
+2.   Imports the full contents of the head revision from the given p4
+depot path into a single commit in the git branch 'refs/remotes/p4/master'.
++
+3.   Creates a local branch, 'master' from this remote and checks it out.
+
+To reproduce the entire p4 history in git, use the '@all' modifier on
+the depot path:
+------------
+$ git p4 clone //depot/path/project@all
+------------
+
+
+Sync
+~~~~
+As development continues in the p4 repository, those changes can
+be included in the git repository using:
+------------
+$ git p4 sync
+------------
+This command finds new changes in p4 and imports them as git commits.
+
+P4 repositories can be added to an existing git repository using
+'git p4 sync' too:
+------------
+$ mkdir repo-git
+$ cd repo-git
+$ git init
+$ git p4 sync //path/in/your/perforce/depot
+------------
+This imports the specified depot into
+'refs/remotes/p4/master' in an existing git repository.  The
+'--branch' option can be used to specify a different branch to
+be used for the p4 content.
+
+If a git repository includes branches 'refs/remotes/origin/p4', these
+will be fetched and consulted first during a 'git p4 sync'.  Since
+importing directly from p4 is considerably slower than pulling changes
+from a git remote, this can be useful in a multi-developer environment.
+
+
+Rebase
+~~~~~~
+A common working pattern is to fetch the latest changes from the p4 depot
+and merge them with local uncommitted changes.  Often, the p4 repository
+is the ultimate location for all code, thus a rebase workflow makes
+sense.  This command does 'git p4 sync' followed by 'git rebase' to move
+local commits on top of updated p4 changes.
+------------
+$ git p4 rebase
+------------
+
+
+Submit
+~~~~~~
+Submitting changes from a git repository back to the p4 repository
+requires a separate p4 client workspace.  This should be specified
+using the 'P4CLIENT' environment variable or the git configuration
+variable 'git-p4.client'.  The p4 client must exist, but the client root
+will be created and populated if it does not already exist.
+
+To submit all changes that are in the current git branch but not in
+the 'p4/master' branch, use:
+------------
+$ git p4 submit
+------------
+
+To specify a branch other than the current one, use:
+------------
+$ git p4 submit topicbranch
+------------
+
+The upstream reference is generally 'refs/remotes/p4/master', but can
+be overridden using the '--origin=' command-line option.
+
+The p4 changes will be created as the user invoking 'git p4 submit'. The
+'--preserve-user' option will cause ownership to be modified
+according to the author of the git commit.  This option requires admin
+privileges in p4, which can be granted using 'p4 protect'.
+
+
+OPTIONS
+-------
+
+General options
+~~~~~~~~~~~~~~~
+All commands except clone accept this option.
+
+--git-dir <dir>::
+       Set the 'GIT_DIR' environment variable.  See linkgit:git[1].
+
+Sync options
+~~~~~~~~~~~~
+These options can be used in the initial 'clone' as well as in
+subsequent 'sync' operations.
+
+--branch <branch>::
+       Import changes into given branch.  If the branch starts with
+       'refs/', it will be used as is, otherwise the path 'refs/heads/'
+       will be prepended.  The default branch is 'master'.  If used
+       with an initial clone, no HEAD will be checked out.
++
+This example imports a new remote "p4/proj2" into an existing
+git repository:
+----
+    $ git init
+    $ git p4 sync --branch=refs/remotes/p4/proj2 //depot/proj2
+----
+
+--detect-branches::
+       Use the branch detection algorithm to find new paths in p4.  It is
+       documented below in "BRANCH DETECTION".
+
+--changesfile <file>::
+       Import exactly the p4 change numbers listed in 'file', one per
+       line.  Normally, 'git p4' inspects the current p4 repository
+       state and detects the changes it should import.
+
+--silent::
+       Do not print any progress information.
+
+--verbose::
+       Provide more progress information.
+
+--detect-labels::
+       Query p4 for labels associated with the depot paths, and add
+       them as tags in git.
+
+--import-local::
+       By default, p4 branches are stored in 'refs/remotes/p4/',
+       where they will be treated as remote-tracking branches by
+       linkgit:git-branch[1] and other commands.  This option instead
+       puts p4 branches in 'refs/heads/p4/'.  Note that future
+       sync operations must specify '--import-local' as well so that
+       they can find the p4 branches in refs/heads.
+
+--max-changes <n>::
+       Limit the number of imported changes to 'n'.  Useful to
+       limit the amount of history when using the '@all' p4 revision
+       specifier.
+
+--keep-path::
+       The mapping of file names from the p4 depot path to git, by
+       default, involves removing the entire depot path.  With this
+       option, the full p4 depot path is retained in git.  For example,
+       path '//depot/main/foo/bar.c', when imported from
+       '//depot/main/', becomes 'foo/bar.c'.  With '--keep-path', the
+       git path is instead 'depot/main/foo/bar.c'.
+
+--use-client-spec::
+       Use a client spec to find the list of interesting files in p4.
+       See the "CLIENT SPEC" section below.
+
+Clone options
+~~~~~~~~~~~~~
+These options can be used in an initial 'clone', along with the 'sync'
+options described above.
+
+--destination <directory>::
+       Where to create the git repository.  If not provided, the last
+       component in the p4 depot path is used to create a new
+       directory.
+
+--bare::
+       Perform a bare clone.  See linkgit:git-clone[1].
+
+-/ <path>::
+       Exclude selected depot paths when cloning.
+
+Submit options
+~~~~~~~~~~~~~~
+These options can be used to modify 'git p4 submit' behavior.
+
+--verbose::
+       Provide more progress information.
+
+--origin <commit>::
+       Upstream location from which commits are identified to submit to
+       p4.  By default, this is the most recent p4 commit reachable
+       from 'HEAD'.
+
+-M[<n>]::
+       Detect renames.  See linkgit:git-diff[1].  Renames will be
+       represented in p4 using explicit 'move' operations.  There
+       is no corresponding option to detect copies, but there are
+       variables for both moves and copies.
+
+--preserve-user::
+       Re-author p4 changes before submitting to p4.  This option
+       requires p4 admin privileges.
+
+
+DEPOT PATH SYNTAX
+-----------------
+The p4 depot path argument to 'git p4 sync' and 'git p4 clone' can
+be one or more space-separated p4 depot paths, with an optional
+p4 revision specifier on the end:
+
+"//depot/my/project"::
+    Import one commit with all files in the '#head' change under that tree.
+
+"//depot/my/project@all"::
+    Import one commit for each change in the history of that depot path.
+
+"//depot/my/project@1,6"::
+    Import only changes 1 through 6.
+
+"//depot/proj1@all //depot/proj2@all"::
+    Import all changes from both named depot paths into a single
+    repository.  Only files below these directories are included.
+    There is not a subdirectory in git for each "proj1" and "proj2".
+    You must use the '--destination' option when specifying more
+    than one depot path.  The revision specifier must be specified
+    identically on each depot path.  If there are files in the
+    depot paths with the same name, the path with the most recently
+    updated version of the file is the one that appears in git.
+
+See 'p4 help revisions' for the full syntax of p4 revision specifiers.
+
+
+CLIENT SPEC
+-----------
+The p4 client specification is maintained with the 'p4 client' command
+and contains among other fields, a View that specifies how the depot
+is mapped into the client repository.  The 'clone' and 'sync' commands
+can consult the client spec when given the '--use-client-spec' option or
+when the useClientSpec variable is true.  After 'git p4 clone', the
+useClientSpec variable is automatically set in the repository
+configuration file.  This allows future 'git p4 submit' commands to
+work properly; the submit command looks only at the variable and does
+not have a command-line option.
+
+The full syntax for a p4 view is documented in 'p4 help views'.  Git-p4
+knows only a subset of the view syntax.  It understands multi-line
+mappings, overlays with '+', exclusions with '-' and double-quotes
+around whitespace.  Of the possible wildcards, git-p4 only handles
+'...', and only when it is at the end of the path.  Git-p4 will complain
+if it encounters an unhandled wildcard.
+
+The name of the client can be given to git-p4 in multiple ways.  The
+variable 'git-p4.client' takes precedence if it exists.  Otherwise,
+normal p4 mechanisms of determining the client are used:  environment
+variable P4CLIENT, a file referenced by P4CONFIG, or the local host name.
+
+
+BRANCH DETECTION
+----------------
+P4 does not have the same concept of a branch as git.  Instead,
+p4 organizes its content as a directory tree, where by convention
+different logical branches are in different locations in the tree.
+The 'p4 branch' command is used to maintain mappings between
+different areas in the tree, and indicate related content.  'git p4'
+can use these mappings to determine branch relationships.
+
+If you have a repository where all the branches of interest exist as
+subdirectories of a single depot path, you can use '--detect-branches'
+when cloning or syncing to have 'git p4' automatically find
+subdirectories in p4, and to generate these as branches in git.
+
+For example, if the P4 repository structure is:
+----
+//depot/main/...
+//depot/branch1/...
+----
+
+And "p4 branch -o branch1" shows a View line that looks like:
+----
+//depot/main/... //depot/branch1/...
+----
+
+Then this 'git p4 clone' command:
+----
+git p4 clone --detect-branches //depot@all
+----
+produces a separate branch in 'refs/remotes/p4/' for //depot/main,
+called 'master', and one for //depot/branch1 called 'depot/branch1'.
+
+However, it is not necessary to create branches in p4 to be able to use
+them like branches.  Because it is difficult to infer branch
+relationships automatically, a git configuration setting
+'git-p4.branchList' can be used to explicitly identify branch
+relationships.  It is a list of "source:destination" pairs, like a
+simple p4 branch specification, where the "source" and "destination" are
+the path elements in the p4 repository.  The example above relied on the
+presence of the p4 branch.  Without p4 branches, the same result will
+occur with:
+----
+git config git-p4.branchList main:branch1
+git p4 clone --detect-branches //depot@all
+----
+
+
+PERFORMANCE
+-----------
+The fast-import mechanism used by 'git p4' creates one pack file for
+each invocation of 'git p4 sync'.  Normally, git garbage compression
+(linkgit:git-gc[1]) automatically compresses these to fewer pack files,
+but explicit invocation of 'git repack -adf' may improve performance.
+
+
+CONFIGURATION VARIABLES
+-----------------------
+The following config settings can be used to modify 'git p4' behavior.
+They all are in the 'git-p4' section.
+
+General variables
+~~~~~~~~~~~~~~~~~
+git-p4.user::
+       User specified as an option to all p4 commands, with '-u <user>'.
+       The environment variable 'P4USER' can be used instead.
+
+git-p4.password::
+       Password specified as an option to all p4 commands, with
+       '-P <password>'.
+       The environment variable 'P4PASS' can be used instead.
+
+git-p4.port::
+       Port specified as an option to all p4 commands, with
+       '-p <port>'.
+       The environment variable 'P4PORT' can be used instead.
+
+git-p4.host::
+       Host specified as an option to all p4 commands, with
+       '-h <host>'.
+       The environment variable 'P4HOST' can be used instead.
+
+git-p4.client::
+       Client specified as an option to all p4 commands, with
+       '-c <client>', including the client spec.
+
+Clone and sync variables
+~~~~~~~~~~~~~~~~~~~~~~~~
+git-p4.syncFromOrigin::
+       Because importing commits from other git repositories is much faster
+       than importing them from p4, a mechanism exists to find p4 changes
+       first in git remotes.  If branches exist under 'refs/remote/origin/p4',
+       those will be fetched and used when syncing from p4.  This
+       variable can be set to 'false' to disable this behavior.
+
+git-p4.branchUser::
+       One phase in branch detection involves looking at p4 branches
+       to find new ones to import.  By default, all branches are
+       inspected.  This option limits the search to just those owned
+       by the single user named in the variable.
+
+git-p4.branchList::
+       List of branches to be imported when branch detection is
+       enabled.  Each entry should be a pair of branch names separated
+       by a colon (:).  This example declares that both branchA and
+       branchB were created from main:
+-------------
+git config       git-p4.branchList main:branchA
+git config --add git-p4.branchList main:branchB
+-------------
+
+git-p4.useClientSpec::
+       Specify that the p4 client spec should be used to identify p4
+       depot paths of interest.  This is equivalent to specifying the
+       option '--use-client-spec'.  See the "CLIENT SPEC" section above.
+       This variable is a boolean, not the name of a p4 client.
+
+Submit variables
+~~~~~~~~~~~~~~~~
+git-p4.detectRenames::
+       Detect renames.  See linkgit:git-diff[1].
+
+git-p4.detectCopies::
+       Detect copies.  See linkgit:git-diff[1].
+
+git-p4.detectCopiesHarder::
+       Detect copies harder.  See linkgit:git-diff[1].
+
+git-p4.preserveUser::
+       On submit, re-author changes to reflect the git author,
+       regardless of who invokes 'git p4 submit'.
+
+git-p4.allowMissingP4Users::
+       When 'preserveUser' is true, 'git p4' normally dies if it
+       cannot find an author in the p4 user map.  This setting
+       submits the change regardless.
+
+git-p4.skipSubmitEdit::
+       The submit process invokes the editor before each p4 change
+       is submitted.  If this setting is true, though, the editing
+       step is skipped.
+
+git-p4.skipSubmitEditCheck::
+       After editing the p4 change message, 'git p4' makes sure that
+       the description really was changed by looking at the file
+       modification time.  This option disables that test.
+
+git-p4.allowSubmit::
+       By default, any branch can be used as the source for a 'git p4
+       submit' operation.  This configuration variable, if set, permits only
+       the named branches to be used as submit sources.  Branch names
+       must be the short names (no "refs/heads/"), and should be
+       separated by commas (","), with no spaces.
+
+git-p4.skipUserNameCheck::
+       If the user running 'git p4 submit' does not exist in the p4
+       user map, 'git p4' exits.  This option can be used to force
+       submission regardless.
+
+
+IMPLEMENTATION DETAILS
+----------------------
+* Changesets from p4 are imported using git fast-import.
+* Cloning or syncing does not require a p4 client; file contents are
+  collected using 'p4 print'.
+* Submitting requires a p4 client, which is not in the same location
+  as the git repository.  Patches are applied, one at a time, to
+  this p4 client and submitted from there.
+* Each commit imported by 'git p4' has a line at the end of the log
+  message indicating the p4 depot location and change number.  This
+  line is used by later 'git p4 sync' operations to know which p4
+  changes are new.
index e1da46876682e9d95a7505e1bc116cbe07f2ec61..0f18ec891ac75effb102a445a93bdcf4432bb697 100644 (file)
@@ -108,7 +108,7 @@ include::merge-options.txt[]
        fetched, the rebase uses that information to avoid rebasing
        non-local changes.
 +
-See `branch.<name>.rebase` and `branch.autosetuprebase` in
+See `pull.rebase`, `branch.<name>.rebase` and `branch.autosetuprebase` in
 linkgit:git-config[1] if you want to make `git pull` always use
 `{litdd}rebase` instead of merging.
 +
index a43e87448ba035278b58b191ea7c1c04179985a6..c4bde6509e5a2f652dca9345a3a28e9e674c216a 100644 (file)
@@ -341,7 +341,7 @@ since you pulled from him:
 
 ----------------
 $ git fetch git://.... linus
-$ LT=`cat .git/FETCH_HEAD`
+$ LT=`git rev-parse FETCH_HEAD`
 ----------------
 
 Your work tree is still based on your HEAD ($JC), but you have
index 504945c69153a30252527c16945836fd2aa2ec5a..520aaa94fb7b2be7c11e7eed013acf36055a41ab 100644 (file)
@@ -409,10 +409,13 @@ The interactive mode is meant for this type of workflow:
 
 where point 2. consists of several instances of
 
-a. regular use
+a) regular use
+
  1. finish something worthy of a commit
  2. commit
-b. independent fixup
+
+b) independent fixup
+
  1. realize that something does not work
  2. fix that
  3. commit it
index 5a8c5061f3701c57bab75b2b4c70ad620f7e536f..d376d19ef79962c5755e958eec7a491c96479a1f 100644 (file)
@@ -14,7 +14,7 @@ SYNOPSIS
 'git remote rename' <old> <new>
 'git remote rm' <name>
 'git remote set-head' <name> (-a | -d | <branch>)
-'git remote set-branches' <name> [--add] <branch>...
+'git remote set-branches' [--add] <name> <branch>...
 'git remote set-url' [--push] <name> <newurl> [<oldurl>]
 'git remote set-url --add' [--push] <name> <newurl>
 'git remote set-url --delete' [--push] <name> <url>
index b2832fc7eb809af9865d83cdb20829354165f62e..b674866e6d166ccce096b58f7cecdbdb183c0194 100644 (file)
@@ -9,8 +9,8 @@ SYNOPSIS
 --------
 [verse]
 'git reset' [-q] [<commit>] [--] <paths>...
-'git reset' [--patch|-p] [<commit>] [--] [<paths>...]
-'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]
+'git reset' (--patch | -p) [<commit>] [--] [<paths>...]
+'git reset' (--soft | --mixed | --hard | --merge | --keep) [-q] [<commit>]
 
 DESCRIPTION
 -----------
@@ -34,7 +34,7 @@ Alternatively, using linkgit:git-checkout[1] and specifying a commit, you
 can copy the contents of a path out of a commit to the index and to the
 working tree in one go.
 
-'git reset' --patch|-p [<commit>] [--] [<paths>...]::
+'git reset' (--patch | -p) [<commit>] [--] [<paths>...]::
        Interactively select hunks in the difference between the index
        and <commit> (defaults to HEAD).  The chosen hunks are applied
        in reverse to the index.
@@ -43,7 +43,7 @@ This means that `git reset -p` is the opposite of `git add -p`, i.e.
 you can use it to selectively reset hunks. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `\--patch` mode.
 
-'git reset' [--<mode>] [<commit>]::
+'git reset' --<mode> [<commit>]::
        This form resets the current branch head to <commit> and
        possibly updates the index (resetting it to the tree of <commit>) and
        the working tree depending on <mode>, which
index 42c9676eaa40f2a3d43514c09509e319258ca8b4..8023dc086d044d0ac2cab685ac1c8b050adda95b 100644 (file)
@@ -180,6 +180,10 @@ print a message to stderr and exit with nonzero status.
 <args>...::
        Flags and parameters to be parsed.
 
+--resolve-git-dir <path>::
+       Check if <path> is a valid git-dir or a git-file pointing to a valid
+       git-dir. If <path> is a valid git-dir the resolved path to git-dir will
+       be printed.
 
 include::revisions.txt[]
 
index b311d59c7c06dd696e3c667c97a5982137bdd87a..b699a3458eff439b05049dab94610a57ac62fc0a 100644 (file)
@@ -9,6 +9,9 @@ SYNOPSIS
 --------
 [verse]
 'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
+'git revert' --continue
+'git revert' --quit
+'git revert' --abort
 
 DESCRIPTION
 -----------
@@ -91,6 +94,10 @@ effect to your index in a row.
        Pass the merge strategy-specific option through to the
        merge strategy.  See linkgit:git-merge[1] for details.
 
+SEQUENCER SUBCOMMANDS
+---------------------
+include::sequencer.txt[]
+
 EXAMPLES
 --------
 `git revert HEAD~3`::
index a2f346ca710e03a7f5c65574659ac578ab8bf615..5e5f1c89646cf4389bfd30ba8e90eb0552e80aab 100644 (file)
@@ -68,6 +68,16 @@ require_work_tree_exists::
        cd_to_toplevel, which is impossible to do if there is no
        working tree.
 
+require_clean_work_tree <action> [<hint>]::
+       checks that the working tree and index associated with the
+       repository have no uncommitted changes to tracked files.
+       Otherwise it emits an error message of the form `Cannot
+       <action>: <reason>. <hint>`, and dies.  Example:
++
+----------------
+require_clean_work_tree rebase "Please commit or stash them."
+----------------
+
 get_author_ident_from_commit::
        outputs code for use with eval to set the GIT_AUTHOR_NAME,
        GIT_AUTHOR_EMAIL and GIT_AUTHOR_DATE variables for a given commit.
index 3c4589529960e013df364c68e4480caa09b744c6..fcee0008a9d868bb27c9eacbcfe72a3a3f8ed60f 100644 (file)
@@ -44,7 +44,7 @@ OPTIONS
 -d::
 --dereference::
 
-       Dereference tags into object IDs as well. They will be shown with "^{}"
+       Dereference tags into object IDs as well. They will be shown with "{caret}{}"
        appended.
 
 -s::
@@ -73,9 +73,9 @@ OPTIONS
 --exclude-existing[=<pattern>]::
 
        Make 'git show-ref' act as a filter that reads refs from stdin of the
-       form "^(?:<anything>\s)?<refname>(?:{backslash}{caret}\{\})?$"
+       form "`{caret}(?:<anything>\s)?<refname>(?:{backslash}{caret}{})?$`"
        and performs the following actions on each:
-       (1) strip "^{}" at the end of line if any;
+       (1) strip "{caret}{}" at the end of line if any;
        (2) ignore if pattern is provided and does not head-match refname;
        (3) warn if refname is not a well-formed refname and skip;
        (4) ignore if refname is a ref that exists in the local repository;
index b78f031cd4464b21be145d4ffa79ff39dc8bd2bb..a80d94650d3a6b724dc68aac18fd562ff38d2cf8 100644 (file)
@@ -3,26 +3,83 @@ git-stripspace(1)
 
 NAME
 ----
-git-stripspace - Filter out empty lines
+git-stripspace - Remove unnecessary whitespace
 
 
 SYNOPSIS
 --------
 [verse]
-'git stripspace' [-s | --strip-comments] < <stream>
+'git stripspace' [-s | --strip-comments] < input
 
 DESCRIPTION
 -----------
-Remove multiple empty lines, and empty lines at beginning and end.
+
+Clean the input in the manner used by 'git' for text such as commit
+messages, notes, tags and branch descriptions.
+
+With no arguments, this will:
+
+- remove trailing whitespace from all lines
+- collapse multiple consecutive empty lines into one empty line
+- remove empty lines from the beginning and end of the input
+- add a missing '\n' to the last line if necessary.
+
+In the case where the input consists entirely of whitespace characters, no
+output will be produced.
+
+*NOTE*: This is intended for cleaning metadata, prefer the `--whitespace=fix`
+mode of linkgit:git-apply[1] for correcting whitespace of patches or files in
+the repository.
 
 OPTIONS
 -------
 -s::
 --strip-comments::
-       In addition to empty lines, also strip lines starting with '#'.
+       Skip and remove all lines starting with '#'.
+
+EXAMPLES
+--------
+
+Given the following noisy input with '$' indicating the end of a line:
 
-<stream>::
-       Byte stream to act on.
+--------
+|A brief introduction   $
+|   $
+|$
+|A new paragraph$
+|# with a commented-out line    $
+|explaining lots of stuff.$
+|$
+|# An old paragraph, also commented-out. $
+|      $
+|The end.$
+|  $
+---------
+
+Use 'git stripspace' with no arguments to obtain:
+
+--------
+|A brief introduction$
+|$
+|A new paragraph$
+|# with a commented-out line$
+|explaining lots of stuff.$
+|$
+|# An old paragraph, also commented-out.$
+|$
+|The end.$
+---------
+
+Use 'git stripspace --strip-comments' to obtain:
+
+--------
+|A brief introduction$
+|$
+|A new paragraph$
+|explaining lots of stuff.$
+|$
+|The end.$
+---------
 
 GIT
 ---
index cd9c167e0aa063205bbfae2a27bc49c4334b903d..b72964947afcc39af3b97c5b9eeb1969b44e0d05 100644 (file)
@@ -125,6 +125,8 @@ too (and can also report changes to a submodule's work tree).
 init::
        Initialize the submodules, i.e. register each submodule name
        and url found in .gitmodules into .git/config.
+       It will also copy the value of `submodule.$name.update` into
+       .git/config.
        The key used in .git/config is `submodule.$name.url`.
        This command does not alter existing information in .git/config.
        You can then customize the submodule clone URLs in .git/config
@@ -138,7 +140,7 @@ update::
        checkout the commit specified in the index of the containing repository.
        This will make the submodules HEAD be detached unless `--rebase` or
        `--merge` is specified or the key `submodule.$name.update` is set to
-       `rebase` or `merge`.
+       `rebase`, `merge` or `none`.
 +
 If the submodule is not yet initialized, and you just want to use the
 setting as stored in .gitmodules, you can automatically initialize the
@@ -146,6 +148,10 @@ submodule with the `--init` option.
 +
 If `--recursive` is specified, this command will recurse into the
 registered submodules, and update any nested submodules within.
++
+If the configuration key `submodule.$name.update` is set to `none` the
+submodule with name `$name` will not be updated by default. This can be
+overriden by adding `--checkout` to the command.
 
 summary::
        Show commit summary between the given commit (defaults to HEAD) and
index f977e8780b5e152b18d07a61fde8c2f86f98a0ce..34ee785064128bce0feb2cfc86d3040b42a8f428 100644 (file)
@@ -234,6 +234,14 @@ svn:mergeinfo property in the SVN repository when possible. Currently, this can
 only be done when dcommitting non-fast-forward merges where all parents but the
 first have already been pushed into SVN.
 
+--interactive;;
+       Ask the user to confirm that a patch set should actually be sent to SVN.
+       For each patch, one may answer "yes" (accept this patch), "no" (discard this
+       patch), "all" (accept all patches), or "quit".
+       +
+       'git svn dcommit' returns immediately if answer if "no" or "quit", without
+       commiting anything to SVN.
+
 'branch'::
        Create a branch in the SVN repository.
 
index fb1c0ac694bdac20e2fef74710df35c1c4b1e774..53ff5f6cf7b9420933b022accace1355db6337c6 100644 (file)
@@ -38,27 +38,34 @@ created (i.e. a lightweight tag).
 A GnuPG signed tag object will be created when `-s` or `-u
 <key-id>` is used.  When `-u <key-id>` is not used, the
 committer identity for the current user is used to find the
-GnuPG key for signing.
+GnuPG key for signing.         The configuration variable `gpg.program`
+is used to specify custom GnuPG binary.
+
 
 OPTIONS
 -------
 -a::
+--annotate::
        Make an unsigned, annotated tag object
 
 -s::
-       Make a GPG-signed tag, using the default e-mail address's key
+--sign::
+       Make a GPG-signed tag, using the default e-mail address's key.
 
 -u <key-id>::
-       Make a GPG-signed tag, using the given key
+--local-user=<key-id>::
+       Make a GPG-signed tag, using the given key.
 
 -f::
 --force::
        Replace an existing tag with the given name (instead of failing)
 
 -d::
+--delete::
        Delete existing tags with the given names.
 
 -v::
+--verify::
        Verify the gpg signature of the given tag names.
 
 -n<num>::
@@ -69,6 +76,7 @@ OPTIONS
        If the tag is not annotated, the commit message is displayed instead.
 
 -l <pattern>::
+--list <pattern>::
        List tags with names that match the given pattern (or all if no
        pattern is given).  Running "git tag" without arguments also
        lists all tags. The pattern is a shell wildcard (i.e., matched
@@ -79,6 +87,7 @@ OPTIONS
        Only list tags which contain the specified commit.
 
 -m <msg>::
+--message=<msg>::
        Use the given tag message (instead of prompting).
        If multiple `-m` options are given, their values are
        concatenated as separate paragraphs.
@@ -86,11 +95,19 @@ OPTIONS
        is given.
 
 -F <file>::
+--file=<file>::
        Take the tag message from the given file.  Use '-' to
        read the message from the standard input.
        Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
        is given.
 
+--cleanup=<mode>::
+       This option sets how the tag message is cleaned up.
+       The  '<mode>' can be one of 'verbatim', 'whitespace' and 'strip'.  The
+       'strip' mode is default. The 'verbatim' mode does not change message at
+       all, 'whitespace' removes just leading/trailing whitespace lines and
+       'strip' removes both whitespace and commentary.
+
 <tagname>::
        The name of the tag to create, delete, or describe.
        The new tag name must pass all checks defined by
index 6803e1309f0eaf43ec483e3344e886876bacf741..458cc076fcb80e4f89771bdcc3b200a1117ea158 100644 (file)
@@ -9,11 +9,11 @@ git - the stupid content tracker
 SYNOPSIS
 --------
 [verse]
-'git' [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
+'git' [--version] [--help] [-c <name>=<value>]
+    [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
     [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
     [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
-    [-c <name>=<value>]
-    [--help] <command> [<args>]
+    <command> [<args>]
 
 DESCRIPTION
 -----------
@@ -44,9 +44,28 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.7.7.5/git.html[documentation for release 1.7.7.5]
+* link:v1.7.9.4/git.html[documentation for release 1.7.9.4]
 
 * release notes for
+  link:RelNotes/1.7.9.4.txt[1.7.9.4],
+  link:RelNotes/1.7.9.3.txt[1.7.9.3],
+  link:RelNotes/1.7.9.2.txt[1.7.9.2],
+  link:RelNotes/1.7.9.1.txt[1.7.9.1],
+  link:RelNotes/1.7.9.txt[1.7.9].
+
+* link:v1.7.8.4/git.html[documentation for release 1.7.8.4]
+
+* release notes for
+  link:RelNotes/1.7.8.4.txt[1.7.8.4],
+  link:RelNotes/1.7.8.3.txt[1.7.8.3],
+  link:RelNotes/1.7.8.2.txt[1.7.8.2],
+  link:RelNotes/1.7.8.1.txt[1.7.8.1],
+  link:RelNotes/1.7.8.txt[1.7.8].
+
+* link:v1.7.7.6/git.html[documentation for release 1.7.7.6]
+
+* release notes for
+  link:RelNotes/1.7.7.6.txt[1.7.7.6],
   link:RelNotes/1.7.7.5.txt[1.7.7.5],
   link:RelNotes/1.7.7.4.txt[1.7.7.4],
   link:RelNotes/1.7.7.3.txt[1.7.7.3],
@@ -692,6 +711,12 @@ other
        a pager.  See also the `core.pager` option in
        linkgit:git-config[1].
 
+'GIT_EDITOR'::
+       This environment variable overrides `$EDITOR` and `$VISUAL`.
+       It is used by several git comands when, on interactive mode,
+       an editor is to be launched. See also linkgit:git-var[1]
+       and the `core.editor` option in linkgit:git-config[1].
+
 'GIT_SSH'::
        If this environment variable is set then 'git fetch'
        and 'git push' will use this command instead
index 25e46aeb7a32287c7dc2666d2633c4787ae1c59a..a85b187e0479b99e137160c1190f174d80675fa1 100644 (file)
@@ -500,6 +500,8 @@ patterns are available:
 
 - `java` suitable for source code in the Java language.
 
+- `matlab` suitable for source code in the MATLAB language.
+
 - `objc` suitable for source code in the Objective-C language.
 
 - `pascal` suitable for source code in the Pascal/Delphi language.
index c27d086f68432d094a0fa7c1f9da353d6d3a0513..fb0d5692a4d2e898da0dea9c346420467722badc 100644 (file)
@@ -1004,7 +1004,7 @@ Updating from ae3a2da... to a80b4aa....
 Fast-forward (no commit created; -m option ignored)
  example |    1 +
  hello   |    1 +
- 2 files changed, 2 insertions(+), 0 deletions(-)
+ 2 files changed, 2 insertions(+)
 ----------------
 
 Because your branch did not contain anything more than what had
diff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt
new file mode 100644 (file)
index 0000000..066f825
--- /dev/null
@@ -0,0 +1,183 @@
+gitcredentials(7)
+=================
+
+NAME
+----
+gitcredentials - providing usernames and passwords to git
+
+SYNOPSIS
+--------
+------------------
+git config credential.https://example.com.username myusername
+git config credential.helper "$helper $options"
+------------------
+
+DESCRIPTION
+-----------
+
+Git will sometimes need credentials from the user in order to perform
+operations; for example, it may need to ask for a username and password
+in order to access a remote repository over HTTP. This manual describes
+the mechanisms git uses to request these credentials, as well as some
+features to avoid inputting these credentials repeatedly.
+
+REQUESTING CREDENTIALS
+----------------------
+
+Without any credential helpers defined, git will try the following
+strategies to ask the user for usernames and passwords:
+
+1. If the `GIT_ASKPASS` environment variable is set, the program
+   specified by the variable is invoked. A suitable prompt is provided
+   to the program on the command line, and the user's input is read
+   from its standard output.
+
+2. Otherwise, if the `core.askpass` configuration variable is set, its
+   value is used as above.
+
+3. Otherwise, if the `SSH_ASKPASS` environment variable is set, its
+   value is used as above.
+
+4. Otherwise, the user is prompted on the terminal.
+
+AVOIDING REPETITION
+-------------------
+
+It can be cumbersome to input the same credentials over and over.  Git
+provides two methods to reduce this annoyance:
+
+1. Static configuration of usernames for a given authentication context.
+
+2. Credential helpers to cache or store passwords, or to interact with
+   a system password wallet or keychain.
+
+The first is simple and appropriate if you do not have secure storage available
+for a password. It is generally configured by adding this to your config:
+
+---------------------------------------
+[credential "https://example.com"]
+       username = me
+---------------------------------------
+
+Credential helpers, on the other hand, are external programs from which git can
+request both usernames and passwords; they typically interface with secure
+storage provided by the OS or other programs.
+
+To use a helper, you must first select one to use. Git currently
+includes the following helpers:
+
+cache::
+
+       Cache credentials in memory for a short period of time. See
+       linkgit:git-credential-cache[1] for details.
+
+store::
+
+       Store credentials indefinitely on disk. See
+       linkgit:git-credential-store[1] for details.
+
+You may also have third-party helpers installed; search for
+`credential-*` in the output of `git help -a`, and consult the
+documentation of individual helpers.  Once you have selected a helper,
+you can tell git to use it by putting its name into the
+credential.helper variable.
+
+1. Find a helper.
++
+-------------------------------------------
+$ git help -a | grep credential-
+credential-foo
+-------------------------------------------
+
+2. Read its description.
++
+-------------------------------------------
+$ git help credential-foo
+-------------------------------------------
+
+3. Tell git to use it.
++
+-------------------------------------------
+$ git config --global credential.helper foo
+-------------------------------------------
+
+If there are multiple instances of the `credential.helper` configuration
+variable, each helper will be tried in turn, and may provide a username,
+password, or nothing. Once git has acquired both a username and a
+password, no more helpers will be tried.
+
+
+CREDENTIAL CONTEXTS
+-------------------
+
+Git considers each credential to have a context defined by a URL. This context
+is used to look up context-specific configuration, and is passed to any
+helpers, which may use it as an index into secure storage.
+
+For instance, imagine we are accessing `https://example.com/foo.git`. When git
+looks into a config file to see if a section matches this context, it will
+consider the two a match if the context is a more-specific subset of the
+pattern in the config file. For example, if you have this in your config file:
+
+--------------------------------------
+[credential "https://example.com"]
+       username = foo
+--------------------------------------
+
+then we will match: both protocols are the same, both hosts are the same, and
+the "pattern" URL does not care about the path component at all. However, this
+context would not match:
+
+--------------------------------------
+[credential "https://kernel.org"]
+       username = foo
+--------------------------------------
+
+because the hostnames differ. Nor would it match `foo.example.com`; git
+compares hostnames exactly, without considering whether two hosts are part of
+the same domain. Likewise, a config entry for `http://example.com` would not
+match: git compares the protocols exactly.
+
+
+CONFIGURATION OPTIONS
+---------------------
+
+Options for a credential context can be configured either in
+`credential.\*` (which applies to all credentials), or
+`credential.<url>.\*`, where <url> matches the context as described
+above.
+
+The following options are available in either location:
+
+helper::
+
+       The name of an external credential helper, and any associated options.
+       If the helper name is not an absolute path, then the string `git
+       credential-` is prepended. The resulting string is executed by the
+       shell (so, for example, setting this to `foo --option=bar` will execute
+       `git credential-foo --option=bar` via the shell. See the manual of
+       specific helpers for examples of their use.
+
+username::
+
+       A default username, if one is not provided in the URL.
+
+useHttpPath::
+
+       By default, git does not consider the "path" component of an http URL
+       to be worth matching via external helpers. This means that a credential
+       stored for `https://example.com/foo.git` will also be used for
+       `https://example.com/bar.git`. If you do want to distinguish these
+       cases, set this option to `true`.
+
+
+CUSTOM HELPERS
+--------------
+
+You can write your own custom helpers to interface with any system in
+which you keep credentials. See the documentation for git's
+link:technical/api-credentials.html[credentials API] for details.
+
+GIT
+---
+Part of the linkgit:git[1] suite
index f1e4422acc4ddba515da5617759f818b52cb151d..e00a4d21709118ef352bd02538e28569f00b738d 100644 (file)
@@ -34,12 +34,12 @@ $ echo 'hello world' > file.txt
 $ git add .
 $ git commit -a -m "initial commit"
 [master (root-commit) 54196cc] initial commit
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ 1 file changed, 1 insertion(+)
  create mode 100644 file.txt
 $ echo 'hello world!' >file.txt
 $ git commit -a -m "add emphasis"
 [master c4d59f3] add emphasis
- 1 files changed, 1 insertions(+), 1 deletions(-)
+ 1 file changed, 1 insertion(+), 1 deletion(-)
 ------------------------------------------------
 
 What are the 7 digits of hex that git responded to the commit with?
diff --git a/Documentation/gitweb.conf.txt b/Documentation/gitweb.conf.txt
new file mode 100644 (file)
index 0000000..7aba497
--- /dev/null
@@ -0,0 +1,889 @@
+gitweb.conf(5)
+==============
+
+NAME
+----
+gitweb.conf - Gitweb (git web interface) configuration file
+
+SYNOPSIS
+--------
+/etc/gitweb.conf, /etc/gitweb-common.conf, $GITWEBDIR/gitweb_config.perl
+
+DESCRIPTION
+-----------
+
+The gitweb CGI script for viewing Git repositories over the web uses a
+perl script fragment as its configuration file.  You can set variables
+using "`our $variable = value`"; text from a "#" character until the
+end of a line is ignored.  See *perlsyn*(1) for details.
+
+An example:
+
+    # gitweb configuration file for http://git.example.org
+    #
+    our $projectroot = "/srv/git"; # FHS recommendation
+    our $site_name = 'Example.org >> Repos';
+
+
+The configuration file is used to override the default settings that
+were built into gitweb at the time the 'gitweb.cgi' script was generated.
+
+While one could just alter the configuration settings in the gitweb
+CGI itself, those changes would be lost upon upgrade.  Configuration
+settings might also be placed into a file in the same directory as the
+CGI script with the default name 'gitweb_config.perl' -- allowing
+one to have multiple gitweb instances with different configurations by
+the use of symlinks.
+
+Note that some configuration can be controlled on per-repository rather than
+gitweb-wide basis: see "Per-repository gitweb configuration" subsection on
+linkgit:gitweb[1] manpage.
+
+
+DISCUSSION
+----------
+Gitweb reads configuration data from the following sources in the
+following order:
+
+ * built-in values (some set during build stage),
+
+ * common system-wide configuration file (defaults to
+   '/etc/gitweb-common.conf'),
+
+ * either per-instance configuration file (defaults to 'gitweb_config.perl'
+   in the same directory as the installed gitweb), or if it does not exists
+   then fallback system-wide configuration file (defaults to '/etc/gitweb.conf').
+
+Values obtained in later configuration files override values obtained earlier
+in the above sequence.
+
+Locations of the common system-wide configuration file, the fallback
+system-wide configuration file and the per-instance configuration file
+are defined at compile time using build-time Makefile configuration
+variables, respectively `GITWEB_CONFIG_COMMON`, `GITWEB_CONFIG_SYSTEM`
+and `GITWEB_CONFIG`.
+
+You can also override locations of gitweb configuration files during
+runtime by setting the following environment variables:
+`GITWEB_CONFIG_COMMON`, `GITWEB_CONFIG_SYSTEM` and `GITWEB_CONFIG`
+to a non-empty value.
+
+
+The syntax of the configuration files is that of Perl, since these files are
+handled by sourcing them as fragments of Perl code (the language that
+gitweb itself is written in). Variables are typically set using the
+`our` qualifier (as in "`our $variable = <value>;`") to avoid syntax
+errors if a new version of gitweb no longer uses a variable and therefore
+stops declaring it.
+
+You can include other configuration file using read_config_file()
+subroutine.  For example, one might want to put gitweb configuration
+related to access control for viewing repositories via Gitolite (one
+of git repository management tools) in a separate file, e.g. in
+'/etc/gitweb-gitolite.conf'.  To include it, put
+
+--------------------------------------------------
+read_config_file("/etc/gitweb-gitolite.conf");
+--------------------------------------------------
+
+somewhere in gitweb configuration file used, e.g. in per-installation
+gitweb configuration file.  Note that read_config_file() checks itself
+that the file it reads exists, and does nothing if it is not found.
+It also handles errors in included file.
+
+
+The default configuration with no configuration file at all may work
+perfectly well for some installations.  Still, a configuration file is
+useful for customizing or tweaking the behavior of gitweb in many ways, and
+some optional features will not be present unless explicitly enabled using
+the configurable `%features` variable (see also "Configuring gitweb
+features" section below).
+
+
+CONFIGURATION VARIABLES
+-----------------------
+Some configuration variables have their default values (embedded in the CGI
+script) set during building gitweb -- if that is the case, this fact is put
+in their description.  See gitweb's 'INSTALL' file for instructions on building
+and installing gitweb.
+
+
+Location of repositories
+~~~~~~~~~~~~~~~~~~~~~~~~
+The configuration variables described below control how gitweb finds
+git repositories, and how repositories are displayed and accessed.
+
+See also "Repositories" and later subsections in linkgit:gitweb[1] manpage.
+
+$projectroot::
+       Absolute filesystem path which will be prepended to project path;
+       the path to repository is `$projectroot/$project`.  Set to
+       `$GITWEB_PROJECTROOT` during installation.  This variable has to be
+       set correctly for gitweb to find repositories.
++
+For example, if `$projectroot` is set to "/srv/git" by putting the following
+in gitweb config file:
++
+----------------------------------------------------------------------------
+our $projectroot = "/srv/git";
+----------------------------------------------------------------------------
++
+then
++
+------------------------------------------------
+http://git.example.com/gitweb.cgi?p=foo/bar.git
+------------------------------------------------
++
+and its path_info based equivalent
++
+------------------------------------------------
+http://git.example.com/gitweb.cgi/foo/bar.git
+------------------------------------------------
++
+will map to the path '/srv/git/foo/bar.git' on the filesystem.
+
+$projects_list::
+       Name of a plain text file listing projects, or a name of directory
+       to be scanned for projects.
++
+Project list files should list one project per line, with each line
+having the following format
++
+-----------------------------------------------------------------------------
+<URI-encoded filesystem path to repository> SP <URI-encoded repository owner>
+-----------------------------------------------------------------------------
++
+The default value of this variable is determined by the `GITWEB_LIST`
+makefile variable at installation time.  If this variable is empty, gitweb
+will fall back to scanning the `$projectroot` directory for repositories.
+
+$project_maxdepth::
+       If `$projects_list` variable is unset, gitweb will recursively
+       scan filesystem for git repositories.  The `$project_maxdepth`
+       is used to limit traversing depth, relative to `$projectroot`
+       (starting point); it means that directories which are further
+       from `$projectroot` than `$project_maxdepth` will be skipped.
++
+It is purely performance optimization, originally intended for MacOS X,
+where recursive directory traversal is slow.  Gitweb follows symbolic
+links, but it detects cycles, ignoring any duplicate files and directories.
++
+The default value of this variable is determined by the build-time
+configuration variable `GITWEB_PROJECT_MAXDEPTH`, which defaults to
+2007.
+
+$export_ok::
+       Show repository only if this file exists (in repository).  Only
+       effective if this variable evaluates to true.  Can be set when
+       building gitweb by setting `GITWEB_EXPORT_OK`.  This path is
+       relative to `GIT_DIR`.  git-daemon[1] uses 'git-daemon-export-ok',
+       unless started with `--export-all`.  By default this variable is
+       not set, which means that this feature is turned off.
+
+$export_auth_hook::
+       Function used to determine which repositories should be shown.
+       This subroutine should take one parameter, the full path to
+       a project, and if it returns true, that project will be included
+       in the projects list and can be accessed through gitweb as long
+       as it fulfills the other requirements described by $export_ok,
+       $projects_list, and $projects_maxdepth.  Example:
++
+----------------------------------------------------------------------------
+our $export_auth_hook = sub { return -e "$_[0]/git-daemon-export-ok"; };
+----------------------------------------------------------------------------
++
+though the above might be done by using `$export_ok` instead
++
+----------------------------------------------------------------------------
+our $export_ok = "git-daemon-export-ok";
+----------------------------------------------------------------------------
++
+If not set (default), it means that this feature is disabled.
++
+See also more involved example in "Controlling access to git repositories"
+subsection on linkgit:gitweb[1] manpage.
+
+$strict_export::
+       Only allow viewing of repositories also shown on the overview page.
+       This for example makes `$gitweb_export_ok` file decide if repository is
+       available and not only if it is shown.  If `$gitweb_list` points to
+       file with list of project, only those repositories listed would be
+       available for gitweb.  Can be set during building gitweb via
+       `GITWEB_STRICT_EXPORT`.  By default this variable is not set, which
+       means that you can directly access those repositories that are hidden
+       from projects list page (e.g. the are not listed in the $projects_list
+       file).
+
+
+Finding files
+~~~~~~~~~~~~~
+The following configuration variables tell gitweb where to find files.
+The values of these variables are paths on the filesystem.
+
+$GIT::
+       Core git executable to use.  By default set to `$GIT_BINDIR/git`, which
+       in turn is by default set to `$(bindir)/git`.  If you use git installed
+       from a binary package, you should usually set this to "/usr/bin/git".
+       This can just be "git" if your web server has a sensible PATH; from
+       security point of view it is better to use absolute path to git binary.
+       If you have multiple git versions installed it can be used to choose
+       which one to use.  Must be (correctly) set for gitweb to be able to
+       work.
+
+$mimetypes_file::
+       File to use for (filename extension based) guessing of MIME types before
+       trying '/etc/mime.types'.  *NOTE* that this path, if relative, is taken
+       as relative to the current git repository, not to CGI script.  If unset,
+       only '/etc/mime.types' is used (if present on filesystem).  If no mimetypes
+       file is found, mimetype guessing based on extension of file is disabled.
+       Unset by default.
+
+$highlight_bin::
+       Path to the highlight executable to use (it must be the one from
+       http://www.andre-simon.de[] due to assumptions about parameters and output).
+       By default set to 'highlight'; set it to full path to highlight
+       executable if it is not installed on your web server's PATH.
+       Note that 'highlight' feature must be set for gitweb to actually
+       use syntax hightlighting.
++
+*NOTE*: if you want to add support for new file type (supported by
+"highlight" but not used by gitweb), you need to modify `%highlight_ext`
+or `%highlight_basename`, depending on whether you detect type of file
+based on extension (for example "sh") or on its basename (for example
+"Makefile").  The keys of these hashes are extension and basename,
+respectively, and value for given key is name of syntax to be passed via
+`--syntax <syntax>` to highlighter.
++
+For example if repositories you are hosting use "phtml" extension for
+PHP files, and you want to have correct syntax-highlighting for those
+files, you can add the following to gitweb configuration:
++
+---------------------------------------------------------
+our %highlight_ext;
+$highlight_ext{'phtml'} = 'php';
+---------------------------------------------------------
+
+
+Links and their targets
+~~~~~~~~~~~~~~~~~~~~~~~
+The configuration variables described below configure some of gitweb links:
+their target and their look (text or image), and where to find page
+prerequisites (stylesheet, favicon, images, scripts).  Usually they are left
+at their default values, with the possible exception of `@stylesheets`
+variable.
+
+@stylesheets::
+       List of URIs of stylesheets (relative to the base URI of a page). You
+       might specify more than one stylesheet, for example to use "gitweb.css"
+       as base with site specific modifications in a separate stylesheet
+       to make it easier to upgrade gitweb.  For example, you can add
+       a `site` stylesheet by putting
++
+----------------------------------------------------------------------------
+push @stylesheets, "gitweb-site.css";
+----------------------------------------------------------------------------
++
+in the gitweb config file.  Those values that are relative paths are
+relative to base URI of gitweb.
++
+This list should contain the URI of gitweb's standard stylesheet.  The default
+URI of gitweb stylesheet can be set at build time using the `GITWEB_CSS`
+makefile variable.  Its default value is 'static/gitweb.css'
+(or 'static/gitweb.min.css' if the `CSSMIN` variable is defined,
+i.e. if CSS minifier is used during build).
++
+*Note*: there is also a legacy `$stylesheet` configuration variable, which was
+used by older gitweb.  If `$stylesheet` variable is defined, only CSS stylesheet
+given by this variable is used by gitweb.
+
+$logo::
+       Points to the location where you put 'git-logo.png' on your web
+       server, or to be more the generic URI of logo, 72x27 size).  This image
+       is displayed in the top right corner of each gitweb page and used as
+       a logo for the Atom feed.  Relative to the base URI of gitweb (as a path).
+       Can be adjusted when building gitweb using `GITWEB_LOGO` variable
+       By default set to 'static/git-logo.png'.
+
+$favicon::
+       Points to the location where you put 'git-favicon.png' on your web
+       server, or to be more the generic URI of favicon, which will be served
+       as "image/png" type.  Web browsers that support favicons (website icons)
+       may display them in the browser's URL bar and next to the site name in
+       bookmarks.  Relative to the base URI of gitweb.  Can be adjusted at
+       build time using `GITWEB_FAVICON` variable.
+       By default set to 'static/git-favicon.png'.
+
+$javascript::
+       Points to the location where you put 'gitweb.js' on your web server,
+       or to be more generic the URI of JavaScript code used by gitweb.
+       Relative to the base URI of gitweb.  Can be set at build time using
+       the `GITWEB_JS` build-time configuration variable.
++
+The default value is either 'static/gitweb.js', or 'static/gitweb.min.js' if
+the `JSMIN` build variable was defined, i.e. if JavaScript minifier was used
+at build time.  *Note* that this single file is generated from multiple
+individual JavaScript "modules".
+
+$home_link::
+       Target of the home link on the top of all pages (the first part of view
+       "breadcrumbs").  By default it is set to the absolute URI of a current page
+       (to the value of `$my_uri` variable, or to "/" if `$my_uri` is undefined
+       or is an empty string).
+
+$home_link_str::
+       Label for the "home link" at the top of all pages, leading to `$home_link`
+       (usually the main gitweb page, which contains the projects list).  It is
+       used as the first component of gitweb's "breadcrumb trail":
+       `<home link> / <project> / <action>`.  Can be set at build time using
+       the `GITWEB_HOME_LINK_STR` variable.  By default it is set to "projects",
+       as this link leads to the list of projects.  Other popular choice it to
+       set it to the name of site.
+
+$logo_url::
+$logo_label::
+       URI and label (title) for the Git logo link (or your site logo,
+       if you chose to use different logo image). By default, these both
+       refer to git homepage, http://git-scm.com[]; in the past, they pointed
+       to git documentation at http://www.kernel.org[].
+
+
+Changing gitweb's look
+~~~~~~~~~~~~~~~~~~~~~~
+You can adjust how pages generated by gitweb look using the variables described
+below.  You can change the site name, add common headers and footers for all
+pages, and add a description of this gitweb installation on its main page
+(which is the projects list page), etc.
+
+$site_name::
+       Name of your site or organization, to appear in page titles.  Set it
+       to something descriptive for clearer bookmarks etc.  If this variable
+       is not set or is, then gitweb uses the value of the `SERVER_NAME`
+       CGI environment variable, setting site name to "$SERVER_NAME Git",
+       or "Untitled Git" if this variable is not set (e.g. if running gitweb
+       as standalone script).
++
+Can be set using the `GITWEB_SITENAME` at build time.  Unset by default.
+
+$site_html_head_string::
+       HTML snippet to be included in the <head> section of each page.
+       Can be set using `GITWEB_SITE_HTML_HEAD_STRING` at build time.
+       No default value.
+
+$site_header::
+       Name of a file with HTML to be included at the top of each page.
+       Relative to the directory containing the 'gitweb.cgi' script.
+       Can be set using `GITWEB_SITE_HEADER` at build time.  No default
+       value.
+
+$site_footer::
+       Name of a file with HTML to be included at the bottom of each page.
+       Relative to the directory containing the 'gitweb.cgi' script.
+       Can be set using `GITWEB_SITE_FOOTER` at build time.  No default
+       value.
+
+$home_text::
+       Name of a HTML file which, if it exists, is included on the
+       gitweb projects overview page ("projects_list" view).  Relative to
+       the directory containing the gitweb.cgi script.  Default value
+       can be adjusted during build time using `GITWEB_HOMETEXT` variable.
+       By default set to 'indextext.html'.
+
+$projects_list_description_width::
+       The width (in characters) of the "Description" column of the projects list.
+       Longer descriptions will be truncated (trying to cut at word boundary);
+       the full description is available in the 'title' attribute (usually shown on
+       mouseover).  The default is 25, which might be too small if you
+       use long project descriptions.
+
+$default_projects_order::
+       Default value of ordering of projects on projects list page, which
+       means the ordering used if you don't explicitly sort projects list
+       (if there is no "o" CGI query parameter in the URL).  Valid values
+       are "none" (unsorted), "project" (projects are by project name,
+       i.e. path to repository relative to `$projectroot`), "descr"
+       (project description), "owner", and "age" (by date of most current
+       commit).
++
+Default value is "project".  Unknown value means unsorted.
+
+
+Changing gitweb's behavior
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+These configuration variables control _internal_ gitweb behavior.
+
+$default_blob_plain_mimetype::
+       Default mimetype for the blob_plain (raw) view, if mimetype checking
+       doesn't result in some other type; by default "text/plain".
+       Gitweb guesses mimetype of a file to display based on extension
+       of its filename, using `$mimetypes_file` (if set and file exists)
+       and '/etc/mime.types' files (see *mime.types*(5) manpage; only
+       filename extension rules are supported by gitweb).
+
+$default_text_plain_charset::
+       Default charset for text files. If this is not set, the web server
+       configuration will be used.  Unset by default.
+
+$fallback_encoding::
+       Gitweb assumes this charset when a line contains non-UTF-8 characters.
+       The fallback decoding is used without error checking, so it can be even
+       "utf-8". The value must be a valid encoding; see the *Encoding::Supported*(3pm)
+       man page for a list. The default is "latin1", aka. "iso-8859-1".
+
+@diff_opts::
+       Rename detection options for git-diff and git-diff-tree. The default is
+       (\'-M'); set it to (\'-C') or (\'-C', \'-C') to also detect copies,
+       or set it to () i.e. empty list if you don't want to have renames
+       detection.
++
+*Note* that rename and especially copy detection can be quite
+CPU-intensive.  Note also that non git tools can have problems with
+patches generated with options mentioned above, especially when they
+involve file copies (\'-C') or criss-cross renames (\'-B').
+
+
+Some optional features and policies
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Most of features are configured via `%feature` hash; however some of extra
+gitweb features can be turned on and configured using variables described
+below.  This list beside configuration variables that control how gitweb
+looks does contain variables configuring administrative side of gitweb
+(e.g. cross-site scripting prevention; admittedly this as side effect
+affects how "summary" pages look like, or load limiting).
+
+@git_base_url_list::
+       List of git base URLs.  These URLs are used to generate URLs
+       describing from where to fetch a project, which are shown on
+       project summary page.  The full fetch URL is "`$git_base_url/$project`",
+       for each element of this list. You can set up multiple base URLs
+       (for example one for `git://` protocol, and one for `http://`
+       protocol).
++
+Note that per repository configuration can be set in '$GIT_DIR/cloneurl'
+file, or as values of multi-value `gitweb.url` configuration variable in
+project config.  Per-repository configuration takes precedence over value
+composed from `@git_base_url_list` elements and project name.
++
+You can setup one single value (single entry/item in this list) at build
+time by setting the `GITWEB_BASE_URL` built-time configuration variable.
+By default it is set to (), i.e. an empty list.  This means that gitweb
+would not try to create project URL (to fetch) from project name.
+
+$projects_list_group_categories::
+       Whether to enables the grouping of projects by category on the project
+       list page. The category of a project is determined by the
+       `$GIT_DIR/category` file or the `gitweb.category` variable in each
+       repository's configuration.  Disabled by default (set to 0).
+
+$project_list_default_category::
+       Default category for projects for which none is specified.  If this is
+       set to the empty string, such projects will remain uncategorized and
+       listed at the top, above categorized projects.  Used only if project
+       categories are enabled, which means if `$projects_list_group_categories`
+       is true.  By default set to "" (empty string).
+
+$prevent_xss::
+       If true, some gitweb features are disabled to prevent content in
+       repositories from launching cross-site scripting (XSS) attacks.  Set this
+       to true if you don't trust the content of your repositories.
+       False by default (set to 0).
+
+$maxload::
+       Used to set the maximum load that we will still respond to gitweb queries.
+       If the server load exceeds this value then gitweb will return
+       "503 Service Unavailable" error.  The server load is taken to be 0
+       if gitweb cannot determine its value.  Currently it works only on Linux,
+       where it uses '/proc/loadavg'; the load there is the number of active
+       tasks on the system -- processes that are actually running -- averaged
+       over the last minute.
++
+Set `$maxload` to undefined value (`undef`) to turn this feature off.
+The default value is 300.
+
+$per_request_config::
+       If this is set to code reference, it will be run once for each request.
+       You can set parts of configuration that change per session this way.
+       For example, one might use the following code in a gitweb configuration
+       file
++
+--------------------------------------------------------------------------------
+our $per_request_config = sub {
+       $ENV{GL_USER} = $cgi->remote_user || "gitweb";
+};
+--------------------------------------------------------------------------------
++
+If `$per_request_config` is not a code reference, it is interpreted as boolean
+value.  If it is true gitweb will process config files once per request,
+and if it is false gitweb will process config files only once, each time it
+is executed.  True by default (set to 1).
++
+*NOTE*: `$my_url`, `$my_uri`, and `$base_url` are overwritten with their default
+values before every request, so if you want to change them, be sure to set
+this variable to true or a code reference effecting the desired changes.
++
+This variable matters only when using persistent web environments that
+serve multiple requests using single gitweb instance, like mod_perl,
+FastCGI or Plackup.
+
+
+Other variables
+~~~~~~~~~~~~~~~
+Usually you should not need to change (adjust) any of configuration
+variables described below; they should be automatically set by gitweb to
+correct value.
+
+
+$version::
+       Gitweb version, set automatically when creating gitweb.cgi from
+       gitweb.perl. You might want to modify it if you are running modified
+       gitweb, for example
++
+---------------------------------------------------
+our $version .= " with caching";
+---------------------------------------------------
++
+if you run modified version of gitweb with caching support.  This variable
+is purely informational, used e.g. in the "generator" meta header in HTML
+header.
+
+$my_url::
+$my_uri::
+       Full URL and absolute URL of the gitweb script;
+       in earlier versions of gitweb you might have need to set those
+       variables, but now there should be no need to do it.  See
+       `$per_request_config` if you need to set them still.
+
+$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 '<base href="$base_url">' only for URLs with nonempty
+       PATH_INFO.  Usually gitweb sets its value correctly,
+       and there is no need to set this variable, e.g. to $my_uri or "/".
+       See `$per_request_config` if you need to override it anyway.
+
+
+CONFIGURING GITWEB FEATURES
+---------------------------
+Many gitweb features can be enabled (or disabled) and configured using the
+`%feature` hash.  Names of gitweb features are keys of this hash.
+
+Each `%feature` hash element is a hash reference and has the following
+structure:
+----------------------------------------------------------------------
+"<feature_name>" => {
+       "sub" => <feature-sub (subroutine)>,
+       "override" => <allow-override (boolean)>,
+       "default" => [ <options>... ]
+},
+----------------------------------------------------------------------
+Some features cannot be overridden per project.  For those
+features the structure of appropriate `%feature` hash element has a simpler
+form:
+----------------------------------------------------------------------
+"<feature_name>" => {
+       "override" => 0,
+       "default" => [ <options>... ]
+},
+----------------------------------------------------------------------
+As one can see it lacks the \'sub' element.
+
+The meaning of each part of feature configuration is described
+below:
+
+default::
+       List (array reference) of feature parameters (if there are any),
+       used also to toggle (enable or disable) given feature.
++
+Note that it is currently *always* an array reference, even if
+feature doesn't accept any configuration parameters, and \'default'
+is used only to turn it on or off.  In such case you turn feature on
+by setting this element to `[1]`, and torn it off by setting it to
+`[0]`.  See also the passage about the "blame" feature in the "Examples"
+section.
++
+To disable features that accept parameters (are configurable), you
+need to set this element to empty list i.e. `[]`.
+
+override::
+       If this field has a true value then the given feature is
+       overriddable, which means that it can be configured
+       (or enabled/disabled) on a per-repository basis.
++
+Usually given "<feature>" is configurable via the `gitweb.<feature>`
+config variable in the per-repository git configuration file.
++
+*Note* that no feature is overriddable by default.
+
+sub::
+       Internal detail of implementation.  What is important is that
+       if this field is not present then per-repository override for
+       given feature is not supported.
++
+You wouldn't need to ever change it in gitweb config file.
+
+
+Features in `%feature`
+~~~~~~~~~~~~~~~~~~~~~~
+The gitweb features that are configurable via `%feature` hash are listed
+below.  This should be a complete list, but ultimately the authoritative
+and complete list is in gitweb.cgi source code, with features described
+in the comments.
+
+blame::
+       Enable the "blame" and "blame_incremental" blob views, showing for
+       each line the last commit that modified it; see linkgit:git-blame[1].
+       This can be very CPU-intensive and is therefore disabled by default.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.blame` configuration variable (boolean).
+
+snapshot::
+       Enable and configure the "snapshot" action, which allows user to
+       download a compressed archive of any tree or commit, as produced
+       by linkgit:git-archive[1] and possibly additionally compressed.
+       This can potentially generate high traffic if you have large project.
++
+The value of \'default' is a list of names of snapshot formats,
+defined in `%known_snapshot_formats` hash, that you wish to offer.
+Supported formats include "tgz", "tbz2", "txz" (gzip/bzip2/xz
+compressed tar archive) and "zip"; please consult gitweb sources for
+a definitive list.  By default only "tgz" is offered.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.blame` configuration variable, which contains
+a comma separated list of formats or "none" to disable snapshots.
+Unknown values are ignored.
+
+grep::
+       Enable grep search, which lists the files in currently selected
+       tree (directory) containing the given string; see linkgit:git-grep[1].
+       This can be potentially CPU-intensive, of course.  Enabled by default.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.grep` configuration variable (boolean).
+
+pickaxe::
+       Enable the so called pickaxe search, which will list the commits
+       that introduced or removed a given string in a file.  This can be
+       practical and quite faster alternative to "blame" action, but it is
+       still potentially CPU-intensive.  Enabled by default.
++
+The pickaxe search is described in linkgit:git-log[1] (the
+description of `-S<string>` option, which refers to pickaxe entry in
+linkgit:gitdiffcore[7] for more details).
++
+This feature can be configured on a per-repository basis by setting
+repository's `gitweb.pickaxe` configuration variable (boolean).
+
+show-sizes::
+       Enable showing size of blobs (ordinary files) in a "tree" view, in a
+       separate column, similar to what `ls -l` does; see description of
+       `-l` option in linkgit:git-ls-tree[1] manpage.  This costs a bit of
+       I/O.  Enabled by default.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.showsizes` configuration variable (boolean).
+
+patches::
+       Enable and configure "patches" view, which displays list of commits in email
+       (plain text) output format; see also linkgit:git-format-patch[1].
+       The value is the maximum number of patches in a patchset generated
+       in "patches" view.  Set the 'default' field to a list containing single
+       item of or to an empty list to disable patch view, or to a list
+       containing a single negative number to remove any limit.
+       Default value is 16.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.patches` configuration variable (integer).
+
+avatar::
+       Avatar support.  When this feature is enabled, views such as
+       "shortlog" or "commit" will display an avatar associated with
+       the email of each committer and author.
++
+Currently available providers are *"gravatar"* and *"picon"*.
+Only one provider at a time can be selected ('default' is one element list).
+If an unknown provider is specified, the feature is disabled.
+*Note* that some providers might require extra Perl packages to be
+installed; see 'gitweb/INSTALL' for more details.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.avatar` configuration variable.
++
+See also `%avatar_size` with pixel sizes for icons and avatars
+("default" is used for one-line like "log" and "shortlog", "double"
+is used for two-line like "commit", "commitdiff" or "tag").  If the
+default font sizes or lineheights are changed (e.g. via adding extra
+CSS stylesheet in `@stylesheets`), it may be appropriate to change
+these values.
+
+highlight::
+       Server-side syntax highlight support in "blob" view.  It requires
+       `$highlight_bin` program to be available (see the description of
+       this variable in the "Configuration variables" section above),
+       and therefore is disabled by default.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.highlight` configuration variable (boolean).
+
+remote_heads::
+       Enable displaying remote heads (remote-tracking branches) in the "heads"
+       list.  In most cases the list of remote-tracking branches is an
+       unnecessary internal private detail, and this feature is therefore
+       disabled by default.  linkgit:git-instaweb[1], which is usually used
+       to browse local repositories, enables and uses this feature.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.remote_heads` configuration variable (boolean).
+
+
+The remaining features cannot be overridden on a per project basis.
+
+search::
+       Enable text search, which will list the commits which match author,
+       committer or commit text to a given string; see the description of
+       `--author`, `--committer` and `--grep` options in linkgit:git-log[1]
+       manpage.  Enabled by default.
++
+Project specific override is not supported.
+
+forks::
+       If this feature is enabled, gitweb considers projects in
+       subdirectories of project root (basename) to be forks of existing
+       projects.  For each project `$projname.git`, projects in the
+       `$projname/` directory and its subdirectories will not be
+       shown in the main projects list.  Instead, a \'+' mark is shown
+       next to `$projname`, which links to a "forks" view that lists all
+       the forks (all projects in `$projname/` subdirectory).  Additionally
+       a "forks" view for a project is linked from project summary page.
++
+If the project list is taken from a file (`$projects_list` points to a
+file), forks are only recognized if they are listed after the main project
+in that file.
++
+Project specific override is not supported.
+
+actions::
+       Insert custom links to the action bar of all project pages.  This
+       allows you to link to third-party scripts integrating into gitweb.
++
+The "default" value consists of a list of triplets in the form
+`("<label>", "<link>", "<position>")` where "position" is the label
+after which to insert the link, "link" is a format string where `%n`
+expands to the project name, `%f` to the project path within the
+filesystem (i.e. "$projectroot/$project"), `%h` to the current hash
+(\'h' gitweb parameter) and `%b` to the current hash base
+(\'hb' gitweb parameter); `%%` expands to \'%'.
++
+For example, at the time this page was written, the http://repo.or.cz[]
+git hosting site set it to the following to enable graphical log
+(using the third party tool *git-browser*):
++
+----------------------------------------------------------------------
+$feature{'actions'}{'default'} =
+       [ ('graphiclog', '/git-browser/by-commit.html?r=%n', 'summary')];
+----------------------------------------------------------------------
++
+This adds a link titled "graphiclog" after the "summary" link, leading to
+`git-browser` script, passing `r=<project>` as a query parameter.
++
+Project specific override is not supported.
+
+timed::
+       Enable displaying how much time and how many git commands it took to
+       generate and display each page in the page footer (at the bottom of
+       page).  For example the footer might contain: "This page took 6.53325
+       seconds and 13 git commands to generate."  Disabled by default.
++
+Project specific override is not supported.
+
+javascript-timezone::
+       Enable and configure the ability to change a common timezone for dates
+       in gitweb output via JavaScript.  Dates in gitweb output include
+       authordate and committerdate in "commit", "commitdiff" and "log"
+       views, and taggerdate in "tag" view.  Enabled by default.
++
+The value is a list of three values: a default timezone (for if the client
+hasn't selected some other timezone and saved it in a cookie), a name of cookie
+where to store selected timezone, and a CSS class used to mark up
+dates for manipulation.  If you want to turn this feature off, set "default"
+to empty list: `[]`.
++
+Typical gitweb config files will only change starting (default) timezone,
+and leave other elements at their default values:
++
+---------------------------------------------------------------------------
+$feature{'javascript-timezone'}{'default'}[0] = "utc";
+---------------------------------------------------------------------------
++
+The example configuration presented here is guaranteed to be backwards
+and forward compatible.
++
+Timezone values can be "local" (for local timezone that browser uses), "utc"
+(what gitweb uses when JavaScript or this feature is disabled), or numerical
+timezones in the form of "+/-HHMM", such as "+0200".
++
+Project specific override is not supported.
+
+
+EXAMPLES
+--------
+
+To enable blame, pickaxe search, and snapshot support (allowing "tar.gz" and
+"zip" snapshots), while allowing individual projects to turn them off, put
+the following in your GITWEB_CONFIG file:
+
+       $feature{'blame'}{'default'} = [1];
+       $feature{'blame'}{'override'} = 1;
+
+       $feature{'pickaxe'}{'default'} = [1];
+       $feature{'pickaxe'}{'override'} = 1;
+
+       $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*(1) to run at level 6 by
+adding the following lines to your gitweb configuration file:
+
+       $known_snapshot_formats{'zip'}{'disabled'} = 1;
+       $known_snapshot_formats{'tgz'}{'compressor'} = ['gzip','-6'];
+
+ENVIRONMENT
+-----------
+The location of per-instance and system-wide configuration files can be
+overridden using the following environment variables:
+
+GITWEB_CONFIG::
+       Sets location of per-instance configuration file.
+GITWEB_CONFIG_SYSTEM::
+       Sets location of fallback system-wide configuration file.
+       This file is read only if per-instance one does not exist.
+GITWEB_CONFIG_COMMON::
+       Sets location of common system-wide configuration file.
+
+
+FILES
+-----
+gitweb_config.perl::
+       This is default name of per-instance configuration file.  The
+       format of this file is described above.
+/etc/gitweb.conf::
+       This is default name of fallback system-wide configuration
+       file.  This file is used only if per-instance configuration
+       variable is not found.
+/etc/gitweb-common.conf::
+       This is default name of common system-wide configuration
+       file.
+
+
+SEE ALSO
+--------
+linkgit:gitweb[1], linkgit:git-instaweb[1]
+
+'gitweb/README', 'gitweb/INSTALL'
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitweb.txt b/Documentation/gitweb.txt
new file mode 100644 (file)
index 0000000..605a085
--- /dev/null
@@ -0,0 +1,704 @@
+gitweb(1)
+=========
+
+NAME
+----
+gitweb - Git web interface (web frontend to Git repositories)
+
+SYNOPSIS
+--------
+To get started with gitweb, run linkgit:git-instaweb[1] from a git repository.
+This would configure and start your web server, and run web browser pointing to
+gitweb.
+
+
+DESCRIPTION
+-----------
+Gitweb provides a web interface to git repositories.  It's features include:
+
+* Viewing multiple Git repositories with common root.
+* Browsing every revision of the repository.
+* Viewing the contents of files in the repository at any revision.
+* Viewing the revision log of branches, history of files and directories,
+  see what was changed when, by who.
+* Viewing the blame/annotation details of any file (if enabled).
+* Generating RSS and Atom feeds of commits, for any branch.
+  The feeds are auto-discoverable in modern web browsers.
+* Viewing everything that was changed in a revision, and step through
+  revisions one at a time, viewing the history of the repository.
+* Finding commits which commit messages matches given search term.
+
+See http://git.kernel.org/?p=git/git.git;a=tree;f=gitweb[] or
+http://repo.or.cz/w/git.git/tree/HEAD:/gitweb/[] for gitweb source code,
+browsed using gitweb itself.
+
+
+CONFIGURATION
+-------------
+Various aspects of gitweb's behavior can be controlled through the configuration
+file 'gitweb_config.perl' or '/etc/gitweb.conf'.  See the linkgit:gitweb.conf[5]
+for details.
+
+Repositories
+~~~~~~~~~~~~
+Gitweb can show information from one or more Git repositories.  These
+repositories have to be all on local filesystem, and have to share common
+repository root, i.e. be all under a single parent repository (but see also
+"Advanced web server setup" section, "Webserver configuration with multiple
+projects' root" subsection).
+
+-----------------------------------------------------------------------
+our $projectroot = '/path/to/parent/directory';
+-----------------------------------------------------------------------
+
+The default value for `$projectroot` is '/pub/git'.  You can change it during
+building gitweb via `GITWEB_PROJECTROOT` build configuration variable.
+
+By default all git repositories under `$projectroot` are visible and available
+to gitweb.  The list of projects is generated by default by scanning the
+`$projectroot` directory for git repositories (for object databases to be
+more exact; gitweb is not interested in a working area, and is best suited
+to showing "bare" repositories).
+
+The name of repository in gitweb is path to it's `$GIT_DIR` (it's object
+database) relative to `$projectroot`.  Therefore the repository $repo can be
+found at "$projectroot/$repo".
+
+
+Projects list file format
+~~~~~~~~~~~~~~~~~~~~~~~~~
+Instead of having gitweb find repositories by scanning filesystem
+starting from $projectroot, you can provide a pre-generated list of
+visible projects by setting `$projects_list` to point to a plain text
+file with a list of projects (with some additional info).
+
+This file uses the following format:
+
+* One record (for project / repository) per line; does not support line
+continuation (newline escaping).
+
+* Leading and trailing whitespace are ignored.
+
+* Whitespace separated fields; any run of whitespace can be used as field
+separator (rules for Perl's "`split(" ", $line)`").
+
+* Fields use modified URI encoding, defined in RFC 3986, section 2.1
+(Percent-Encoding), or rather "Query string encoding" (see
+link:http://en.wikipedia.org/wiki/Query_string#URL_encoding[]), the difference
+being that SP (" ") can be encoded as "{plus}" (and therefore "{plus}" has to be
+also percent-encoded).
++
+Reserved characters are: "%" (used for encoding), "{plus}" (can be used to
+encode SPACE), all whitespace characters as defined in Perl, including SP,
+TAB and LF, (used to separate fields in a record).
+
+* Currently recognized fields are:
+<repository path>::
+       path to repository GIT_DIR, relative to `$projectroot`
+<repository owner>::
+       displayed as repository owner, preferably full name, or email,
+       or both
+
+You can generate the projects list index file using the project_index action
+(the 'TXT' link on projects list page) directly from gitweb; see also
+"Generating projects list using gitweb" section below.
+
+Example contents:
+-----------------------------------------------------------------------
+foo.git       Joe+R+Hacker+<joe@example.com>
+foo/bar.git   O+W+Ner+<owner@example.org>
+-----------------------------------------------------------------------
+
+
+By default this file controls only which projects are *visible* on projects
+list page (note that entries that do not point to correctly recognized git
+repositories won't be displayed by gitweb).  Even if a project is not
+visible on projects list page, you can view it nevertheless by hand-crafting
+a gitweb URL.  By setting `$strict_export` configuration variable (see
+linkgit:gitweb.conf[5]) to true value you can allow viewing only of
+repositories also shown on the overview page (i.e. only projects explicitly
+listed in projects list file will be accessible).
+
+
+Generating projects list using gitweb
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We assume that GITWEB_CONFIG has its default Makefile value, namely
+'gitweb_config.perl'. Put the following in 'gitweb_make_index.perl' file:
+----------------------------------------------------------------------------
+read_config_file("gitweb_config.perl");
+$projects_list = $projectroot;
+----------------------------------------------------------------------------
+
+Then create the following script to get list of project in the format
+suitable for GITWEB_LIST build configuration variable (or
+`$projects_list` variable in gitweb config):
+
+----------------------------------------------------------------------------
+#!/bin/sh
+
+export GITWEB_CONFIG="gitweb_make_index.perl"
+export GATEWAY_INTERFACE="CGI/1.1"
+export HTTP_ACCEPT="*/*"
+export REQUEST_METHOD="GET"
+export QUERY_STRING="a=project_index"
+
+perl -- /var/www/cgi-bin/gitweb.cgi
+----------------------------------------------------------------------------
+
+Run this script and save its output to a file.  This file could then be used
+as projects list file, which means that you can set `$projects_list` to its
+filename.
+
+
+Controlling access to git repositories
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+By default all git repositories under `$projectroot` are visible and
+available to gitweb.  You can however configure how gitweb controls access
+to repositories.
+
+* As described in "Projects list file format" section, you can control which
+projects are *visible* by selectively including repositories in projects
+list file, and setting `$projects_list` gitweb configuration variable to
+point to it.  With `$strict_export` set, projects list file can be used to
+control which repositories are *available* as well.
+
+* You can configure gitweb to only list and allow viewing of the explicitly
+exported repositories, via `$export_ok` variable in gitweb config file; see
+linkgit:gitweb.conf[5] manpage.  If it evaluates to true, gitweb shows
+repositories only if this file named by `$export_ok` exists in its object
+database (if directory has the magic file named `$export_ok`).
++
+For example linkgit:git-daemon[1] by default (unless `--export-all` option
+is used) allows pulling only for those repositories that have
+'git-daemon-export-ok' file.  Adding
++
+--------------------------------------------------------------------------
+our $export_ok = "git-daemon-export-ok";
+--------------------------------------------------------------------------
++
+makes gitweb show and allow access only to those repositories that can be
+fetched from via `git://` protocol.
+
+* Finally, it is possible to specify an arbitrary perl subroutine that will
+be called for each repository to determine if it can be exported.  The
+subroutine receives an absolute path to the project (repository) as its only
+parameter (i.e. "$projectroot/$project").
++
+For example, if you use mod_perl to run the script, and have dumb
+HTTP protocol authentication configured for your repositories, you
+can use the following hook to allow access only if the user is
+authorized to read the files:
++
+--------------------------------------------------------------------------
+$export_auth_hook = sub {
+       use Apache2::SubRequest ();
+       use Apache2::Const -compile => qw(HTTP_OK);
+       my $path = "$_[0]/HEAD";
+       my $r    = Apache2::RequestUtil->request;
+       my $sub  = $r->lookup_file($path);
+       return $sub->filename eq $path
+           && $sub->status == Apache2::Const::HTTP_OK;
+};
+--------------------------------------------------------------------------
+
+
+Per-repository gitweb configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+You can configure individual repositories shown in gitweb by creating file
+in the 'GIT_DIR' of git repository, or by setting some repo configuration
+variable (in 'GIT_DIR/config', see linkgit:git-config[1]).
+
+You can use the following files in repository:
+
+README.html::
+       A html file (HTML fragment) which is included on the gitweb project
+       "summary" page inside `<div>` block element. You can use it for longer
+       description of a project, to provide links (for example to project's
+       homepage), etc. This is recognized only if XSS prevention is off
+       (`$prevent_xss` is false, see linkgit:gitweb.conf[5]); a way to include
+       a README safely when XSS prevention is on may be worked out in the
+       future.
+
+description (or `gitweb.description`)::
+       Short (shortened to `$projects_list_description_width` in the projects
+       list page, which is 25 characters by default; see
+       linkgit:gitweb.conf[5]) single line description of a project (of a
+       repository).  Plain text file; HTML will be escaped.  By default set to
++
+-------------------------------------------------------------------------------
+Unnamed repository; edit this file to name it for gitweb.
+-------------------------------------------------------------------------------
++
+from the template during repository creation, usually installed in
+'/usr/share/git-core/templates/'.  You can use the `gitweb.description` repo
+configuration variable, but the file takes precedence.
+
+category (or `gitweb.category`)::
+       Singe line category of a project, used to group projects if
+       `$projects_list_group_categories` is enabled.  By default (file and
+       configuration variable absent), uncategorized projects are put in the
+       `$project_list_default_category` category.  You can use the
+       `gitweb.category` repo configuration variable, but the file takes
+       precedence.
++
+The configuration variables `$projects_list_group_categories` and
+`$project_list_default_category` are described in linkgit:gitweb.conf[5]
+
+cloneurl (or multiple-valued `gitweb.url`)::
+       File with repository URL (used for clone and fetch), one per line.
+       Displayed in the project summary page. You can use multiple-valued
+       `gitweb.url` repository configuration variable for that, but the file
+       takes precedence.
++
+This is per-repository enhancement / version of global prefix-based
+`@git_base_url_list` gitweb configuration variable (see
+linkgit:gitweb.conf[5]).
+
+gitweb.owner::
+       You can use the `gitweb.owner` repository configuration variable to set
+       repository's owner.  It is displayed in the project list and summary
+       page.
++
+If it's not set, filesystem directory's owner is used (via GECOS field,
+i.e. real name field from *getpwuid*(3)) if `$projects_list` is unset
+(gitweb scans `$projectroot` for repositories); if `$projects_list`
+points to file with list of repositories, then project owner defaults to
+value from this file for given repository.
+
+various `gitweb.*` config variables (in config)::
+       Read description of `%feature` hash for detailed list, and descriptions.
+       See also "Configuring gitweb features" section in linkgit:gitweb.conf[5]
+
+
+ACTIONS, AND URLS
+-----------------
+Gitweb can use path_info (component) based URLs, or it can pass all necessary
+information via query parameters.  The typical gitweb URLs are broken down in to
+five components:
+
+-----------------------------------------------------------------------
+.../gitweb.cgi/<repo>/<action>/<revision>:/<path>?<arguments>
+-----------------------------------------------------------------------
+
+repo::
+       The repository the action will be performed on.
++
+All actions except for those that list all available projects,
+in whatever form, require this parameter.
+
+action::
+       The action that will be run.  Defaults to 'projects_list' if repo
+       is not set, and to 'summary' otherwise.
+
+revision::
+       Revision shown.  Defaults to HEAD.
+
+path::
+       The path within the <repository> that the action is performed on,
+       for those actions that require it.
+
+arguments::
+       Any arguments that control the behaviour of the action.
+
+Some actions require or allow to specify two revisions, and sometimes even two
+pathnames.  In most general form such path_info (component) based gitweb URL
+looks like this:
+
+-----------------------------------------------------------------------
+.../gitweb.cgi/<repo>/<action>/<revision_from>:/<path_from>..<revision_to>:/<path_to>?<arguments>
+-----------------------------------------------------------------------
+
+
+Each action is implemented as a subroutine, and must be present in %actions
+hash.  Some actions are disabled by default, and must be turned on via feature
+mechanism.  For example to enable 'blame' view add the following to gitweb
+configuration file:
+
+-----------------------------------------------------------------------
+$feature{'blame'}{'default'} = [1];
+-----------------------------------------------------------------------
+
+
+Actions:
+~~~~~~~~
+The standard actions are:
+
+project_list::
+       Lists the available Git repositories.  This is the default command if no
+       repository is specified in the URL.
+
+summary::
+       Displays summary about given repository.  This is the default command if
+       no action is specified in URL, and only repository is specified.
+
+heads::
+remotes::
+       Lists all local or all remote-tracking branches in given repository.
++
+The latter is not available by default, unless configured.
+
+tags::
+       List all tags (lightweight and annotated) in given repository.
+
+blob::
+tree::
+       Shows the files and directories in a given repository path, at given
+       revision.  This is default command if no action is specified in the URL,
+       and path is given.
+
+blob_plain::
+       Returns the raw data for the file in given repository, at given path and
+       revision.  Links to this action are marked 'raw'.
+
+blobdiff::
+       Shows the difference between two revisions of the same file.
+
+blame::
+blame_incremental::
+       Shows the blame (also called annotation) information for a file. On a
+       per line basis it shows the revision in which that line was last changed
+       and the user that committed the change.  The incremental version (which
+       if configured is used automatically when JavaScript is enabled) uses
+       Ajax to incrementally add blame info to the contents of given file.
++
+This action is disabled by default for performance reasons.
+
+commit::
+commitdiff::
+       Shows information about a specific commit in a repository.  The 'commit'
+       view shows information about commit in more detail, the 'commitdiff'
+       action shows changeset for given commit.
+
+patch::
+       Returns the commit in plain text mail format, suitable for applying with
+       linkgit:git-am[1].
+
+tag::
+       Display specific annotated tag (tag object).
+
+log::
+shortlog::
+       Shows log information (commit message or just commit subject) for a
+       given branch (starting from given revision).
++
+The 'shortlog' view is more compact; it shows one commit per line.
+
+history::
+       Shows history of the file or directory in a given repository path,
+       starting from given revision (defaults to HEAD, i.e. default branch).
++
+This view is similar to 'shortlog' view.
+
+rss::
+atom::
+       Generates an RSS (or Atom) feed of changes to repository.
+
+
+WEBSERVER CONFIGURATION
+-----------------------
+This section explains how to configure some common webservers to run gitweb. In
+all cases, `/path/to/gitweb` in the examples is the directory you ran installed
+gitweb in, and contains `gitweb_config.perl`.
+
+If you've configured a web server that isn't listed here for gitweb, please send
+in the instructions so they can be included in a future release.
+
+Apache as CGI
+~~~~~~~~~~~~~
+Apache must be configured to support CGI scripts in the directory in
+which gitweb is installed.  Let's assume that it is '/var/www/cgi-bin'
+directory.
+
+-----------------------------------------------------------------------
+ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"
+
+<Directory "/var/www/cgi-bin">
+    Options Indexes FollowSymlinks ExecCGI
+    AllowOverride None
+    Order allow,deny
+    Allow from all
+</Directory>
+-----------------------------------------------------------------------
+
+With that configuration the full path to browse repositories would be:
+
+  http://server/cgi-bin/gitweb.cgi
+
+Apache with mod_perl, via ModPerl::Registry
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+You can use mod_perl with gitweb.  You must install Apache::Registry
+(for mod_perl 1.x) or ModPerl::Registry (for mod_perl 2.x) to enable
+this support.
+
+Assuming that gitweb is installed to '/var/www/perl', the following
+Apache configuration (for mod_perl 2.x) is suitable.
+
+-----------------------------------------------------------------------
+Alias /perl "/var/www/perl"
+
+<Directory "/var/www/perl">
+    SetHandler perl-script
+    PerlResponseHandler ModPerl::Registry
+    PerlOptions +ParseHeaders
+    Options Indexes FollowSymlinks +ExecCGI
+    AllowOverride None
+    Order allow,deny
+    Allow from all
+</Directory>
+-----------------------------------------------------------------------
+
+With that configuration the full path to browse repositories would be:
+
+  http://server/perl/gitweb.cgi
+
+Apache with FastCGI
+~~~~~~~~~~~~~~~~~~~
+Gitweb works with Apache and FastCGI.  First you need to rename, copy
+or symlink gitweb.cgi to gitweb.fcgi.  Let's assume that gitweb is
+installed in '/usr/share/gitweb' directory.  The following Apache
+configuration is suitable (UNTESTED!)
+
+-----------------------------------------------------------------------
+FastCgiServer /usr/share/gitweb/gitweb.cgi
+ScriptAlias /gitweb /usr/share/gitweb/gitweb.cgi
+
+Alias /gitweb/static /usr/share/gitweb/static
+<Directory /usr/share/gitweb/static>
+    SetHandler default-handler
+</Directory>
+-----------------------------------------------------------------------
+
+With that configuration the full path to browse repositories would be:
+
+  http://server/gitweb
+
+
+ADVANCED WEB SERVER SETUP
+-------------------------
+All of those examples use request rewriting, and need `mod_rewrite`
+(or equivalent; examples below are written for Apache).
+
+Single URL for gitweb and for fetching
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+If you want to have one URL for both gitweb and your `http://`
+repositories, you can configure Apache like this:
+
+-----------------------------------------------------------------------
+<VirtualHost *:80>
+    ServerName    git.example.org
+    DocumentRoot  /pub/git
+    SetEnv        GITWEB_CONFIG   /etc/gitweb.conf
+
+    # turning on mod rewrite
+    RewriteEngine on
+
+    # make the front page an internal rewrite to the gitweb script
+    RewriteRule ^/$  /cgi-bin/gitweb.cgi
+
+    # make access for "dumb clients" work
+    RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ \
+               /cgi-bin/gitweb.cgi%{REQUEST_URI}  [L,PT]
+</VirtualHost>
+-----------------------------------------------------------------------
+
+The above configuration expects your public repositories to live under
+'/pub/git' and will serve them as `http://git.domain.org/dir-under-pub-git`,
+both as cloneable GIT URL and as browseable gitweb interface.  If you then
+start your linkgit:git-daemon[1] with `--base-path=/pub/git --export-all`
+then you can even use the `git://` URL with exactly the same path.
+
+Setting the environment variable `GITWEB_CONFIG` will tell gitweb to use the
+named file (i.e. in this example '/etc/gitweb.conf') as a configuration for
+gitweb.  You don't really need it in above example; it is required only if
+your configuration file is in different place than built-in (during
+compiling gitweb) 'gitweb_config.perl' or '/etc/gitweb.conf'.  See
+linkgit:gitweb.conf[5] for details, especially information about precedence
+rules.
+
+If you use the rewrite rules from the example you *might* also need
+something like the following in your gitweb configuration file
+('/etc/gitweb.conf' following example):
+----------------------------------------------------------------------------
+@stylesheets = ("/some/absolute/path/gitweb.css");
+$my_uri    = "/";
+$home_link = "/";
+$per_request_config = 1;
+----------------------------------------------------------------------------
+Nowadays though gitweb should create HTML base tag when needed (to set base
+URI for relative links), so it should work automatically.
+
+
+Webserver configuration with multiple projects' root
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+If you want to use gitweb with several project roots you can edit your
+Apache virtual host and gitweb configuration files in the following way.
+
+The virtual host configuration (in Apache configuration file) should look
+like this:
+--------------------------------------------------------------------------
+<VirtualHost *:80>
+    ServerName    git.example.org
+    DocumentRoot  /pub/git
+    SetEnv        GITWEB_CONFIG  /etc/gitweb.conf
+
+    # turning on mod rewrite
+    RewriteEngine on
+
+    # make the front page an internal rewrite to the gitweb script
+    RewriteRule ^/$  /cgi-bin/gitweb.cgi  [QSA,L,PT]
+
+    # look for a public_git folder in unix users' home
+    # http://git.example.org/~<user>/
+    RewriteRule ^/\~([^\/]+)(/|/gitweb.cgi)?$  /cgi-bin/gitweb.cgi \
+               [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
+
+    # http://git.example.org/+<user>/
+    #RewriteRule ^/\+([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \
+                [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
+
+    # http://git.example.org/user/<user>/
+    #RewriteRule ^/user/([^\/]+)/(gitweb.cgi)?$        /cgi-bin/gitweb.cgi \
+                [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
+
+    # defined list of project roots
+    RewriteRule ^/scm(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \
+               [QSA,E=GITWEB_PROJECTROOT:/pub/scm/,L,PT]
+    RewriteRule ^/var(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \
+               [QSA,E=GITWEB_PROJECTROOT:/var/git/,L,PT]
+
+    # make access for "dumb clients" work
+    RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ \
+               /cgi-bin/gitweb.cgi%{REQUEST_URI}  [L,PT]
+</VirtualHost>
+--------------------------------------------------------------------------
+
+Here actual project root is passed to gitweb via `GITWEB_PROJECT_ROOT`
+environment variable from a web server, so you need to put the following
+line in gitweb configuration file ('/etc/gitweb.conf' in above example):
+--------------------------------------------------------------------------
+$projectroot = $ENV{'GITWEB_PROJECTROOT'} || "/pub/git";
+--------------------------------------------------------------------------
+*Note* that this requires to be set for each request, so either
+`$per_request_config` must be false, or the above must be put in code
+referenced by `$per_request_config`;
+
+These configurations enable two things. First, each unix user (`<user>`) of
+the server will be able to browse through gitweb git repositories found in
+'~/public_git/' with the following url:
+
+  http://git.example.org/~<user>/
+
+If you do not want this feature on your server just remove the second
+rewrite rule.
+
+If you already use `mod_userdir` in your virtual host or you don't want to
+use the \'~' as first character, just comment or remove the second rewrite
+rule, and uncomment one of the following according to what you want.
+
+Second, repositories found in '/pub/scm/' and '/var/git/' will be accessible
+through `http://git.example.org/scm/` and `http://git.example.org/var/`.
+You can add as many project roots as you want by adding rewrite rules like
+the third and the fourth.
+
+
+PATH_INFO usage
+~~~~~~~~~~~~~~~
+If you enable PATH_INFO usage in gitweb by putting
+----------------------------------------------------------------------------
+$feature{'pathinfo'}{'default'} = [1];
+----------------------------------------------------------------------------
+in your gitweb configuration file, it is possible to set up your server so
+that it consumes and produces URLs in the form
+
+  http://git.example.com/project.git/shortlog/sometag
+
+i.e. without 'gitweb.cgi' part, by using a configuration such as the
+following.  This configuration assumes that '/var/www/gitweb' is the
+DocumentRoot of your webserver, contains the gitweb.cgi script and
+complementary static files (stylesheet, favicon, JavaScript):
+
+----------------------------------------------------------------------------
+<VirtualHost *:80>
+       ServerAlias git.example.com
+
+       DocumentRoot /var/www/gitweb
+
+       <Directory /var/www/gitweb>
+               Options ExecCGI
+               AddHandler cgi-script cgi
+
+               DirectoryIndex gitweb.cgi
+
+               RewriteEngine On
+               RewriteCond %{REQUEST_FILENAME} !-f
+               RewriteCond %{REQUEST_FILENAME} !-d
+               RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
+       </Directory>
+</VirtualHost>
+----------------------------------------------------------------------------
+The rewrite rule guarantees that existing static files will be properly
+served, whereas any other URL will be passed to gitweb as PATH_INFO
+parameter.
+
+*Notice* that in this case you don't need special settings for
+`@stylesheets`, `$my_uri` and `$home_link`, but you lose "dumb client"
+access to your project .git dirs (described in "Single URL for gitweb and
+for fetching" section).  A possible workaround for the latter is the
+following: in your project root dir (e.g. '/pub/git') have the projects
+named *without* a .git extension (e.g. '/pub/git/project' instead of
+'/pub/git/project.git') and configure Apache as follows:
+----------------------------------------------------------------------------
+<VirtualHost *:80>
+       ServerAlias git.example.com
+
+       DocumentRoot /var/www/gitweb
+
+       AliasMatch ^(/.*?)(\.git)(/.*)?$ /pub/git$1$3
+       <Directory /var/www/gitweb>
+               Options ExecCGI
+               AddHandler cgi-script cgi
+
+               DirectoryIndex gitweb.cgi
+
+               RewriteEngine On
+               RewriteCond %{REQUEST_FILENAME} !-f
+               RewriteCond %{REQUEST_FILENAME} !-d
+               RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
+       </Directory>
+</VirtualHost>
+----------------------------------------------------------------------------
+
+The additional AliasMatch makes it so that
+
+  http://git.example.com/project.git
+
+will give raw access to the project's git dir (so that the project can be
+cloned), while
+
+  http://git.example.com/project
+
+will provide human-friendly gitweb access.
+
+This solution is not 100% bulletproof, in the sense that if some project has
+a named ref (branch, tag) starting with 'git/', then paths such as
+
+  http://git.example.com/project/command/abranch..git/abranch
+
+will fail with a 404 error.
+
+
+BUGS
+----
+Please report any bugs or feature requests to git@vger.kernel.org,
+putting "gitweb" in the subject of email.
+
+SEE ALSO
+--------
+linkgit:gitweb.conf[5], linkgit:git-instaweb[1]
+
+'gitweb/README', 'gitweb/INSTALL'
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/howto/using-signed-tag-in-pull-request.txt b/Documentation/howto/using-signed-tag-in-pull-request.txt
new file mode 100644 (file)
index 0000000..98c0033
--- /dev/null
@@ -0,0 +1,217 @@
+From: Junio C Hamano <gitster@pobox.com>
+Date: Tue, 17 Jan 2011 13:00:00 -0800
+Subject: Using signed tag in pull requests
+Abstract: Beginning v1.7.9, a contributor can push a signed tag to her
+ publishing repository and ask her integrator to pull it. This assures the
+ integrator that the pulled history is authentic and allows others to
+ later validate it.
+Content-type: text/asciidoc
+
+Using signed tag in pull requests
+=================================
+
+A typical distributed workflow using Git is for a contributor to fork a
+project, build on it, publish the result to her public repository, and ask
+the "upstream" person (often the owner of the project where she forked
+from) to pull from her public repository. Requesting such a "pull" is made
+easy by the `git request-pull` command.
+
+Earlier, a typical pull request may have started like this:
+
+------------
+ The following changes since commit 406da78032179...:
+
+   Froboz 3.2 (2011-09-30 14:20:57 -0700)
+
+ are available in the git repository at:
+
+   example.com:/git/froboz.git for-xyzzy
+------------
+
+followed by a shortlog of the changes and a diffstat.
+
+The request was for a branch name (e.g. `for-xyzzy`) in the public
+repository of the contributor, and even though it stated where the
+contributor forked her work from, the message did not say anything about
+the commit to expect at the tip of the for-xyzzy branch. If the site that
+hosts the public repository of the contributor cannot be fully trusted, it
+was unnecessarily hard to make sure what was pulled by the integrator was
+genuinely what the contributor had produced for the project. Also there
+was no easy way for third-party auditors to later verify the resulting
+history.
+
+Starting from Git release v1.7.9, a contributor can add a signed tag to
+the commit at the tip of the history and ask the integrator to pull that
+signed tag. When the integrator runs `git pull`, the signed tag is
+automatically verified to assure that the history is not tampered with.
+In addition, the resulting merge commit records the content of the signed
+tag, so that other people can verify that the branch merged by the
+integrator was signed by the contributor, without fetching the signed tag
+used to validate the pull request separately and keeping it in the refs
+namespace.
+
+This document describes the workflow between the contributor and the
+integrator, using Git v1.7.9 or later.
+
+
+A contributor or a lieutenant
+-----------------------------
+
+After preparing her work to be pulled, the contributor uses `git tag -s`
+to create a signed tag:
+
+------------
+ $ git checkout work
+ $ ... "git pull" from sublieutenants, "git commit" your own work ...
+ $ git tag -s -m "Completed frotz feature" frotz-for-xyzzy work
+------------
+
+Note that this example uses the `-m` option to create a signed tag with
+just a one-liner message, but this is for illustration purposes only. It
+is advisable to compose a well-written explanation of what the topic does
+to justify why it is worthwhile for the integrator to pull it, as this
+message will eventually become part of the final history after the
+integrator responds to the pull request (as we will see later).
+
+Then she pushes the tag out to her public repository:
+
+------------
+ $ git push example.com:/git/froboz.git/ +frotz-for-xyzzy
+------------
+
+There is no need to push the `work` branch or anything else.
+
+Note that the above command line used a plus sign at the beginning of
+`+frotz-for-xyzzy` to allow forcing the update of a tag, as the same
+contributor may want to reuse a signed tag with the same name after the
+previous pull request has already been responded to.
+
+The contributor then prepares a message to request a "pull":
+
+------------
+ $ git request-pull v3.2 example.com:/git/froboz.git/ frotz-for-xyzzy >msg.txt
+------------
+
+The arguments are:
+
+. the version of the integrator's commit the contributor based her work on;
+. the URL of the repository, to which the contributor has pushed what she
+  wants to get pulled; and
+. the name of the tag the contributor wants to get pulled (earlier, she could
+  write only a branch name here).
+
+The resulting msg.txt file begins like so:
+
+------------
+ The following changes since commit 406da78032179...:
+
+   Froboz 3.2 (2011-09-30 14:20:57 -0700)
+
+ are available in the git repository at:
+
+   example.com:/git/froboz.git tags/frotz-for-xyzzy
+
+ for you to fetch changes up to 703f05ad5835c...:
+
+   Add tests and documentation for frotz (2011-12-02 10:02:52 -0800)
+
+ -----------------------------------------------
+ Completed frotz feature
+ -----------------------------------------------
+------------
+
+followed by a shortlog of the changes and a diffstat.  Comparing this with
+the earlier illustration of the output from the traditional `git request-pull`
+command, the reader should notice that:
+
+. The tip commit to expect is shown to the integrator; and
+. The signed tag message is shown prominently between the dashed lines
+  before the shortlog.
+
+The latter is why the contributor would want to justify why pulling her
+work is worthwhile when creating the signed tag.  The contributor then
+opens her favorite MUA, reads msg.txt, edits and sends it to her upstream
+integrator.
+
+
+Integrator
+----------
+
+After receiving such a pull request message, the integrator fetches and
+integrates the tag named in the request, with:
+
+------------
+ $ git pull example.com:/git/froboz.git/ tags/frotz-for-xyzzy
+------------
+
+This operation will always open an editor to allow the integrator to fine
+tune the commit log message when merging a signed tag.  Also, pulling a
+signed tag will always create a merge commit even when the integrator does
+not have any new commit since the contributor's work forked (i.e. 'fast
+forward'), so that the integrator can properly explain what the merge is
+about and why it was made.
+
+In the editor, the integrator will see something like this:
+
+------------
+ Merge tag 'frotz-for-xyzzy' of example.com:/git/froboz.git/
+
+ Completed frotz feature
+ # gpg: Signature made Fri 02 Dec 2011 10:03:01 AM PST using RSA key ID 96AFE6CB
+ # gpg: Good signature from "Con Tributor <nitfol@example.com>"
+------------
+
+Notice that the message recorded in the signed tag "Completed frotz
+feature" appears here, and again that is why it is important for the
+contributor to explain her work well when creating the signed tag.
+
+As usual, the lines commented with `#` are stripped out. The resulting
+commit records the signed tag used for this validation in a hidden field
+so that it can later be used by others to audit the history. There is no
+need for the integrator to keep a separate copy of the tag in his
+repository (i.e. `git tag -l` won't list the `frotz-for-xyzzy` tag in the
+above example), and there is no need to publish the tag to his public
+repository, either.
+
+After the integrator responds to the pull request and her work becomes
+part of the permanent history, the contributor can remove the tag from
+her public repository, if she chooses, in order to keep the tag namespace
+of her public repository clean, with:
+
+------------
+ $ git push example.com:/git/froboz.git :frotz-for-xyzzy
+------------
+
+
+Auditors
+--------
+
+The `--show-signature` option can be given to `git log` or `git show` and
+shows the verification status of the embedded signed tag in merge commits
+created when the integrator responded to a pull request of a signed tag.
+
+A typical output from `git show --show-signature` may look like this:
+
+------------
+ $ git show --show-signature
+ commit 02306ef6a3498a39118aef9df7975bdb50091585
+ merged tag 'frotz-for-xyzzy'
+ gpg: Signature made Fri 06 Jan 2012 12:41:49 PM PST using RSA key ID 96AFE6CB
+ gpg: Good signature from "Con Tributor <nitfol@example.com>"
+ Merge: 406da78 703f05a
+ Author: Inte Grator <xyzzy@example.com>
+ Date:   Tue Jan 17 13:49:41 2012 -0800
+
+     Merge tag 'frotz-for-xyzzy' of example.com:/git/froboz.git/
+
+     Completed frotz feature
+
+     * tag 'frotz-for-xyzzy' (100 commits)
+       Add tests and documentation for frotz
+       ...
+------------
+
+There is no need for the auditor to explicitly fetch the contributor's
+signature, or to even be aware of what tag(s) the contributor and integrator
+used to communicate the signature.  All the required information is recorded
+as part of the merge commit.
index b613d4ed083d080797f7da90fc92212f98611d07..2bd92c474178559cf03f63ffc19098a76a9cf4e2 100644 (file)
@@ -7,14 +7,24 @@ 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.
 
+--edit::
+-e::
+       Invoke editor before committing successful merge to further
+       edit the default merge message.
+
 --ff::
+       When the merge resolves as a fast-forward, only update the branch
+       pointer, without creating a merge commit.  This is the default
+       behavior.
+
 --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.
+       Create a merge commit even when the merge resolves as a
+       fast-forward.
+
+--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.
 
 --log[=<n>]::
 --no-log::
@@ -49,11 +59,6 @@ merge.
 With --no-squash perform the merge and commit the result. This
 option can be used to override --squash.
 
---ff-only::
-       Refuse to merge and exit with a non-zero status unless the
-       current `HEAD` is already up-to-date or the merge can be
-       resolved as a fast-forward.
-
 -s <strategy>::
 --strategy=<strategy>::
        Use the given merge strategy; can be supplied more than
index 561cc9f7d7ef1a2502d608e59e2eae345bffd685..880b6f2e6f946ab6fc0ff4850759908364d50854 100644 (file)
@@ -132,6 +132,10 @@ The placeholders are:
 - '%N': commit notes
 - '%gD': reflog selector, e.g., `refs/stash@\{1\}`
 - '%gd': shortened reflog selector, e.g., `stash@\{1\}`
+- '%gn': reflog identity name
+- '%gN': reflog identity name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
+- '%ge': reflog identity email
+- '%gE': reflog identity email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
 - '%gs': reflog subject
 - '%Cred': switch color to red
 - '%Cgreen': switch color to green
index 39e62072691d408519ff377cb6e91e8d95175ec4..6a4b6355ba91d60ebf2279903a25032256f2063b 100644 (file)
@@ -117,27 +117,27 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit).
        Pretend as if all the refs in `refs/heads` are listed
        on the command line as '<commit>'. If '<pattern>' is given, limit
        branches to ones matching given shell glob. If pattern lacks '?',
-       '*', or '[', '/*' at the end is implied.
+       '{asterisk}', or '[', '/{asterisk}' at the end is implied.
 
 --tags[=<pattern>]::
 
        Pretend as if all the refs in `refs/tags` are listed
        on the command line as '<commit>'. If '<pattern>' is given, limit
-       tags to ones matching given shell glob. If pattern lacks '?', '*',
-       or '[', '/*' at the end is implied.
+       tags to ones matching given shell glob. If pattern lacks '?', '{asterisk}',
+       or '[', '/{asterisk}' at the end is implied.
 
 --remotes[=<pattern>]::
 
        Pretend as if all the refs in `refs/remotes` are listed
        on the command line as '<commit>'. If '<pattern>' is given, limit
        remote-tracking branches to ones matching given shell glob.
-       If pattern lacks '?', '*', or '[', '/*' at the end is implied.
+       If pattern lacks '?', '{asterisk}', or '[', '/{asterisk}' at the end is implied.
 
 --glob=<glob-pattern>::
        Pretend as if all the refs matching shell glob '<glob-pattern>'
        are listed on the command line as '<commit>'. Leading 'refs/',
-       is automatically prepended if missing. If pattern lacks '?', '*',
-       or '[', '/*' at the end is implied.
+       is automatically prepended if missing. If pattern lacks '?', '{asterisk}',
+       or '[', '/{asterisk}' at the end is implied.
 
 --ignore-missing::
 
index b290b617d4a59ee2ae6b62f2eebd9e86f71c4802..1725661837ad666dc1a06b19162a43f10fe6e13a 100644 (file)
@@ -101,7 +101,7 @@ the '$GIT_DIR/refs' directory or from the '$GIT_DIR/packed-refs' file.
 
 '<rev>{tilde}<n>', e.g. 'master{tilde}3'::
   A suffix '{tilde}<n>' to a revision parameter means the commit
-  object that is the <n>th generation grand-parent of the named
+  object that is the <n>th generation ancestor of the named
   commit object, following only the first parents.  I.e. '<rev>{tilde}3' is
   equivalent to '<rev>{caret}{caret}{caret}' which is equivalent to
   '<rev>{caret}1{caret}1{caret}1'.  See below for an illustration of
diff --git a/Documentation/sequencer.txt b/Documentation/sequencer.txt
new file mode 100644 (file)
index 0000000..5747f44
--- /dev/null
@@ -0,0 +1,12 @@
+--continue::
+       Continue the operation in progress using the information in
+       '.git/sequencer'.  Can be used to continue after resolving
+       conflicts in a failed cherry-pick or revert.
+
+--quit::
+       Forget about the current operation in progress.  Can be used
+       to clear the sequencer state after a failed cherry-pick or
+       revert.
+
+--abort::
+       Cancel the operation and return to the pre-sequence state.
diff --git a/Documentation/technical/api-credentials.txt b/Documentation/technical/api-credentials.txt
new file mode 100644 (file)
index 0000000..21ca6a2
--- /dev/null
@@ -0,0 +1,245 @@
+credentials API
+===============
+
+The credentials API provides an abstracted way of gathering username and
+password credentials from the user (even though credentials in the wider
+world can take many forms, in this document the word "credential" always
+refers to a username and password pair).
+
+Data Structures
+---------------
+
+`struct credential`::
+
+       This struct represents a single username/password combination
+       along with any associated context. All string fields should be
+       heap-allocated (or NULL if they are not known or not applicable).
+       The meaning of the individual context fields is the same as
+       their counterparts in the helper protocol; see the section below
+       for a description of each field.
++
+The `helpers` member of the struct is a `string_list` of helpers.  Each
+string specifies an external helper which will be run, in order, to
+either acquire or store credentials. See the section on credential
+helpers below.
++
+This struct should always be initialized with `CREDENTIAL_INIT` or
+`credential_init`.
+
+
+Functions
+---------
+
+`credential_init`::
+
+       Initialize a credential structure, setting all fields to empty.
+
+`credential_clear`::
+
+       Free any resources associated with the credential structure,
+       returning it to a pristine initialized state.
+
+`credential_fill`::
+
+       Instruct the credential subsystem to fill the username and
+       password fields of the passed credential struct by first
+       consulting helpers, then asking the user. After this function
+       returns, the username and password fields of the credential are
+       guaranteed to be non-NULL. If an error occurs, the function will
+       die().
+
+`credential_reject`::
+
+       Inform the credential subsystem that the provided credentials
+       have been rejected. This will cause the credential subsystem to
+       notify any helpers of the rejection (which allows them, for
+       example, to purge the invalid credentials from storage).  It
+       will also free() the username and password fields of the
+       credential and set them to NULL (readying the credential for
+       another call to `credential_fill`). Any errors from helpers are
+       ignored.
+
+`credential_approve`::
+
+       Inform the credential subsystem that the provided credentials
+       were successfully used for authentication.  This will cause the
+       credential subsystem to notify any helpers of the approval, so
+       that they may store the result to be used again.  Any errors
+       from helpers are ignored.
+
+`credential_from_url`::
+
+       Parse a URL into broken-down credential fields.
+
+Example
+-------
+
+The example below shows how the functions of the credential API could be
+used to login to a fictitious "foo" service on a remote host:
+
+-----------------------------------------------------------------------
+int foo_login(struct foo_connection *f)
+{
+       int status;
+       /*
+        * Create a credential with some context; we don't yet know the
+        * username or password.
+        */
+
+       struct credential c = CREDENTIAL_INIT;
+       c.protocol = xstrdup("foo");
+       c.host = xstrdup(f->hostname);
+
+       /*
+        * Fill in the username and password fields by contacting
+        * helpers and/or asking the user. The function will die if it
+        * fails.
+        */
+       credential_fill(&c);
+
+       /*
+        * Otherwise, we have a username and password. Try to use it.
+        */
+       status = send_foo_login(f, c.username, c.password);
+       switch (status) {
+       case FOO_OK:
+               /* It worked. Store the credential for later use. */
+               credential_accept(&c);
+               break;
+       case FOO_BAD_LOGIN:
+               /* Erase the credential from storage so we don't try it
+                * again. */
+               credential_reject(&c);
+               break;
+       default:
+               /*
+                * Some other error occured. We don't know if the
+                * credential is good or bad, so report nothing to the
+                * credential subsystem.
+                */
+       }
+
+       /* Free any associated resources. */
+       credential_clear(&c);
+
+       return status;
+}
+-----------------------------------------------------------------------
+
+
+Credential Helpers
+------------------
+
+Credential helpers are programs executed by git to fetch or save
+credentials from and to long-term storage (where "long-term" is simply
+longer than a single git process; e.g., credentials may be stored
+in-memory for a few minutes, or indefinitely on disk).
+
+Each helper is specified by a single string. The string is transformed
+by git into a command to be executed using these rules:
+
+  1. If the helper string begins with "!", it is considered a shell
+     snippet, and everything after the "!" becomes the command.
+
+  2. Otherwise, if the helper string begins with an absolute path, the
+     verbatim helper string becomes the command.
+
+  3. Otherwise, the string "git credential-" is prepended to the helper
+     string, and the result becomes the command.
+
+The resulting command then has an "operation" argument appended to it
+(see below for details), and the result is executed by the shell.
+
+Here are some example specifications:
+
+----------------------------------------------------
+# run "git credential-foo"
+foo
+
+# same as above, but pass an argument to the helper
+foo --bar=baz
+
+# the arguments are parsed by the shell, so use shell
+# quoting if necessary
+foo --bar="whitespace arg"
+
+# you can also use an absolute path, which will not use the git wrapper
+/path/to/my/helper --with-arguments
+
+# or you can specify your own shell snippet
+!f() { echo "password=`cat $HOME/.secret`"; }; f
+----------------------------------------------------
+
+Generally speaking, rule (3) above is the simplest for users to specify.
+Authors of credential helpers should make an effort to assist their
+users by naming their program "git-credential-$NAME", and putting it in
+the $PATH or $GIT_EXEC_PATH during installation, which will allow a user
+to enable it with `git config credential.helper $NAME`.
+
+When a helper is executed, it will have one "operation" argument
+appended to its command line, which is one of:
+
+`get`::
+
+       Return a matching credential, if any exists.
+
+`store`::
+
+       Store the credential, if applicable to the helper.
+
+`erase`::
+
+       Remove a matching credential, if any, from the helper's storage.
+
+The details of the credential will be provided on the helper's stdin
+stream. The credential is split into a set of named attributes.
+Attributes are provided to the helper, one per line. Each attribute is
+specified by a key-value pair, separated by an `=` (equals) sign,
+followed by a newline. The key may contain any bytes except `=`,
+newline, or NUL. The value may contain any bytes except newline or NUL.
+In both cases, all bytes are treated as-is (i.e., there is no quoting,
+and one cannot transmit a value with newline or NUL in it). The list of
+attributes is terminated by a blank line or end-of-file.
+
+Git will send the following attributes (but may not send all of
+them for a given credential; for example, a `host` attribute makes no
+sense when dealing with a non-network protocol):
+
+`protocol`::
+
+       The protocol over which the credential will be used (e.g.,
+       `https`).
+
+`host`::
+
+       The remote hostname for a network credential.
+
+`path`::
+
+       The path with which the credential will be used. E.g., for
+       accessing a remote https repository, this will be the
+       repository's path on the server.
+
+`username`::
+
+       The credential's username, if we already have one (e.g., from a
+       URL, from the user, or from a previously run helper).
+
+`password`::
+
+       The credential's password, if we are asking it to be stored.
+
+For a `get` operation, the helper should produce a list of attributes
+on stdout in the same format. A helper is free to produce a subset, or
+even no values at all if it has nothing useful to provide. Any provided
+attributes will overwrite those already known about by git.
+
+For a `store` or `erase` operation, the helper's output is ignored.
+If it fails to perform the requested operation, it may complain to
+stderr to inform the user. If it does not support the requested
+operation (e.g., a read-only store), it should silently ignore the
+request.
+
+If a helper receives any other operation, it should silently ignore the
+request. This leaves room for future operations to be added (older
+helpers will just ignore the new requests).
index f6a4a361bd56e1cc0f4645e09898e6852bfb8a8b..4b92514f60d65232e955cd5dddbeb5318add7274 100644 (file)
@@ -135,9 +135,14 @@ There are some macros to easily define options:
        describes the group or an empty string.
        Start the description with an upper-case letter.
 
-`OPT_BOOLEAN(short, long, &int_var, description)`::
-       Introduce a boolean option.
-       `int_var` is incremented on each use.
+`OPT_BOOL(short, long, &int_var, description)`::
+       Introduce a boolean option. `int_var` is set to one with
+       `--option` and set to zero with `--no-option`.
+
+`OPT_COUNTUP(short, long, &int_var, description)`::
+       Introduce a count-up option.
+       `int_var` is incremented on each use of `--option`, and
+       reset to zero with `--no-option`.
 
 `OPT_BIT(short, long, &int_var, description, mask)`::
        Introduce a boolean option.
@@ -148,8 +153,9 @@ There are some macros to easily define options:
        If used, `int_var` is bitwise-anded with the inverted `mask`.
 
 `OPT_SET_INT(short, long, &int_var, description, integer)`::
-       Introduce a boolean option.
-       If used, set `int_var` to `integer`.
+       Introduce an integer option.
+       `int_var` is set to `integer` with `--option`, and
+       reset to zero with `--no-option`.
 
 `OPT_SET_PTR(short, long, &ptr_var, description, ptr)`::
        Introduce a boolean option.
@@ -198,6 +204,11 @@ There are some macros to easily define options:
        "auto", set `int_var` to 1 if stdout is a tty or a pager,
        0 otherwise.
 
+`OPT_NOOP_NOARG(short, long)`::
+       Introduce an option that has no effect and takes no arguments.
+       Use it to hide deprecated options that are still to be recognized
+       and ignored silently.
+
 
 The last element of the array must be `OPT_END()`.
 
index afe27599511c5f6bab6e4f799fd18e7d83bdd454..95a8bf3846b30650f3ee089a4fbadfcc5a42da20 100644 (file)
@@ -255,8 +255,24 @@ same behaviour as well.
 
 `strbuf_getline`::
 
-       Read a line from a FILE* pointer. The second argument specifies the line
+       Read a line from a FILE *, overwriting the existing contents
+       of the strbuf. The second argument specifies the line
        terminator character, typically `'\n'`.
+       Reading stops after the terminator or at EOF.  The terminator
+       is removed from the buffer before returning.  Returns 0 unless
+       there was nothing left before EOF, in which case it returns `EOF`.
+
+`strbuf_getwholeline`::
+
+       Like `strbuf_getline`, but keeps the trailing terminator (if
+       any) in the buffer.
+
+`strbuf_getwholeline_fd`::
+
+       Like `strbuf_getwholeline`, but operates on a file descriptor.
+       It reads one character at a time, so it is very slow.  Do not
+       use it unless you need the correct position in the file
+       descriptor.
 
 `stripspace`::
 
index a7004c63e7ff5a4fe237267121a5b7c76a4c5496..546980c0a41ce9ba6d09ad5038b4412b7ef42cc7 100644 (file)
@@ -60,6 +60,13 @@ process on the server side over the Git protocol is this:
      "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" |
      nc -v example.com 9418
 
+If the server refuses the request for some reasons, it could abort
+gracefully with an error message.
+
+----
+  error-line     =  PKT-LINE("ERR" SP explanation-text)
+----
+
 
 SSH Transport
 -------------
index 2feae58821eaddf47d5de1c1497bbd939b1ce14b..8ddc96d952c48bd87055591f5b03118f00724010 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.7.6
+DEF_VER=v1.7.9.4
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index bf0d97ef769adda0066578c7823a124385785e94..58b2b86ccf93d045d4c406fe422e94db75533607 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -28,16 +28,25 @@ set up install paths (via config.mak.autogen), so you can write instead
 If you're willing to trade off (much) longer build time for a later
 faster git you can also do a profile feedback build with
 
-       $ make profile-all
-       # make prefix=... install
+       $ make prefix=/usr PROFILE=BUILD all
+       # make prefix=/usr PROFILE=BUILD install
 
 This will run the complete test suite as training workload and then
 rebuild git with the generated profile feedback. This results in a git
 which is a few percent faster on CPU intensive workloads.  This
 may be a good tradeoff for distribution packagers.
 
-Note that the profile feedback build stage currently generates
-a lot of additional compiler warnings.
+Or if you just want to install a profile-optimized version of git into
+your home directory, you could run:
+
+       $ make PROFILE=BUILD install
+
+As a caveat: a profile-optimized build takes a *lot* longer since the
+git tree must be built twice, and in order for the profiling
+measurements to work properly, ccache must be disabled and the test
+suite has to be run using only a single CPU.  In addition, the profile
+feedback build stage currently generates a lot of additional compiler
+warnings.
 
 Issues of note:
 
@@ -83,7 +92,11 @@ Issues of note:
        - "Perl" version 5.8 or later 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.
+         live without these, use NO_PERL.  Note that recent releases of
+         Redhat/Fedora are reported to ship Perl binary package with some
+         core modules stripped away (see http://lwn.net/Articles/477234/),
+         so you might need to install additional packages other than Perl
+         itself, e.g. Time::HiRes.
 
        - "openssl" library is used by git-imap-send to use IMAP over SSL.
          If you don't need it, use NO_OPENSSL.
@@ -106,6 +119,18 @@ Issues of note:
          history graphically, and in git-gui.  If you don't want gitk or
          git-gui, you can use NO_TCLTK.
 
+       - A gettext library is used by default for localizing Git. The
+         primary target is GNU libintl, but the Solaris gettext
+         implementation also works.
+
+         We need a gettext.h on the system for C code, gettext.sh (or
+         Solaris gettext(1)) for shell scripts, and libintl-perl for Perl
+         programs.
+
+         Set NO_GETTEXT to disable localization support and make Git only
+         use English. Under autoconf the configure script will do this
+         automatically if it can't find libintl on the system.
+
  - Some platform specific issues are dealt with Makefile rules,
    but depending on your specific installation, you may not
    have all the libraries/tools needed, or you may have
index 5dbe4f7908fa9aabb7726756f10d2c16894432d0..e4f8e0ef08b9f3f4eb5035f20421ee035127fdf2 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,26 @@ all::
 # Define EXPATDIR=/foo/bar if your expat header and library files are in
 # /foo/bar/include and /foo/bar/lib directories.
 #
+# Define NO_GETTEXT if you don't want Git output to be translated.
+# A translated Git requires GNU libintl or another gettext implementation,
+# plus libintl-perl at runtime.
+#
+# Define HAVE_LIBCHARSET_H if you haven't set NO_GETTEXT and you can't
+# trust the langinfo.h's nl_langinfo(CODESET) function to return the
+# current character set. GNU and Solaris have a nl_langinfo(CODESET),
+# FreeBSD can use either, but MinGW and some others need to use
+# libcharset.h's locale_charset() instead.
+#
+# Define CHARSET_LIB to you need to link with library other than -liconv to
+# use locale_charset() function.  On some platforms this needs to set to
+# -lcharset
+#
+# Define LIBC_CONTAINS_LIBINTL if your gettext implementation doesn't
+# need -lintl when linking.
+#
+# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt
+# doesn't support GNU extensions like --check and --statistics
+#
 # Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
 # it specifies.
 #
@@ -57,8 +77,8 @@ all::
 #
 # Define NO_STRLCPY if you don't have strlcpy.
 #
-# Define NO_STRTOUMAX if you don't have strtoumax in the C library.
-# If your compiler also does not support long long or does not have
+# Define NO_STRTOUMAX if you don't have both strtoimax and strtoumax in the
+# C library. If your compiler also does not support long long or does not have
 # strtoull, define NO_STRTOULL.
 #
 # Define NO_SETENV if you don't have setenv in the C library.
@@ -143,6 +163,8 @@ all::
 #
 # Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
 #
+# Define NO_UNIX_SOCKETS if your system does not offer unix sockets.
+#
 # Define NO_SOCKADDR_STORAGE if your platform does not have struct
 # sockaddr_storage.
 #
@@ -227,6 +249,9 @@ all::
 #
 # Define NO_REGEX if you have no or inferior regex support in your C library.
 #
+# Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the
+# user.
+#
 # Define GETTEXT_POISON if you are debugging the choice of strings marked
 # for translation.  In a GETTEXT_POISON build, you can turn all strings marked
 # for translation into gibberish by setting the GIT_GETTEXT_POISON variable
@@ -250,9 +275,11 @@ all::
 #   DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
 #   DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
 #
-# Define COMPUTE_HEADER_DEPENDENCIES if your compiler supports the -MMD option
-# and you want to avoid rebuilding objects when an unrelated header file
-# changes.
+# Define COMPUTE_HEADER_DEPENDENCIES to "yes" if you want dependencies on
+# header files to be automatically computed, to avoid rebuilding objects when
+# an unrelated header file changes.  Define it to "no" to use the hard-coded
+# dependency rules.  The default is "auto", which means to use computed header
+# dependencies if your compiler is detected to support it.
 #
 # Define CHECK_HEADER_DEPENDENCIES to check for problems in the hard-coded
 # dependency rules.
@@ -305,6 +332,7 @@ gitexecdir = libexec/git-core
 mergetoolsdir = $(gitexecdir)/mergetools
 sharedir = $(prefix)/share
 gitwebdir = $(sharedir)/gitweb
+localedir = $(sharedir)/locale
 template_dir = share/git-core/templates
 htmldir = share/doc/git-doc
 ETC_GITCONFIG = $(sysconfdir)/gitconfig
@@ -313,9 +341,9 @@ lib = lib
 # DESTDIR=
 pathsep = :
 
-export prefix bindir sharedir sysconfdir gitwebdir
+export prefix bindir sharedir sysconfdir gitwebdir localedir
 
-CC = gcc
+CC = cc
 AR = ar
 RM = rm -f
 DIFF = diff
@@ -326,6 +354,7 @@ RPMBUILD = rpmbuild
 TCL_PATH = tclsh
 TCLTK_PATH = wish
 XGETTEXT = xgettext
+MSGFMT = msgfmt
 PTHREAD_LIBS = -lpthread
 PTHREAD_CFLAGS =
 GCOV = gcov
@@ -425,14 +454,20 @@ PROGRAM_OBJS += show-index.o
 PROGRAM_OBJS += upload-pack.o
 PROGRAM_OBJS += http-backend.o
 PROGRAM_OBJS += sh-i18n--envsubst.o
+PROGRAM_OBJS += credential-store.o
+
+# Binary suffix, set to .exe for Windows builds
+X =
 
 PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
 
 TEST_PROGRAMS_NEED_X += test-chmtime
+TEST_PROGRAMS_NEED_X += test-credential
 TEST_PROGRAMS_NEED_X += test-ctype
 TEST_PROGRAMS_NEED_X += test-date
 TEST_PROGRAMS_NEED_X += test-delta
 TEST_PROGRAMS_NEED_X += test-dump-cache-tree
+TEST_PROGRAMS_NEED_X += test-scrap-cache-tree
 TEST_PROGRAMS_NEED_X += test-genrandom
 TEST_PROGRAMS_NEED_X += test-index-version
 TEST_PROGRAMS_NEED_X += test-line-buffer
@@ -509,6 +544,7 @@ LIB_H += argv-array.h
 LIB_H += attr.h
 LIB_H += blob.h
 LIB_H += builtin.h
+LIB_H += bulk-checkin.h
 LIB_H += cache.h
 LIB_H += cache-tree.h
 LIB_H += color.h
@@ -517,11 +553,14 @@ LIB_H += compat/bswap.h
 LIB_H += compat/cygwin.h
 LIB_H += compat/mingw.h
 LIB_H += compat/obstack.h
+LIB_H += compat/terminal.h
 LIB_H += compat/win32/pthread.h
 LIB_H += compat/win32/syslog.h
-LIB_H += compat/win32/sys/poll.h
+LIB_H += compat/win32/poll.h
 LIB_H += compat/win32/dirent.h
+LIB_H += connected.h
 LIB_H += convert.h
+LIB_H += credential.h
 LIB_H += csum-file.h
 LIB_H += decorate.h
 LIB_H += delta.h
@@ -529,9 +568,11 @@ LIB_H += diffcore.h
 LIB_H += diff.h
 LIB_H += dir.h
 LIB_H += exec_cmd.h
+LIB_H += fmt-merge-msg.h
 LIB_H += fsck.h
 LIB_H += gettext.h
 LIB_H += git-compat-util.h
+LIB_H += gpg-interface.h
 LIB_H += graph.h
 LIB_H += grep.h
 LIB_H += hash.h
@@ -555,6 +596,7 @@ LIB_H += parse-options.h
 LIB_H += patch-ids.h
 LIB_H += pkt-line.h
 LIB_H += progress.h
+LIB_H += prompt.h
 LIB_H += quote.h
 LIB_H += reflog-walk.h
 LIB_H += refs.h
@@ -563,6 +605,7 @@ LIB_H += rerere.h
 LIB_H += resolve-undo.h
 LIB_H += revision.h
 LIB_H += run-command.h
+LIB_H += sequencer.h
 LIB_H += sha1-array.h
 LIB_H += sha1-lookup.h
 LIB_H += sideband.h
@@ -572,6 +615,7 @@ LIB_H += streaming.h
 LIB_H += string-list.h
 LIB_H += submodule.h
 LIB_H += tag.h
+LIB_H += thread-utils.h
 LIB_H += transport.h
 LIB_H += tree.h
 LIB_H += tree-walk.h
@@ -594,16 +638,20 @@ LIB_OBJS += base85.o
 LIB_OBJS += bisect.o
 LIB_OBJS += blob.o
 LIB_OBJS += branch.o
+LIB_OBJS += bulk-checkin.o
 LIB_OBJS += bundle.o
 LIB_OBJS += cache-tree.o
 LIB_OBJS += color.o
 LIB_OBJS += combine-diff.o
 LIB_OBJS += commit.o
 LIB_OBJS += compat/obstack.o
+LIB_OBJS += compat/terminal.o
 LIB_OBJS += config.o
 LIB_OBJS += connect.o
+LIB_OBJS += connected.o
 LIB_OBJS += convert.o
 LIB_OBJS += copy.o
+LIB_OBJS += credential.o
 LIB_OBJS += csum-file.o
 LIB_OBJS += ctype.o
 LIB_OBJS += date.o
@@ -623,6 +671,8 @@ LIB_OBJS += entry.o
 LIB_OBJS += environment.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fsck.o
+LIB_OBJS += gpg-interface.o
+LIB_OBJS += gettext.o
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
@@ -658,6 +708,7 @@ LIB_OBJS += pkt-line.o
 LIB_OBJS += preload-index.o
 LIB_OBJS += pretty.o
 LIB_OBJS += progress.o
+LIB_OBJS += prompt.o
 LIB_OBJS += quote.o
 LIB_OBJS += reachable.o
 LIB_OBJS += read-cache.o
@@ -671,6 +722,7 @@ LIB_OBJS += revision.o
 LIB_OBJS += run-command.o
 LIB_OBJS += server-info.o
 LIB_OBJS += setup.o
+LIB_OBJS += sequencer.o
 LIB_OBJS += sha1-array.o
 LIB_OBJS += sha1-lookup.o
 LIB_OBJS += sha1_file.o
@@ -818,12 +870,15 @@ ifeq ($(uname_S),Linux)
        NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
+       LIBC_CONTAINS_LIBINTL = YesPlease
+       HAVE_DEV_TTY = YesPlease
 endif
 ifeq ($(uname_S),GNU/kFreeBSD)
        NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
        DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
+       LIBC_CONTAINS_LIBINTL = YesPlease
 endif
 ifeq ($(uname_S),UnixWare)
        CC = cc
@@ -878,6 +933,7 @@ ifeq ($(uname_S),Darwin)
        endif
        NO_MEMMEM = YesPlease
        USE_ST_TIMESPEC = YesPlease
+       HAVE_DEV_TTY = YesPlease
 endif
 ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
@@ -890,6 +946,7 @@ ifeq ($(uname_S),SunOS)
        NO_MKSTEMPS = YesPlease
        NO_REGEX = YesPlease
        NO_FNMATCH_CASEFOLD = YesPlease
+       NO_MSGFMT_EXTENDED_OPTIONS = YesPlease
        ifeq ($(uname_R),5.6)
                SOCKLEN_T = int
                NO_HSTRERROR = YesPlease
@@ -1013,6 +1070,7 @@ ifeq ($(uname_S),GNU)
        NO_STRLCPY=YesPlease
        NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
+       LIBC_CONTAINS_LIBINTL = YesPlease
 endif
 ifeq ($(uname_S),IRIX)
        NO_SETENV = YesPlease
@@ -1089,8 +1147,10 @@ ifeq ($(uname_S),Windows)
        NO_PREAD = YesPlease
        NEEDS_CRYPTO_WITH_SSL = YesPlease
        NO_LIBGEN_H = YesPlease
+       NO_SYS_POLL_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
        NO_IPV6 = YesPlease
+       NO_UNIX_SOCKETS = YesPlease
        NO_SETENV = YesPlease
        NO_UNSETENV = YesPlease
        NO_STRCASESTR = YesPlease
@@ -1127,7 +1187,7 @@ ifeq ($(uname_S),Windows)
        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/winansi.o \
                compat/win32/pthread.o compat/win32/syslog.o \
-               compat/win32/sys/poll.o compat/win32/dirent.o
+               compat/win32/poll.o compat/win32/dirent.o
        COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
        BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
        EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib
@@ -1182,7 +1242,9 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        NO_PREAD = YesPlease
        NEEDS_CRYPTO_WITH_SSL = YesPlease
        NO_LIBGEN_H = YesPlease
+       NO_SYS_POLL_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
+       NO_UNIX_SOCKETS = YesPlease
        NO_SETENV = YesPlease
        NO_UNSETENV = YesPlease
        NO_STRCASESTR = YesPlease
@@ -1215,7 +1277,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/winansi.o \
                compat/win32/pthread.o compat/win32/syslog.o \
-               compat/win32/sys/poll.o compat/win32/dirent.o
+               compat/win32/poll.o compat/win32/dirent.o
        EXTLIBS += -lws2_32
        PTHREAD_LIBS =
        X = .exe
@@ -1227,6 +1289,7 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
        EXTLIBS += /mingw/lib/libz.a
        NO_R_TO_GCC_LINKER = YesPlease
        INTERNAL_QSORT = YesPlease
+       HAVE_LIBCHARSET_H = YesPlease
 else
        NO_CURL = YesPlease
 endif
@@ -1244,12 +1307,32 @@ endif
 endif
 
 ifdef CHECK_HEADER_DEPENDENCIES
-COMPUTE_HEADER_DEPENDENCIES =
+COMPUTE_HEADER_DEPENDENCIES = no
 USE_COMPUTED_HEADER_DEPENDENCIES =
 endif
 
-ifdef COMPUTE_HEADER_DEPENDENCIES
+ifndef COMPUTE_HEADER_DEPENDENCIES
+COMPUTE_HEADER_DEPENDENCIES = auto
+endif
+
+ifeq ($(COMPUTE_HEADER_DEPENDENCIES),auto)
+dep_check = $(shell $(CC) $(ALL_CFLAGS) \
+       -c -MF /dev/null -MMD -MP -x c /dev/null -o /dev/null 2>&1; \
+       echo $$?)
+ifeq ($(dep_check),0)
+override COMPUTE_HEADER_DEPENDENCIES = yes
+else
+override COMPUTE_HEADER_DEPENDENCIES = no
+endif
+endif
+
+ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
 USE_COMPUTED_HEADER_DEPENDENCIES = YesPlease
+else
+ifneq ($(COMPUTE_HEADER_DEPENDENCIES),no)
+$(error please set COMPUTE_HEADER_DEPENDENCIES to yes, no, or auto \
+(not "$(COMPUTE_HEADER_DEPENDENCIES)"))
+endif
 endif
 
 ifdef SANE_TOOL_PATH
@@ -1395,6 +1478,11 @@ endif
 ifdef NEEDS_LIBGEN
        EXTLIBS += -lgen
 endif
+ifndef NO_GETTEXT
+ifndef LIBC_CONTAINS_LIBINTL
+       EXTLIBS += -lintl
+endif
+endif
 ifdef NEEDS_SOCKET
        EXTLIBS += -lsocket
 endif
@@ -1437,9 +1525,11 @@ ifdef NO_SYMLINK_HEAD
        BASIC_CFLAGS += -DNO_SYMLINK_HEAD
 endif
 ifdef GETTEXT_POISON
-       LIB_OBJS += gettext.o
        BASIC_CFLAGS += -DGETTEXT_POISON
 endif
+ifdef NO_GETTEXT
+       BASIC_CFLAGS += -DNO_GETTEXT
+endif
 ifdef NO_STRCASESTR
        COMPAT_CFLAGS += -DNO_STRCASESTR
        COMPAT_OBJS += compat/strcasestr.o
@@ -1450,7 +1540,7 @@ ifdef NO_STRLCPY
 endif
 ifdef NO_STRTOUMAX
        COMPAT_CFLAGS += -DNO_STRTOUMAX
-       COMPAT_OBJS += compat/strtoumax.o
+       COMPAT_OBJS += compat/strtoumax.o compat/strtoimax.o
 endif
 ifdef NO_STRTOULL
        COMPAT_CFLAGS += -DNO_STRTOULL
@@ -1540,6 +1630,12 @@ ifdef NO_INET_PTON
        LIB_OBJS += compat/inet_pton.o
        BASIC_CFLAGS += -DNO_INET_PTON
 endif
+ifndef NO_UNIX_SOCKETS
+       LIB_OBJS += unix-socket.o
+       LIB_H += unix-socket.h
+       PROGRAM_OBJS += credential-cache.o
+       PROGRAM_OBJS += credential-cache--daemon.o
+endif
 
 ifdef NO_ICONV
        BASIC_CFLAGS += -DNO_ICONV
@@ -1602,6 +1698,15 @@ ifdef HAVE_PATHS_H
        BASIC_CFLAGS += -DHAVE_PATHS_H
 endif
 
+ifdef HAVE_LIBCHARSET_H
+       BASIC_CFLAGS += -DHAVE_LIBCHARSET_H
+       EXTLIBS += $(CHARSET_LIB)
+endif
+
+ifdef HAVE_DEV_TTY
+       BASIC_CFLAGS += -DHAVE_DEV_TTY
+endif
+
 ifdef DIR_HAS_BSD_GROUP_SEMANTICS
        COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
 endif
@@ -1622,6 +1727,10 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
        export GIT_TEST_CMP_USE_COPIED_CONTEXT
 endif
 
+ifndef NO_MSGFMT_EXTENDED_OPTIONS
+       MSGFMT += --check --statistics
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK=NoThanks
 endif
@@ -1652,6 +1761,7 @@ ifndef V
        QUIET_GEN      = @echo '   ' GEN $@;
        QUIET_LNCP     = @echo '   ' LN/CP $@;
        QUIET_XGETTEXT = @echo '   ' XGETTEXT $@;
+       QUIET_MSGFMT   = @echo '   ' MSGFMT $@;
        QUIET_GCOV     = @echo '   ' GCOV $@;
        QUIET_SP       = @echo '   ' SP $<;
        QUIET_SUBDIR0  = +@subdir=
@@ -1667,6 +1777,26 @@ ifdef ASCIIDOC7
        export ASCIIDOC7
 endif
 
+### profile feedback build
+#
+
+# Can adjust this to be a global directory if you want to do extended
+# data gathering
+PROFILE_DIR := $(CURDIR)
+
+ifeq ("$(PROFILE)","GEN")
+       CFLAGS += -fprofile-generate=$(PROFILE_DIR) -DNO_NORETURN=1
+       EXTLIBS += -lgcov
+       export CCACHE_DISABLE=t
+       V=1
+else
+ifneq ("$(PROFILE)","")
+       CFLAGS += -fprofile-use=$(PROFILE_DIR) -fprofile-correction -DNO_NORETURN=1
+       export CCACHE_DISABLE=t
+       V=1
+endif
+endif
+
 # Shell quote (do not use $(call) to accommodate ancient setups);
 
 SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER))
@@ -1678,6 +1808,7 @@ bindir_SQ = $(subst ','\'',$(bindir))
 bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
 mandir_SQ = $(subst ','\'',$(mandir))
 infodir_SQ = $(subst ','\'',$(infodir))
+localedir_SQ = $(subst ','\'',$(localedir))
 gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 template_dir_SQ = $(subst ','\'',$(template_dir))
 htmldir_SQ = $(subst ','\'',$(htmldir))
@@ -1722,7 +1853,17 @@ export DIFF TAR INSTALL DESTDIR SHELL_PATH
 
 SHELL = $(SHELL_PATH)
 
-all:: shell_compatibility_test $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
+all:: shell_compatibility_test
+
+ifeq "$(PROFILE)" "BUILD"
+ifeq ($(filter all,$(MAKECMDGOALS)),all)
+all:: profile-clean
+       $(MAKE) PROFILE=GEN all
+       $(MAKE) PROFILE=GEN -j1 test
+endif
+endif
+
+all:: $(ALL_PROGRAMS) $(SCRIPT_LIB) $(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 -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';)
 endif
@@ -1733,7 +1874,7 @@ ifndef NO_TCLTK
        $(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
 endif
 ifndef NO_PERL
-       $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+       $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all
 endif
 ifndef NO_PYTHON
        $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
@@ -1783,6 +1924,7 @@ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
     -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
     -e 's|@@DIFF@@|$(DIFF_SQ)|' \
     -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+    -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \
     -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
     -e $(BROKEN_PATH_FIX) \
     $@.sh >$@+
@@ -1896,9 +2038,9 @@ OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS)
 dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
 dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
 
-ifdef COMPUTE_HEADER_DEPENDENCIES
+ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
 $(dep_dirs):
-       mkdir -p $@
+       @mkdir -p $@
 
 missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs))
 dep_file = $(dir $@).depend/$(notdir $@).d
@@ -1909,7 +2051,7 @@ Please unset CHECK_HEADER_DEPENDENCIES and try again)
 endif
 endif
 
-ifndef COMPUTE_HEADER_DEPENDENCIES
+ifneq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
 ifndef CHECK_HEADER_DEPENDENCIES
 dep_dirs =
 missing_dep_dirs =
@@ -2035,6 +2177,9 @@ config.sp config.s config.o: EXTRA_CPPFLAGS = \
 attr.sp attr.s attr.o: EXTRA_CPPFLAGS = \
        -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"'
 
+gettext.sp gettext.s gettext.o: EXTRA_CPPFLAGS = \
+       -DGIT_LOCALE_PATH='"$(localedir_SQ)"'
+
 http.sp http.s http.o: EXTRA_CPPFLAGS = \
        -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"'
 
@@ -2108,32 +2253,57 @@ XGETTEXT_FLAGS = \
 XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \
        --keyword=_ --keyword=N_ --keyword="Q_:1,2"
 XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell
+XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
 LOCALIZED_C := $(C_OBJ:o=c)
 LOCALIZED_SH := $(SCRIPT_SH)
+LOCALIZED_PERL := $(SCRIPT_PERL)
+
+ifdef XGETTEXT_INCLUDE_TESTS
+LOCALIZED_C += t/t0200/test.c
+LOCALIZED_SH += t/t0200/test.sh
+LOCALIZED_PERL += t/t0200/test.perl
+endif
 
 po/git.pot: $(LOCALIZED_C)
        $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ $(XGETTEXT_FLAGS_C) $(LOCALIZED_C)
        $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_SH) \
                $(LOCALIZED_SH)
+       $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_PERL) \
+               $(LOCALIZED_PERL)
        mv $@+ $@
 
 pot: po/git.pot
 
+POFILES := $(wildcard po/*.po)
+MOFILES := $(patsubst po/%.po,po/build/locale/%/LC_MESSAGES/git.mo,$(POFILES))
+
+ifndef NO_GETTEXT
+all:: $(MOFILES)
+endif
+
+po/build/locale/%/LC_MESSAGES/git.mo: po/%.po
+       $(QUIET_MSGFMT)mkdir -p $(dir $@) && $(MSGFMT) -o $@ $<
+
+FIND_SOURCE_FILES = ( git ls-files '*.[hcS]' 2>/dev/null || \
+                       $(FIND) . \( -name .git -type d -prune \) \
+                               -o \( -name '*.[hcS]' -type f -print \) )
+
 $(ETAGS_TARGET): FORCE
        $(RM) $(ETAGS_TARGET)
-       $(FIND) . -name '*.[hcS]' -print | xargs etags -a -o $(ETAGS_TARGET)
+       $(FIND_SOURCE_FILES) | xargs etags -a -o $(ETAGS_TARGET)
 
 tags: FORCE
        $(RM) tags
-       $(FIND) . -name '*.[hcS]' -print | xargs ctags -a
+       $(FIND_SOURCE_FILES) | xargs ctags -a
 
 cscope:
        $(RM) cscope*
-       $(FIND) . -name '*.[hcS]' -print | xargs cscope -b
+       $(FIND_SOURCE_FILES) | xargs cscope -b
 
 ### Detect prefix changes
 TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\
-             $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
+             $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\
+             $(localedir_SQ)
 
 GIT-CFLAGS: FORCE
        @FLAGS='$(TRACK_CFLAGS)'; \
@@ -2170,7 +2340,9 @@ endif
 ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
        @echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@
 endif
+       @echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
        @echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@
+       @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
@@ -2284,8 +2456,12 @@ install: all
        $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
-       (cd mergetools && $(TAR) cf - .) | \
-       (cd '$(DESTDIR_SQ)$(mergetools_instdir_SQ)' && umask 022 && $(TAR) xof -)
+       $(INSTALL) -m 644 mergetools/* '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
+ifndef NO_GETTEXT
+       $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(localedir_SQ)'
+       (cd po/build/locale && $(TAR) cf - .) | \
+       (cd '$(DESTDIR_SQ)$(localedir_SQ)' && umask 022 && $(TAR) xof -)
+endif
 ifndef NO_PERL
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
        $(MAKE) -C gitweb install
@@ -2415,13 +2591,18 @@ distclean: clean
        $(RM) configure
        $(RM) po/git.pot
 
-clean:
+profile-clean:
+       $(RM) $(addsuffix *.gcda,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
+       $(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
+
+clean: profile-clean
        $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \
                builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB)
        $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
        $(RM) $(TEST_PROGRAMS)
        $(RM) -r bin-wrappers
        $(RM) -r $(dep_dirs)
+       $(RM) -r po/build/
        $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
        $(RM) -r autom4te.cache
        $(RM) config.log config.mak.autogen config.mak.append config.status config.cache
@@ -2444,7 +2625,7 @@ ifndef NO_TCLTK
 endif
        $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-LDFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
 
-.PHONY: all install clean strip
+.PHONY: all install profile-clean clean strip
 .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
 .PHONY: FORCE cscope
 
@@ -2554,18 +2735,3 @@ cover_db: coverage-report
 cover_db_html: cover_db
        cover -report html -outputdir cover_db_html cover_db
 
-### profile feedback build
-#
-.PHONY: profile-all profile-clean
-
-PROFILE_GEN_CFLAGS := $(CFLAGS) -fprofile-generate -DNO_NORETURN=1
-PROFILE_USE_CFLAGS := $(CFLAGS) -fprofile-use -fprofile-correction -DNO_NORETURN=1
-
-profile-clean:
-       $(RM) $(addsuffix *.gcda,$(object_dirs))
-       $(RM) $(addsuffix *.gcno,$(object_dirs))
-
-profile-all: profile-clean
-       $(MAKE) CFLAGS="$(PROFILE_GEN_CFLAGS)" all
-       $(MAKE) CFLAGS="$(PROFILE_GEN_CFLAGS)" -j1 test
-       $(MAKE) CFLAGS="$(PROFILE_USE_CFLAGS)" all
diff --git a/README b/README
index 67cfeb2016b24df1cb406c18145efd399f6a1792..d2690ec8dc6e5f054c33a5f3b4c26f6ad038706d 100644 (file)
--- a/README
+++ b/README
@@ -42,10 +42,12 @@ including full documentation and Git related tools.
 
 The user discussion and development of Git take place on the Git
 mailing list -- everyone is welcome to post bug reports, feature
-requests, comments and patches to git@vger.kernel.org. To subscribe
-to the list, send an email with just "subscribe git" in the body to
-majordomo@vger.kernel.org. The mailing list archives are available at
-http://marc.theaimsgroup.com/?l=git and other archival sites.
+requests, comments and patches to git@vger.kernel.org (read
+Documentation/SubmittingPatches for instructions on patch submission).
+To subscribe to the list, send an email with just "subscribe git" in
+the body to majordomo@vger.kernel.org. The mailing list archives are
+available at http://marc.theaimsgroup.com/?l=git and other archival
+sites.
 
 The messages titled "A note from the maintainer", "What's in
 git.git (stable)" and "What's cooking in git.git (topics)" and
index 472b95854bce66c50c310ed18dc149a3207df11f..f9f04dd382737edd1e0caba5326e892017224a1d 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.7.7.6.txt
\ No newline at end of file
+Documentation/RelNotes/1.7.9.4.txt
\ No newline at end of file
index 0be4b5f008e1646946ba12fa5e51aa0830911ece..65a07859f261e12e88d638a1f4546583e0133d4c 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -19,6 +19,25 @@ static struct {
        { "detachedhead", &advice_detached_head },
 };
 
+void advise(const char *advice, ...)
+{
+       struct strbuf buf = STRBUF_INIT;
+       va_list params;
+       const char *cp, *np;
+
+       va_start(params, advice);
+       strbuf_addf(&buf, advice, params);
+       va_end(params);
+
+       for (cp = buf.buf; *cp; cp = np) {
+               np = strchrnul(cp, '\n');
+               fprintf(stderr, _("hint: %.*s\n"), (int)(np - cp), cp);
+               if (*np)
+                       np++;
+       }
+       strbuf_release(&buf);
+}
+
 int git_default_advice_config(const char *var, const char *value)
 {
        const char *k = skip_prefix(var, "advice.");
@@ -34,16 +53,23 @@ int git_default_advice_config(const char *var, const char *value)
        return 0;
 }
 
-void NORETURN die_resolve_conflict(const char *me)
+int error_resolve_conflict(const char *me)
 {
+       error("'%s' is not possible because you have unmerged files.", me);
        if (advice_resolve_conflict)
                /*
                 * Message used both when 'git commit' fails and when
                 * other commands doing a merge do.
                 */
-               die("'%s' is not possible because you have unmerged files.\n"
-                   "Please, fix them up in the work tree, and then use 'git add/rm <file>' as\n"
-                   "appropriate to mark resolution and make a commit, or use 'git commit -a'.", me);
-       else
-               die("'%s' is not possible because you have unmerged files.", me);
+               advise(_("Fix them up in the work tree,\n"
+                        "and then use 'git add/rm <file>' as\n"
+                        "appropriate to mark resolution and make a commit,\n"
+                        "or use 'git commit -a'."));
+       return -1;
+}
+
+void NORETURN die_resolve_conflict(const char *me)
+{
+       error_resolve_conflict(me);
+       die("Exiting because of an unresolved conflict.");
 }
index 3244ebb5c1caffebaaf8f28ce221533c38ec29b5..e5d0af782b1445b48b49cd58f481a593268c3384 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -11,7 +11,8 @@ extern int advice_implicit_identity;
 extern int advice_detached_head;
 
 int git_default_advice_config(const char *var, const char *value);
-
+void advise(const char *advice, ...);
+int error_resolve_conflict(const char *me);
 extern void NORETURN die_resolve_conflict(const char *me);
 
 #endif /* ADVICE_H */
index 3fd7f475f1d8ff5efe602a10f02ea3e1c3aa9edd..1ee837d7170cfa52da6725cfe7c5ae0d6d67462e 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -247,7 +247,8 @@ static void parse_pathspec_arg(const char **pathspec,
 }
 
 static void parse_treeish_arg(const char **argv,
-               struct archiver_args *ar_args, const char *prefix)
+               struct archiver_args *ar_args, const char *prefix,
+               int remote)
 {
        const char *name = argv[0];
        const unsigned char *commit_sha1;
@@ -256,6 +257,24 @@ static void parse_treeish_arg(const char **argv,
        const struct commit *commit;
        unsigned char sha1[20];
 
+       /* Remotes are only allowed to fetch actual refs */
+       if (remote) {
+               char *ref = NULL;
+               const char *refname, *colon = NULL;
+
+               colon = strchr(name, ':');
+               if (colon)
+                       refname = xstrndup(name, colon - name);
+               else
+                       refname = name;
+
+               if (!dwim_ref(refname, strlen(refname), sha1, &ref))
+                       die("no such ref: %s", refname);
+               if (refname != name)
+                       free((void *)refname);
+               free(ref);
+       }
+
        if (get_sha1(name, sha1))
                die("Not a valid object name");
 
@@ -318,7 +337,7 @@ static int parse_archive_args(int argc, const char **argv,
                        "prepend prefix to each pathname in the archive"),
                OPT_STRING('o', "output", &output, "file",
                        "write the archive to this file"),
-               OPT_BOOLEAN(0, "worktree-attributes", &worktree_attributes,
+               OPT_BOOL(0, "worktree-attributes", &worktree_attributes,
                        "read .gitattributes in working directory"),
                OPT__VERBOSE(&verbose, "report archived files on stderr"),
                OPT__COMPR('0', &compression_level, "store only", 0),
@@ -332,7 +351,7 @@ static int parse_archive_args(int argc, const char **argv,
                OPT__COMPR_HIDDEN('8', &compression_level, 8),
                OPT__COMPR('9', &compression_level, "compress better", 9),
                OPT_GROUP(""),
-               OPT_BOOLEAN('l', "list", &list,
+               OPT_BOOL('l', "list", &list,
                        "list supported archive formats"),
                OPT_GROUP(""),
                OPT_STRING(0, "remote", &remote, "repo",
@@ -414,7 +433,7 @@ int write_archive(int argc, const char **argv, const char *prefix,
                setup_git_directory();
        }
 
-       parse_treeish_arg(argv, &args, prefix);
+       parse_treeish_arg(argv, &args, prefix, remote);
        parse_pathspec_arg(argv + 1, &args);
 
        return ar->write_archive(ar, &args);
index de05bf824620324e42b7a27571ecd3e763cf7e8f..6e186e29cc4a6a74944798e8c4248219e9c5997f 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -800,25 +800,25 @@ static int check_ancestors(const char *prefix)
 {
        struct rev_info revs;
        struct object_array pending_copy;
-       int i, res;
+       int res;
 
        bisect_rev_setup(&revs, prefix, "^%s", "%s", 0);
 
        /* Save pending objects, so they can be cleaned up later. */
-       memset(&pending_copy, 0, sizeof(pending_copy));
-       for (i = 0; i < revs.pending.nr; i++)
-               add_object_array(revs.pending.objects[i].item,
-                                revs.pending.objects[i].name,
-                                &pending_copy);
+       pending_copy = revs.pending;
+       revs.leak_pending = 1;
 
+       /*
+        * bisect_common calls prepare_revision_walk right away, which
+        * (together with .leak_pending = 1) makes us the sole owner of
+        * the list of pending objects.
+        */
        bisect_common(&revs);
        res = (revs.commits != NULL);
 
        /* Clean up objects used, as they will be reused. */
-       for (i = 0; i < pending_copy.nr; i++) {
-               struct object *o = pending_copy.objects[i].item;
-               clear_commit_marks((struct commit *)o, ALL_REV_FLAGS);
-       }
+       clear_commit_marks_for_object_array(&pending_copy, ALL_REV_FLAGS);
+       free(pending_copy.objects);
 
        return res;
 }
index 22f2e4db2d1d05184dc2eeec2ff87021a67e01f2..ec3c3ff007a53105089b9699d5917bccc28c7cfe 100644 (file)
--- a/bisect.h
+++ b/bisect.h
@@ -15,13 +15,12 @@ extern void print_commit_list(struct commit_list *list,
                              const char *format_cur,
                              const char *format_last);
 
-/* bisect_show_flags flags in struct rev_list_info */
 #define BISECT_SHOW_ALL                (1<<0)
-#define BISECT_SHOW_TRIED      (1<<1)
+#define REV_LIST_QUIET         (1<<1)
 
 struct rev_list_info {
        struct rev_info *revs;
-       int bisect_show_flags;
+       int flags;
        int show_timestamp;
        int hdr_termination;
        const char *header_prefix;
index fecedd3b4635c79adcbe0e26613af079beff64d0..9971820a184d9713126c3c9f763dd8f6ec1b1a50 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -135,6 +135,37 @@ static int setup_tracking(const char *new_ref, const char *orig_ref,
        return 0;
 }
 
+struct branch_desc_cb {
+       const char *config_name;
+       const char *value;
+};
+
+static int read_branch_desc_cb(const char *var, const char *value, void *cb)
+{
+       struct branch_desc_cb *desc = cb;
+       if (strcmp(desc->config_name, var))
+               return 0;
+       free((char *)desc->value);
+       return git_config_string(&desc->value, var, value);
+}
+
+int read_branch_desc(struct strbuf *buf, const char *branch_name)
+{
+       struct branch_desc_cb cb;
+       struct strbuf name = STRBUF_INIT;
+       strbuf_addf(&name, "branch.%s.description", branch_name);
+       cb.config_name = name.buf;
+       cb.value = NULL;
+       if (git_config(read_branch_desc_cb, &cb) < 0) {
+               strbuf_release(&name);
+               return -1;
+       }
+       if (cb.value)
+               strbuf_addstr(buf, cb.value);
+       strbuf_release(&name);
+       return 0;
+}
+
 int validate_new_branchname(const char *name, struct strbuf *ref,
                            int force, int attr_only)
 {
@@ -150,7 +181,7 @@ int validate_new_branchname(const char *name, struct strbuf *ref,
                const char *head;
                unsigned char sha1[20];
 
-               head = resolve_ref("HEAD", sha1, 0, NULL);
+               head = resolve_ref_unsafe("HEAD", sha1, 0, NULL);
                if (!is_bare_repository() && head && !strcmp(head, ref->buf))
                        die("Cannot force update the current branch.");
        }
@@ -159,7 +190,8 @@ int validate_new_branchname(const char *name, struct strbuf *ref,
 
 void create_branch(const char *head,
                   const char *name, const char *start_name,
-                  int force, int reflog, enum branch_track track)
+                  int force, int reflog, int clobber_head,
+                  enum branch_track track)
 {
        struct ref_lock *lock = NULL;
        struct commit *commit;
@@ -174,7 +206,8 @@ void create_branch(const char *head,
                explicit_tracking = 1;
 
        if (validate_new_branchname(name, &ref, force,
-                                   track == BRANCH_TRACK_OVERRIDE)) {
+                                   track == BRANCH_TRACK_OVERRIDE ||
+                                   clobber_head)) {
                if (!force)
                        dont_change_ref = 1;
                else
@@ -240,6 +273,7 @@ void create_branch(const char *head,
 void remove_branch_state(void)
 {
        unlink(git_path("CHERRY_PICK_HEAD"));
+       unlink(git_path("REVERT_HEAD"));
        unlink(git_path("MERGE_HEAD"));
        unlink(git_path("MERGE_RR"));
        unlink(git_path("MERGE_MSG"));
index 1285158dd4f26e5bbb0e0d7133055f168fee773f..b99c5a369e31a85d1fff822460e69a79d8c6102b 100644 (file)
--- a/branch.h
+++ b/branch.h
@@ -13,7 +13,8 @@
  * branch for (if any).
  */
 void create_branch(const char *head, const char *name, const char *start_name,
-                  int force, int reflog, enum branch_track track);
+                  int force, int reflog,
+                  int clobber_head, enum branch_track track);
 
 /*
  * Validates that the requested branch may be created, returning the
@@ -46,4 +47,9 @@ void remove_branch_state(void);
 #define BRANCH_CONFIG_VERBOSE 01
 extern void install_branch_config(int flag, const char *local, const char *origin, const char *remote);
 
+/*
+ * Read branch description
+ */
+extern int read_branch_desc(struct strbuf *, const char *branch_name);
+
 #endif
index 0e9da9083491eb3c18464b730f481cf74ee82430..857b9c8aa85fff5764b528485a880cd9dfa95b17 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -14,8 +14,14 @@ extern const char git_usage_string[];
 extern const char git_more_info_string[];
 
 extern void prune_packed_objects(int);
+
+struct fmt_merge_msg_opts {
+       unsigned add_title:1;
+       int shortlog_len;
+};
+
 extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
-                        int merge_title, int shortlog_len);
+                        struct fmt_merge_msg_opts *);
 extern void commit_notes(struct notes_tree *t, const char *msg);
 
 struct notes_rewrite_cfg {
@@ -133,6 +139,7 @@ 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_archive_writer(int argc, const char **argv, const char *prefix);
 extern int cmd_upload_tar(int argc, const char **argv, const char *prefix);
 extern int cmd_var(int argc, const char **argv, const char *prefix);
 extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
index c59b0c98fefefc413c8330715fffcc83142d5b2d..b79336d712b4c71cc2f026b0e9f42ea0bcebfc6f 100644 (file)
@@ -13,6 +13,7 @@
 #include "diff.h"
 #include "diffcore.h"
 #include "revision.h"
+#include "bulk-checkin.h"
 
 static const char * const builtin_add_usage[] = {
        "git add [options] [--] <filepattern>...",
@@ -279,6 +280,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
 
        argc = setup_revisions(argc, argv, &rev, NULL);
        rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+       DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES);
        out = open(file, O_CREAT | O_WRONLY, 0644);
        if (out < 0)
                die (_("Could not open '%s' for writing."), file);
@@ -458,11 +460,15 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                free(seen);
        }
 
+       plug_bulk_checkin();
+
        exit_status |= add_files_to_cache(prefix, pathspec, flags);
 
        if (add_new_files)
                exit_status |= add_files(&dir, flags);
 
+       unplug_bulk_checkin();
+
  finish:
        if (active_cache_changed) {
                if (write_cache(newfd, active_cache, active_nr) ||
index 082fe8f3e3e6088adcce4b54ae351c4ec1f03786..389898f13364eb640077c1d82fefea98d9d3755f 100644 (file)
@@ -14,6 +14,7 @@
 #include "builtin.h"
 #include "string-list.h"
 #include "dir.h"
+#include "diff.h"
 #include "parse-options.h"
 
 /*
@@ -3241,7 +3242,7 @@ static void stat_patch_list(struct patch *patch)
                show_stats(patch);
        }
 
-       printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
+       print_stat_summary(stdout, files, adds, dels);
 }
 
 static void numstat_patch_list(struct patch *patch)
@@ -3587,15 +3588,12 @@ static int write_out_one_reject(struct patch *patch)
        return -1;
 }
 
-static int write_out_results(struct patch *list, int skipped_patch)
+static int write_out_results(struct patch *list)
 {
        int phase;
        int errs = 0;
        struct patch *l;
 
-       if (!list && !skipped_patch)
-               return error("No changes");
-
        for (phase = 0; phase < 2; phase++) {
                l = list;
                while (l) {
@@ -3721,6 +3719,9 @@ static int apply_patch(int fd, const char *filename, int options)
                offset += nr;
        }
 
+       if (!list && !skipped_patch)
+               die("unrecognized input");
+
        if (whitespace_error && (ws_error_action == die_on_ws_error))
                apply = 0;
 
@@ -3738,7 +3739,7 @@ static int apply_patch(int fd, const char *filename, int options)
            !apply_with_reject)
                exit(1);
 
-       if (apply && write_out_results(list, skipped_patch))
+       if (apply && write_out_results(list))
                exit(1);
 
        if (fake_ancestor)
@@ -3838,7 +3839,6 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
        int i;
        int errs = 0;
        int is_not_gitdir = !startup_info->have_repository;
-       int binary;
        int force_apply = 0;
 
        const char *whitespace_option = NULL;
@@ -3857,12 +3857,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
                        "ignore additions made by the patch"),
                OPT_BOOLEAN(0, "stat", &diffstat,
                        "instead of applying the patch, output diffstat for the input"),
-               { OPTION_BOOLEAN, 0, "allow-binary-replacement", &binary,
-                 NULL, "old option, now no-op",
-                 PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
-               { OPTION_BOOLEAN, 0, "binary", &binary,
-                 NULL, "old option, now no-op",
-                 PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
+               OPT_NOOP_NOARG(0, "allow-binary-replacement"),
+               OPT_NOOP_NOARG(0, "binary"),
                OPT_BOOLEAN(0, "numstat", &numstat,
                        "shows number of added and deleted lines in decimal notation"),
                OPT_BOOLEAN(0, "summary", &summary,
index 3e1f7e1e453f6ecc28fb910686c6580e77929556..5a67c202f06abeaa90a7547d78b536f7f2b9db24 100644 (file)
@@ -2096,6 +2096,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        if (!contents_from || strcmp("-", contents_from)) {
                struct stat st;
                const char *read_from;
+               char *buf_ptr;
                unsigned long buf_len;
 
                if (contents_from) {
@@ -2113,8 +2114,8 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
                switch (st.st_mode & S_IFMT) {
                case S_IFREG:
                        if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
-                           textconv_object(read_from, mode, null_sha1, &buf.buf, &buf_len))
-                               buf.len = buf_len;
+                           textconv_object(read_from, mode, null_sha1, &buf_ptr, &buf_len))
+                               strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1);
                        else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
                                die_errno("cannot open or read '%s'", read_from);
                        break;
index f49596f826228e22d354194ddbb73ec100d8728e..d8cccf725d3fab24ad585a26629373fc987bb3f8 100644 (file)
@@ -104,6 +104,7 @@ static int branch_merged(int kind, const char *name,
         */
        struct commit *reference_rev = NULL;
        const char *reference_name = NULL;
+       void *reference_name_to_free = NULL;
        int merged;
 
        if (kind == REF_LOCAL_BRANCH) {
@@ -114,8 +115,8 @@ static int branch_merged(int kind, const char *name,
                    branch->merge &&
                    branch->merge[0] &&
                    branch->merge[0]->dst &&
-                   (reference_name =
-                    resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
+                   (reference_name = reference_name_to_free =
+                    resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
                        reference_rev = lookup_commit_reference(sha1);
        }
        if (!reference_rev)
@@ -141,6 +142,7 @@ static int branch_merged(int kind, const char *name,
                                "         '%s', even though it is merged to HEAD."),
                                name, reference_name);
        }
+       free(reference_name_to_free);
        return merged;
 }
 
@@ -186,7 +188,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                free(name);
 
                name = xstrdup(mkpath(fmt, bname.buf));
-               if (!resolve_ref(name, sha1, 1, NULL)) {
+               if (read_ref(name, sha1)) {
                        error(_("%sbranch '%s' not found."),
                                        remote, bname.buf);
                        ret = 1;
@@ -250,7 +252,7 @@ static char *resolve_symref(const char *src, const char *prefix)
        int flag;
        const char *dst, *cp;
 
-       dst = resolve_ref(src, sha1, 0, &flag);
+       dst = resolve_ref_unsafe(src, sha1, 0, &flag);
        if (!(dst && (flag & REF_ISSYMREF)))
                return NULL;
        if (prefix && (cp = skip_prefix(dst, prefix)))
@@ -260,9 +262,22 @@ static char *resolve_symref(const char *src, const char *prefix)
 
 struct append_ref_cb {
        struct ref_list *ref_list;
+       const char **pattern;
        int ret;
 };
 
+static int match_patterns(const char **pattern, const char *refname)
+{
+       if (!*pattern)
+               return 1; /* no pattern always matches */
+       while (*pattern) {
+               if (!fnmatch(*pattern, refname, 0))
+                       return 1;
+               pattern++;
+       }
+       return 0;
+}
+
 static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
 {
        struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
@@ -297,6 +312,9 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
        if ((kind & ref_list->kinds) == 0)
                return 0;
 
+       if (!match_patterns(cb->pattern, refname))
+               return 0;
+
        commit = NULL;
        if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
                commit = lookup_commit_reference_gently(sha1, 1);
@@ -492,7 +510,7 @@ static void show_detached(struct ref_list *ref_list)
        }
 }
 
-static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
+static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern)
 {
        int i;
        struct append_ref_cb cb;
@@ -506,11 +524,16 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
        if (merge_filter != NO_FILTER)
                init_revisions(&ref_list.revs, NULL);
        cb.ref_list = &ref_list;
+       cb.pattern = pattern;
        cb.ret = 0;
        for_each_rawref(append_ref, &cb);
        if (merge_filter != NO_FILTER) {
                struct commit *filter;
                filter = lookup_commit_reference_gently(merge_filter_ref, 0);
+               if (!filter)
+                       die("object '%s' does not point to a commit",
+                           sha1_to_hex(merge_filter_ref));
+
                filter->object.flags |= UNINTERESTING;
                add_pending_object(&ref_list.revs,
                                   (struct object *) filter, "");
@@ -523,7 +546,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
        qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
 
        detached = (detached && (kinds & REF_LOCAL_BRANCH));
-       if (detached)
+       if (detached && match_patterns(pattern, "HEAD"))
                show_detached(&ref_list);
 
        for (i = 0; i < ref_list.index; i++) {
@@ -548,9 +571,9 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
 static void rename_branch(const char *oldname, const char *newname, int force)
 {
        struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
-       unsigned char sha1[20];
        struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
        int recovery = 0;
+       int clobber_head_ok;
 
        if (!oldname)
                die(_("cannot rename the current branch while not on any."));
@@ -560,13 +583,19 @@ static void rename_branch(const char *oldname, const char *newname, int force)
                 * Bad name --- this could be an attempt to rename a
                 * ref that we used to allow to be created by accident.
                 */
-               if (resolve_ref(oldref.buf, sha1, 1, NULL))
+               if (ref_exists(oldref.buf))
                        recovery = 1;
                else
                        die(_("Invalid branch name: '%s'"), oldname);
        }
 
-       validate_new_branchname(newname, &newref, force, 0);
+       /*
+        * A command like "git branch -M currentbranch currentbranch" cannot
+        * cause the worktree to become inconsistent with HEAD, so allow it.
+        */
+       clobber_head_ok = !strcmp(oldname, newname);
+
+       validate_new_branchname(newname, &newref, force, clobber_head_ok);
 
        strbuf_addf(&logmsg, "Branch: renamed %s to %s",
                 oldref.buf, newref.buf);
@@ -606,11 +635,49 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int
        return 0;
 }
 
+static const char edit_description[] = "BRANCH_DESCRIPTION";
+
+static int edit_branch_description(const char *branch_name)
+{
+       FILE *fp;
+       int status;
+       struct strbuf buf = STRBUF_INIT;
+       struct strbuf name = STRBUF_INIT;
+
+       read_branch_desc(&buf, branch_name);
+       if (!buf.len || buf.buf[buf.len-1] != '\n')
+               strbuf_addch(&buf, '\n');
+       strbuf_addf(&buf,
+                   "# Please edit the description for the branch\n"
+                   "#   %s\n"
+                   "# Lines starting with '#' will be stripped.\n",
+                   branch_name);
+       fp = fopen(git_path(edit_description), "w");
+       if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
+               strbuf_release(&buf);
+               return error(_("could not write branch description template: %s\n"),
+                            strerror(errno));
+       }
+       strbuf_reset(&buf);
+       if (launch_editor(git_path(edit_description), &buf, NULL)) {
+               strbuf_release(&buf);
+               return -1;
+       }
+       stripspace(&buf, 1);
+
+       strbuf_addf(&name, "branch.%s.description", branch_name);
+       status = git_config_set(name.buf, buf.buf);
+       strbuf_release(&name);
+       strbuf_release(&buf);
+
+       return status;
+}
+
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
-       int delete = 0, rename = 0, force_create = 0;
+       int delete = 0, rename = 0, force_create = 0, list = 0;
        int verbose = 0, abbrev = -1, detached = 0;
-       int reflog = 0;
+       int reflog = 0, edit_description = 0;
        enum branch_track track;
        int kinds = REF_LOCAL_BRANCH;
        struct commit_list *with_commit = NULL;
@@ -624,7 +691,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_SET_INT( 0, "set-upstream",  &track, "change upstream info",
                        BRANCH_TRACK_OVERRIDE),
                OPT__COLOR(&branch_use_color, "use colored output"),
-               OPT_SET_INT('r', NULL,     &kinds, "act on remote-tracking branches",
+               OPT_SET_INT('r', "remotes",     &kinds, "act on remote-tracking branches",
                        REF_REMOTE_BRANCH),
                {
                        OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
@@ -641,13 +708,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT__ABBREV(&abbrev),
 
                OPT_GROUP("Specific git-branch actions:"),
-               OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches",
+               OPT_SET_INT('a', "all", &kinds, "list both remote-tracking and local branches",
                        REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
-               OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1),
+               OPT_BIT('d', "delete", &delete, "delete fully merged branch", 1),
                OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2),
-               OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
+               OPT_BIT('m', "move", &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(0, "list", &list, "list branch names"),
+               OPT_BOOLEAN('l', "create-reflog", &reflog, "create the branch's reflog"),
+               OPT_BOOLEAN(0, "edit-description", &edit_description,
+                           "edit the description for the branch"),
                OPT__FORCE(&force_create, "force creation (when already exists)"),
                {
                        OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
@@ -671,10 +741,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        track = git_branch_track;
 
-       head = resolve_ref("HEAD", head_sha1, 0, NULL);
+       head = resolve_refdup("HEAD", head_sha1, 0, NULL);
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
-       head = xstrdup(head);
        if (!strcmp(head, "HEAD")) {
                detached = 1;
        } else {
@@ -686,7 +755,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
                             0);
-       if (!!delete + !!rename + !!force_create > 1)
+
+       if (!delete && !rename && !edit_description && argc == 0)
+               list = 1;
+
+       if (!!delete + !!rename + !!force_create + !!list > 1)
                usage_with_options(builtin_branch_usage, options);
 
        if (abbrev == -1)
@@ -694,17 +767,48 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        if (delete)
                return delete_branches(argc, argv, delete > 1, kinds);
-       else if (argc == 0)
-               return print_ref_list(kinds, detached, verbose, abbrev, with_commit);
-       else if (rename && (argc == 1))
-               rename_branch(head, argv[0], rename > 1);
-       else if (rename && (argc == 2))
-               rename_branch(argv[0], argv[1], rename > 1);
-       else if (argc <= 2) {
+       else if (list)
+               return print_ref_list(kinds, detached, verbose, abbrev,
+                                     with_commit, argv);
+       else if (edit_description) {
+               const char *branch_name;
+               struct strbuf branch_ref = STRBUF_INIT;
+
+               if (detached)
+                       die("Cannot give description to detached HEAD");
+               if (!argc)
+                       branch_name = head;
+               else if (argc == 1)
+                       branch_name = argv[0];
+               else
+                       usage_with_options(builtin_branch_usage, options);
+
+               strbuf_addf(&branch_ref, "refs/heads/%s", branch_name);
+               if (!ref_exists(branch_ref.buf)) {
+                       strbuf_release(&branch_ref);
+
+                       if (!argc)
+                               return error("No commit on branch '%s' yet.",
+                                            branch_name);
+                       else
+                               return error("No such branch '%s'.", branch_name);
+               }
+               strbuf_release(&branch_ref);
+
+               if (edit_branch_description(branch_name))
+                       return 1;
+       } else if (rename) {
+               if (argc == 1)
+                       rename_branch(head, argv[0], rename > 1);
+               else if (argc == 2)
+                       rename_branch(argv[0], argv[1], rename > 1);
+               else
+                       usage_with_options(builtin_branch_usage, options);
+       } else if (argc > 0 && argc <= 2) {
                if (kinds != REF_LOCAL_BRANCH)
                        die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
                create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
-                             force_create, reflog, track);
+                             force_create, reflog, 0, track);
        } else
                usage_with_options(builtin_branch_usage, options);
 
index abb11650fdd936d01210552b68739f2bff82ed06..44c421eb0fe9cd8947d6666d15d790bc241ee7f3 100644 (file)
@@ -5,6 +5,7 @@
 #include "parse-options.h"
 
 static int all_attrs;
+static int cached_attrs;
 static int stdin_paths;
 static const char * const check_attr_usage[] = {
 "git check-attr [-a | --all | attr...] [--] pathname...",
@@ -16,6 +17,7 @@ static int null_term_line;
 
 static const struct option check_attr_options[] = {
        OPT_BOOLEAN('a', "all", &all_attrs, "report all attributes set on file"),
+       OPT_BOOLEAN(0,  "cached", &cached_attrs, "use .gitattributes only from the index"),
        OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"),
        OPT_BOOLEAN('z', NULL, &null_term_line,
                "input paths are terminated by a null character"),
@@ -101,6 +103,9 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
                die("invalid cache");
        }
 
+       if (cached_attrs)
+               git_attr_set_direction(GIT_ATTR_INDEX, NULL);
+
        doubledash = -1;
        for (i = 0; doubledash < 0 && i < argc; i++) {
                if (!strcmp(argv[i], "--"))
index 0723cf245e52e6c0ab99cbe92c74010f9f7d7167..28a7320271a9555356170bfdb06ffb4b07bc9b92 100644 (file)
@@ -8,29 +8,32 @@
 #include "strbuf.h"
 
 static const char builtin_check_ref_format_usage[] =
-"git check-ref-format [--print] <refname>\n"
+"git check-ref-format [--normalize] [options] <refname>\n"
 "   or: git check-ref-format --branch <branchname-shorthand>";
 
 /*
- * Remove leading slashes and replace each run of adjacent slashes in
- * src with a single slash, and write the result to dst.
+ * Return a copy of refname but with leading slashes removed and runs
+ * of adjacent slashes replaced with single slashes.
  *
  * 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)
+static char *collapse_slashes(const char *refname)
 {
+       char *ret = xmalloc(strlen(refname) + 1);
        char ch;
        char prev = '/';
+       char *cp = ret;
 
-       while ((ch = *src++) != '\0') {
+       while ((ch = *refname++) != '\0') {
                if (prev == '/' && ch == prev)
                        continue;
 
-               *dst++ = ch;
+               *cp++ = ch;
                prev = ch;
        }
-       *dst = '\0';
+       *cp = '\0';
+       return ret;
 }
 
 static int check_ref_format_branch(const char *arg)
@@ -45,27 +48,41 @@ static int check_ref_format_branch(const char *arg)
        return 0;
 }
 
-static int check_ref_format_print(const char *arg)
-{
-       char *refname = xmalloc(strlen(arg) + 1);
-
-       if (check_ref_format(arg))
-               return 1;
-       collapse_slashes(refname, arg);
-       printf("%s\n", refname);
-       return 0;
-}
-
 int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
 {
+       int i;
+       int normalize = 0;
+       int flags = 0;
+       const char *refname;
+
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage(builtin_check_ref_format_usage);
 
        if (argc == 3 && !strcmp(argv[1], "--branch"))
                return check_ref_format_branch(argv[2]);
-       if (argc == 3 && !strcmp(argv[1], "--print"))
-               return check_ref_format_print(argv[2]);
-       if (argc != 2)
+
+       for (i = 1; i < argc && argv[i][0] == '-'; i++) {
+               if (!strcmp(argv[i], "--normalize") || !strcmp(argv[i], "--print"))
+                       normalize = 1;
+               else if (!strcmp(argv[i], "--allow-onelevel"))
+                       flags |= REFNAME_ALLOW_ONELEVEL;
+               else if (!strcmp(argv[i], "--no-allow-onelevel"))
+                       flags &= ~REFNAME_ALLOW_ONELEVEL;
+               else if (!strcmp(argv[i], "--refspec-pattern"))
+                       flags |= REFNAME_REFSPEC_PATTERN;
+               else
+                       usage(builtin_check_ref_format_usage);
+       }
+       if (! (i == argc - 1))
                usage(builtin_check_ref_format_usage);
-       return !!check_ref_format(argv[1]);
+
+       refname = argv[i];
+       if (normalize)
+               refname = collapse_slashes(refname);
+       if (check_refname_format(refname, flags))
+               return 1;
+       if (normalize)
+               printf("%s\n", refname);
+
+       return 0;
 }
index 7ea9f29aa6d24edf44431161208a306798e69623..a76aa2a6fd6ad076f4d10e4f5227d24b31c063ca 100644 (file)
@@ -34,6 +34,7 @@ struct checkout_opts {
        int force_detach;
        int writeout_stage;
        int writeout_error;
+       int overwrite_ignore;
 
        /* not set by parse_options */
        int branch_exists;
@@ -114,16 +115,21 @@ static int check_stage(int stage, struct cache_entry *ce, int pos)
                return error(_("path '%s' does not have their version"), ce->name);
 }
 
-static int check_all_stages(struct cache_entry *ce, int pos)
+static int check_stages(unsigned stages, struct cache_entry *ce, int pos)
 {
-       if (ce_stage(ce) != 1 ||
-           active_nr <= pos + 2 ||
-           strcmp(active_cache[pos+1]->name, ce->name) ||
-           ce_stage(active_cache[pos+1]) != 2 ||
-           strcmp(active_cache[pos+2]->name, ce->name) ||
-           ce_stage(active_cache[pos+2]) != 3)
-               return error(_("path '%s' does not have all three versions"),
-                            ce->name);
+       unsigned seen = 0;
+       const char *name = ce->name;
+
+       while (pos < active_nr) {
+               ce = active_cache[pos];
+               if (strcmp(name, ce->name))
+                       break;
+               seen |= (1 << ce_stage(ce));
+               pos++;
+       }
+       if ((stages & seen) != stages)
+               return error(_("path '%s' does not have all necessary versions"),
+                            name);
        return 0;
 }
 
@@ -150,18 +156,27 @@ static int checkout_merged(int pos, struct checkout *state)
        int status;
        unsigned char sha1[20];
        mmbuffer_t result_buf;
+       unsigned char threeway[3][20];
+       unsigned mode = 0;
+
+       memset(threeway, 0, sizeof(threeway));
+       while (pos < active_nr) {
+               int stage;
+               stage = ce_stage(ce);
+               if (!stage || strcmp(path, ce->name))
+                       break;
+               hashcpy(threeway[stage - 1], ce->sha1);
+               if (stage == 2)
+                       mode = create_ce_mode(ce->ce_mode);
+               pos++;
+               ce = active_cache[pos];
+       }
+       if (is_null_sha1(threeway[1]) || is_null_sha1(threeway[2]))
+               return error(_("path '%s' does not have necessary versions"), path);
 
-       if (ce_stage(ce) != 1 ||
-           active_nr <= pos + 2 ||
-           strcmp(active_cache[pos+1]->name, path) ||
-           ce_stage(active_cache[pos+1]) != 2 ||
-           strcmp(active_cache[pos+2]->name, path) ||
-           ce_stage(active_cache[pos+2]) != 3)
-               return error(_("path '%s' does not have all 3 versions"), path);
-
-       read_mmblob(&ancestor, active_cache[pos]->sha1);
-       read_mmblob(&ours, active_cache[pos+1]->sha1);
-       read_mmblob(&theirs, active_cache[pos+2]->sha1);
+       read_mmblob(&ancestor, threeway[0]);
+       read_mmblob(&ours, threeway[1]);
+       read_mmblob(&theirs, threeway[2]);
 
        /*
         * NEEDSWORK: re-create conflicts from merges with
@@ -192,9 +207,7 @@ static int checkout_merged(int pos, struct checkout *state)
        if (write_sha1_file(result_buf.ptr, result_buf.size,
                            blob_type, sha1))
                die(_("Unable to add merge result for '%s'"), path);
-       ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode),
-                             sha1,
-                             path, 2, 0);
+       ce = make_cache_entry(mode, sha1, path, 2, 0);
        if (!ce)
                die(_("make_cache_entry failed for path '%s'"), path);
        status = checkout_entry(ce, state, NULL);
@@ -252,7 +265,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
                        } else if (stage) {
                                errs |= check_stage(stage, ce, pos);
                        } else if (opts->merge) {
-                               errs |= check_all_stages(ce, pos);
+                               errs |= check_stages((1<<2) | (1<<3), ce, pos);
                        } else {
                                errs = 1;
                                error(_("path '%s' is unmerged"), ce->name);
@@ -288,7 +301,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
            commit_locked_index(lock_file))
                die(_("unable to write new index file"));
 
-       resolve_ref("HEAD", rev, 0, &flag);
+       read_ref_full("HEAD", rev, 0, &flag);
        head = lookup_commit_reference_gently(rev, 1);
 
        errs |= post_checkout_hook(head, head, 0);
@@ -409,9 +422,11 @@ static int merge_working_tree(struct checkout_opts *opts,
                topts.gently = opts->merge && old->commit;
                topts.verbose_update = !opts->quiet;
                topts.fn = twoway_merge;
-               topts.dir = xcalloc(1, sizeof(*topts.dir));
-               topts.dir->flags |= DIR_SHOW_IGNORED;
-               setup_standard_excludes(topts.dir);
+               if (opts->overwrite_ignore) {
+                       topts.dir = xcalloc(1, sizeof(*topts.dir));
+                       topts.dir->flags |= DIR_SHOW_IGNORED;
+                       setup_standard_excludes(topts.dir);
+               }
                tree = parse_tree_indirect(old->commit ?
                                           old->commit->object.sha1 :
                                           EMPTY_TREE_SHA1_BIN);
@@ -540,7 +555,9 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                else
                        create_branch(old->name, opts->new_branch, new->name,
                                      opts->new_branch_force ? 1 : 0,
-                                     opts->new_branch_log, opts->track);
+                                     opts->new_branch_log,
+                                     opts->new_branch_force ? 1 : 0,
+                                     opts->track);
                new->name = opts->new_branch;
                setup_branch_path(new);
        }
@@ -565,8 +582,12 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                create_symref("HEAD", new->path, msg.buf);
                if (!opts->quiet) {
                        if (old->path && !strcmp(new->path, old->path)) {
-                               fprintf(stderr, _("Already on '%s'\n"),
-                                       new->name);
+                               if (opts->new_branch_force)
+                                       fprintf(stderr, _("Reset branch '%s'\n"),
+                                               new->name);
+                               else
+                                       fprintf(stderr, _("Already on '%s'\n"),
+                                               new->name);
                        } else if (opts->new_branch) {
                                if (opts->branch_exists)
                                        fprintf(stderr, _("Switched to and reset branch '%s'\n"), new->name);
@@ -593,23 +614,11 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                report_tracking(new);
 }
 
-static int add_one_ref_to_rev_list_arg(const char *refname,
-                                      const unsigned char *sha1,
-                                      int flags,
-                                      void *cb_data)
+static int add_pending_uninteresting_ref(const char *refname,
+                                        const unsigned char *sha1,
+                                        int flags, void *cb_data)
 {
-       argv_array_push(cb_data, refname);
-       return 0;
-}
-
-static int clear_commit_marks_from_one_ref(const char *refname,
-                                     const unsigned char *sha1,
-                                     int flags,
-                                     void *cb_data)
-{
-       struct commit *commit = lookup_commit_reference_gently(sha1, 1);
-       if (commit)
-               clear_commit_marks(commit, -1);
+       add_pending_sha1(cb_data, refname, sha1, flags | UNINTERESTING);
        return 0;
 }
 
@@ -678,18 +687,21 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
  */
 static void orphaned_commit_warning(struct commit *commit)
 {
-       struct argv_array args = ARGV_ARRAY_INIT;
        struct rev_info revs;
-
-       argv_array_push(&args, "(internal)");
-       argv_array_push(&args, sha1_to_hex(commit->object.sha1));
-       argv_array_push(&args, "--not");
-       for_each_ref(add_one_ref_to_rev_list_arg, &args);
-       argv_array_push(&args, "--");
+       struct object *object = &commit->object;
+       struct object_array refs;
 
        init_revisions(&revs, NULL);
-       if (setup_revisions(args.argc - 1, args.argv, &revs, NULL) != 1)
-               die(_("internal error: only -- alone should have been left"));
+       setup_revisions(0, NULL, &revs, NULL);
+
+       object->flags &= ~UNINTERESTING;
+       add_pending_object(&revs, object, sha1_to_hex(object->sha1));
+
+       for_each_ref(add_pending_uninteresting_ref, &revs);
+
+       refs = revs.pending;
+       revs.leak_pending = 1;
+
        if (prepare_revision_walk(&revs))
                die(_("internal error in revision walk"));
        if (!(commit->object.flags & UNINTERESTING))
@@ -697,24 +709,22 @@ static void orphaned_commit_warning(struct commit *commit)
        else
                describe_detached_head(_("Previous HEAD position was"), commit);
 
-       argv_array_clear(&args);
-       clear_commit_marks(commit, -1);
-       for_each_ref(clear_commit_marks_from_one_ref, NULL);
+       clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);
+       free(refs.objects);
 }
 
 static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
 {
        int ret = 0;
        struct branch_info old;
+       void *path_to_free;
        unsigned char rev[20];
        int flag;
        memset(&old, 0, sizeof(old));
-       old.path = xstrdup(resolve_ref("HEAD", rev, 0, &flag));
+       old.path = path_to_free = resolve_refdup("HEAD", rev, 0, &flag);
        old.commit = lookup_commit_reference_gently(rev, 1);
-       if (!(flag & REF_ISSYMREF)) {
-               free((char *)old.path);
+       if (!(flag & REF_ISSYMREF))
                old.path = NULL;
-       }
 
        if (old.path && !prefixcmp(old.path, "refs/heads/"))
                old.name = old.path + strlen("refs/heads/");
@@ -728,8 +738,10 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
        }
 
        ret = merge_working_tree(opts, &old, new);
-       if (ret)
+       if (ret) {
+               free(path_to_free);
                return ret;
+       }
 
        if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
                orphaned_commit_warning(old.commit);
@@ -737,7 +749,7 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
        update_refs_for_switch(opts, &old, new);
 
        ret = post_checkout_hook(old.commit, new->commit, 1);
-       free((char *)old.path);
+       free(path_to_free);
        return ret || opts->writeout_error;
 }
 
@@ -875,8 +887,8 @@ static int parse_branchname_arg(int argc, const char **argv,
        new->name = arg;
        setup_branch_path(new);
 
-       if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK &&
-           resolve_ref(new->path, branch_rev, 1, NULL))
+       if (!check_refname_format(new->path, 0) &&
+           !read_ref(new->path, branch_rev))
                hashcpy(rev, branch_rev);
        else
                new->path = NULL; /* not an existing branch */
@@ -910,6 +922,17 @@ static int parse_branchname_arg(int argc, const char **argv,
        return argcount;
 }
 
+static int switch_unborn_to_new_branch(struct checkout_opts *opts)
+{
+       int status;
+       struct strbuf branch_ref = STRBUF_INIT;
+
+       strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch);
+       status = create_symref("HEAD", branch_ref.buf, "checkout -b");
+       strbuf_release(&branch_ref);
+       return status;
+}
+
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
        struct checkout_opts opts;
@@ -936,6 +959,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                            3),
                OPT__FORCE(&opts.force, "force checkout (throw away local modifications)"),
                OPT_BOOLEAN('m', "merge", &opts.merge, "perform a 3-way merge with the new branch"),
+               OPT_BOOLEAN(0, "overwrite-ignore", &opts.overwrite_ignore, "update ignored files (default)"),
                OPT_STRING(0, "conflict", &conflict_style, "style",
                           "conflict style (merge or diff3)"),
                OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
@@ -947,6 +971,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
        memset(&opts, 0, sizeof(opts));
        memset(&new, 0, sizeof(new));
+       opts.overwrite_ignore = 1;
 
        gitmodules_config();
        git_config(git_checkout_config, &opts);
@@ -1067,7 +1092,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                struct strbuf buf = STRBUF_INIT;
 
                opts.branch_exists = validate_new_branchname(opts.new_branch, &buf,
-                                                            !!opts.new_branch_force, 0);
+                                                            !!opts.new_branch_force,
+                                                            !!opts.new_branch_force);
 
                strbuf_release(&buf);
        }
@@ -1078,5 +1104,13 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        if (opts.writeout_stage)
                die(_("--ours/--theirs is incompatible with switching branches."));
 
+       if (!new.commit) {
+               unsigned char rev[20];
+               int flag;
+
+               if (!read_ref_full("HEAD", rev, 0, &flag) &&
+                   (flag & REF_ISSYMREF) && is_null_sha1(rev))
+                       return switch_unborn_to_new_branch(&opts);
+       }
        return switch_branches(&opts, &new);
 }
index efe8b6cce5a9f2ae40c6f69755debecfb6b501ca..0fb5956b48a77767e650d85f0818790736a4f94d 100644 (file)
@@ -45,7 +45,7 @@ static char *option_branch = NULL;
 static const char *real_git_dir;
 static char *option_upload_pack = "git-upload-pack";
 static int option_verbosity;
-static int option_progress;
+static int option_progress = -1;
 static struct string_list option_config;
 static struct string_list option_reference;
 
@@ -60,8 +60,8 @@ static int opt_parse_reference(const struct option *opt, const char *arg, int un
 
 static struct option builtin_clone_options[] = {
        OPT__VERBOSITY(&option_verbosity),
-       OPT_BOOLEAN(0, "progress", &option_progress,
-                       "force progress reporting"),
+       OPT_BOOL(0, "progress", &option_progress,
+                "force progress reporting"),
        OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
                    "don't create a checkout"),
        OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
@@ -84,8 +84,8 @@ static struct option builtin_clone_options[] = {
                   "directory from which templates will be used"),
        OPT_CALLBACK(0 , "reference", &option_reference, "repo",
                     "reference repository", &opt_parse_reference),
-       OPT_STRING('o', "origin", &option_origin, "branch",
-                  "use <branch> instead of 'origin' to track upstream"),
+       OPT_STRING('o', "origin", &option_origin, "name",
+                  "use <name> 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",
@@ -105,7 +105,7 @@ static const char *argv_submodule[] = {
 
 static char *get_repo_path(const char *repo, int *is_bundle)
 {
-       static char *suffix[] = { "/.git", ".git", "" };
+       static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
        static char *bundle_suffix[] = { ".bundle", "" };
        struct stat st;
        int i;
@@ -115,7 +115,7 @@ static char *get_repo_path(const char *repo, int *is_bundle)
                path = mkpath("%s%s", repo, suffix[i]);
                if (stat(path, &st))
                        continue;
-               if (S_ISDIR(st.st_mode)) {
+               if (S_ISDIR(st.st_mode) && is_git_directory(path)) {
                        *is_bundle = 0;
                        return xstrdup(absolute_path(path));
                } else if (S_ISREG(st.st_mode) && st.st_size > 8) {
index d083795e26e7893c6b7d466f9bcf2be1311cfeb2..164b655df93fea1ec2f63f5238c1ad7a377c8385 100644 (file)
@@ -8,8 +8,9 @@
 #include "tree.h"
 #include "builtin.h"
 #include "utf8.h"
+#include "gpg-interface.h"
 
-static const char commit_tree_usage[] = "git commit-tree <sha1> [(-p <sha1>)...] < changelog";
+static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S<signer>] [-m <message>] [-F <file>] <sha1> <changelog";
 
 static void new_parent(struct commit *parent, struct commit_list **parents_p)
 {
@@ -25,38 +26,98 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)
        commit_list_insert(parent, parents_p);
 }
 
+static int commit_tree_config(const char *var, const char *value, void *cb)
+{
+       int status = git_gpg_config(var, value, NULL);
+       if (status)
+               return status;
+       return git_default_config(var, value, cb);
+}
+
 int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 {
-       int i;
+       int i, got_tree = 0;
        struct commit_list *parents = NULL;
        unsigned char tree_sha1[20];
        unsigned char commit_sha1[20];
        struct strbuf buffer = STRBUF_INIT;
+       const char *sign_commit = NULL;
 
-       git_config(git_default_config, NULL);
+       git_config(commit_tree_config, NULL);
 
        if (argc < 2 || !strcmp(argv[1], "-h"))
                usage(commit_tree_usage);
+
        if (get_sha1(argv[1], tree_sha1))
                die("Not a valid object name %s", argv[1]);
 
-       for (i = 2; i < argc; i += 2) {
-               unsigned char sha1[20];
-               const char *a, *b;
-               a = argv[i]; b = argv[i+1];
-               if (!b || strcmp(a, "-p"))
-                       usage(commit_tree_usage);
-
-               if (get_sha1(b, sha1))
-                       die("Not a valid object name %s", b);
-               assert_sha1_type(sha1, OBJ_COMMIT);
-               new_parent(lookup_commit(sha1), &parents);
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strcmp(arg, "-p")) {
+                       unsigned char sha1[20];
+                       if (argc <= ++i)
+                               usage(commit_tree_usage);
+                       if (get_sha1(argv[i], sha1))
+                               die("Not a valid object name %s", argv[i]);
+                       assert_sha1_type(sha1, OBJ_COMMIT);
+                       new_parent(lookup_commit(sha1), &parents);
+                       continue;
+               }
+
+               if (!memcmp(arg, "-S", 2)) {
+                       sign_commit = arg + 2;
+                       continue;
+               }
+
+               if (!strcmp(arg, "-m")) {
+                       if (argc <= ++i)
+                               usage(commit_tree_usage);
+                       if (buffer.len)
+                               strbuf_addch(&buffer, '\n');
+                       strbuf_addstr(&buffer, argv[i]);
+                       strbuf_complete_line(&buffer);
+                       continue;
+               }
+
+               if (!strcmp(arg, "-F")) {
+                       int fd;
+
+                       if (argc <= ++i)
+                               usage(commit_tree_usage);
+                       if (buffer.len)
+                               strbuf_addch(&buffer, '\n');
+                       if (!strcmp(argv[i], "-"))
+                               fd = 0;
+                       else {
+                               fd = open(argv[i], O_RDONLY);
+                               if (fd < 0)
+                                       die_errno("git commit-tree: failed to open '%s'",
+                                                 argv[i]);
+                       }
+                       if (strbuf_read(&buffer, fd, 0) < 0)
+                               die_errno("git commit-tree: failed to read '%s'",
+                                         argv[i]);
+                       if (fd && close(fd))
+                               die_errno("git commit-tree: failed to close '%s'",
+                                         argv[i]);
+                       strbuf_complete_line(&buffer);
+                       continue;
+               }
+
+               if (get_sha1(arg, tree_sha1))
+                       die("Not a valid object name %s", arg);
+               if (got_tree)
+                       die("Cannot give more than one trees");
+               got_tree = 1;
        }
 
-       if (strbuf_read(&buffer, 0, 0) < 0)
-               die_errno("git commit-tree: failed to read");
+       if (!buffer.len) {
+               if (strbuf_read(&buffer, 0, 0) < 0)
+                       die_errno("git commit-tree: failed to read");
+       }
 
-       if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
+       if (commit_tree(&buffer, tree_sha1, parents, commit_sha1,
+                       NULL, sign_commit)) {
                strbuf_release(&buffer);
                return 1;
        }
index 66ffe31736933d1ad15fdeb7bb321f10fcab1dd3..eae5a29aeb4248b972f97632026993c25d5e03da 100644 (file)
@@ -26,6 +26,7 @@
 #include "unpack-trees.h"
 #include "quote.h"
 #include "submodule.h"
+#include "gpg-interface.h"
 
 static const char * const builtin_commit_usage[] = {
        "git commit [options] [--] <filepattern>...",
@@ -81,10 +82,13 @@ static const char *template_file;
 static const char *author_message, *author_message_buffer;
 static char *edit_message, *use_message;
 static char *fixup_message, *squash_message;
-static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff;
+static int all, also, interactive, patch_interactive, only, amend, signoff;
+static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int no_post_rewrite, allow_empty_message;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
+static char *sign_commit;
+
 /*
  * The default commit message cleanup mode will remove the lines
  * beginning with # (shell comments) and leading and trailing
@@ -103,7 +107,7 @@ static enum commit_whence whence;
 static int use_editor = 1, include_status = 1;
 static int show_ignored_in_status;
 static const char *only_include_assumed;
-static struct strbuf message;
+static struct strbuf message = STRBUF_INIT;
 
 static int null_termination;
 static enum {
@@ -138,12 +142,14 @@ static struct option builtin_commit_options[] = {
        OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
        OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
        OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
-       OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
+       OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C/-c/--amend)"),
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
-       OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
+       OPT_BOOL('e', "edit", &edit_flag, "force edit of commit"),
        OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
        OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
+       { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+         "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
        /* end commit message options */
 
        OPT_GROUP("Commit contents options"),
@@ -190,16 +196,16 @@ static void determine_whence(struct wt_status *s)
 
 static const char *whence_s(void)
 {
-       char *s = "";
+       const char *s = "";
 
        switch (whence) {
        case FROM_COMMIT:
                break;
        case FROM_MERGE:
-               s = "merge";
+               s = _("merge");
                break;
        case FROM_CHERRY_PICK:
-               s = "cherry-pick";
+               s = _("cherry-pick");
                break;
        }
 
@@ -255,8 +261,9 @@ static int list_paths(struct string_list *list, const char *with_tree,
        m = xcalloc(1, i);
 
        if (with_tree) {
-               const char *max_prefix = pathspec_prefix(prefix, pattern);
-               overlay_tree_on_cache(with_tree, max_prefix);
+               char *max_prefix = common_prefix(pattern);
+               overlay_tree_on_cache(with_tree, max_prefix ? max_prefix : prefix);
+               free(max_prefix);
        }
 
        for (i = 0; i < active_nr; i++) {
@@ -393,6 +400,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
                fd = hold_locked_index(&index_lock, 1);
                add_files_to_cache(also ? prefix : NULL, pathspec, 0);
                refresh_cache_or_die(refresh_flags);
+               update_main_cache_tree(1);
                if (write_cache(fd, active_cache, active_nr) ||
                    close_lock_file(&index_lock))
                        die(_("unable to write new_index file"));
@@ -413,6 +421,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
                fd = hold_locked_index(&index_lock, 1);
                refresh_cache_or_die(refresh_flags);
                if (active_cache_changed) {
+                       update_main_cache_tree(1);
                        if (write_cache(fd, active_cache, active_nr) ||
                            commit_locked_index(&index_lock))
                                die(_("unable to write new_index file"));
@@ -534,6 +543,7 @@ static void determine_author_info(struct strbuf *author_ident)
 
        if (author_message) {
                const char *a, *lb, *rb, *eol;
+               size_t len;
 
                a = strstr(author_message_buffer, "\nauthor ");
                if (!a)
@@ -554,6 +564,11 @@ static void determine_author_info(struct strbuf *author_ident)
                                         (a + strlen("\nauthor "))));
                email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<")));
                date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> ")));
+               len = eol - (rb + strlen("> "));
+               date = xmalloc(len + 2);
+               *date = '@';
+               memcpy(date + 1, rb + strlen("> "), len);
+               date[len + 1] = '\0';
        }
 
        if (force_author) {
@@ -861,10 +876,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
         */
        discard_cache();
        read_cache_from(index_file);
-       if (!active_cache_tree)
-               active_cache_tree = cache_tree();
-       if (cache_tree_update(active_cache_tree,
-                             active_cache, active_nr, 0, 0) < 0) {
+       if (update_main_cache_tree(0)) {
                error(_("Error building trees"));
                return 0;
        }
@@ -1019,8 +1031,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
 
        if (logfile || message.len || use_message || fixup_message)
                use_editor = 0;
-       if (edit_flag)
-               use_editor = 1;
+       if (0 <= edit_flag)
+               use_editor = edit_flag;
        if (!use_editor)
                setenv("GIT_EDITOR", ":", 1);
 
@@ -1258,7 +1270,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
        struct commit *commit;
        struct strbuf format = STRBUF_INIT;
        unsigned char junk_sha1[20];
-       const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
+       const char *head;
        struct pretty_print_context pctx = {0};
        struct strbuf author_ident = STRBUF_INIT;
        struct strbuf committer_ident = STRBUF_INIT;
@@ -1303,6 +1315,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
 
+       head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL);
        printf("[%s%s ",
                !prefixcmp(head, "refs/heads/") ?
                        head + 11 :
@@ -1323,6 +1336,7 @@ 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;
+       int status;
 
        if (!strcmp(k, "commit.template"))
                return git_config_pathname(&template_file, k, v);
@@ -1331,6 +1345,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)
                return 0;
        }
 
+       status = git_gpg_config(k, v, NULL);
+       if (status)
+               return status;
        return git_status_config(k, v, s);
 }
 
@@ -1381,6 +1398,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        int allow_fast_forward = 1;
        struct wt_status s;
        struct commit *current_head = NULL;
+       struct commit_extra_header *extra = NULL;
 
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_commit_usage, builtin_commit_options);
@@ -1424,7 +1442,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                        pptr = &commit_list_insert(c->item, pptr)->next;
        } else if (whence == FROM_MERGE) {
                struct strbuf m = STRBUF_INIT;
-               struct commit *commit;
                FILE *fp;
 
                if (!reflog_msg)
@@ -1435,11 +1452,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                        die_errno(_("could not open '%s' for reading"),
                                  git_path("MERGE_HEAD"));
                while (strbuf_getline(&m, fp, '\n') != EOF) {
-                       unsigned char sha1[20];
-                       if (get_sha1_hex(m.buf, sha1) < 0)
+                       struct commit *parent;
+
+                       parent = get_merge_parent(m.buf);
+                       if (!parent)
                                die(_("Corrupt MERGE_HEAD file (%s)"), m.buf);
-                       commit = lookup_commit_or_die(sha1, "MERGE_HEAD");
-                       pptr = &commit_list_insert(commit, pptr)->next;
+                       pptr = &commit_list_insert(parent, pptr)->next;
                }
                fclose(fp);
                strbuf_release(&m);
@@ -1482,12 +1500,21 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                exit(1);
        }
 
-       if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,
-                       author_ident.buf)) {
+       if (amend) {
+               const char *exclude_gpgsig[2] = { "gpgsig", NULL };
+               extra = read_commit_extra_headers(current_head, exclude_gpgsig);
+       } else {
+               struct commit_extra_header **tail = &extra;
+               append_merge_tag_headers(parents, &tail);
+       }
+
+       if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1,
+                                author_ident.buf, sign_commit, extra)) {
                rollback_index_files();
                die(_("failed to write commit object"));
        }
        strbuf_release(&author_ident);
+       free_commit_extra_headers(extra);
 
        ref_lock = lock_any_ref_for_update("HEAD",
                                           !current_head
@@ -1513,6 +1540,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        }
 
        unlink(git_path("CHERRY_PICK_HEAD"));
+       unlink(git_path("REVERT_HEAD"));
        unlink(git_path("MERGE_HEAD"));
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("MERGE_MODE"));
index 0315ad76f881aab9a64084f8d2fdd2019907e4cb..d35c06ae51573eafbddd6309fda3f90ecef35d54 100644 (file)
@@ -444,7 +444,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                ret = git_config_set(argv[0], value);
                if (ret == CONFIG_NOTHING_SET)
                        error("cannot overwrite multiple values with a single value\n"
-                       "       Use a regexp, --add or --set-all to change %s.", argv[0]);
+                       "       Use a regexp, --add or --replace-all to change %s.", argv[0]);
                return ret;
        }
        else if (actions == ACTION_SET_ALL) {
index 0fe638fc45c780e53901b8be22d3b4d715be3c30..387afa75680d813bccd5493e5946600c08679890 100644 (file)
@@ -14,6 +14,7 @@
 #include "log-tree.h"
 #include "builtin.h"
 #include "submodule.h"
+#include "sha1-array.h"
 
 struct blobinfo {
        unsigned char sha1[20];
@@ -169,7 +170,7 @@ static int builtin_diff_combined(struct rev_info *revs,
                                 struct object_array_entry *ent,
                                 int ents)
 {
-       const unsigned char (*parent)[20];
+       struct sha1_array parents = SHA1_ARRAY_INIT;
        int i;
 
        if (argc > 1)
@@ -177,12 +178,11 @@ static int builtin_diff_combined(struct rev_info *revs,
 
        if (!revs->dense_combined_merges && !revs->combine_merges)
                revs->dense_combined_merges = revs->combine_merges = 1;
-       parent = xmalloc(ents * sizeof(*parent));
-       for (i = 0; i < ents; i++)
-               hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
-       diff_tree_combined(parent[0], parent + 1, ents - 1,
+       for (i = 1; i < ents; i++)
+               sha1_array_append(&parents, ent[i].item->sha1);
+       diff_tree_combined(ent[0].item->sha1, &parents,
                           revs->dense_combined_merges, revs);
-       free((void *)parent);
+       sha1_array_clear(&parents);
        return 0;
 }
 
index 9836e6b7ca22e254c06d8d766d510ef43a8cbe90..08fed989a4a611def523aa69a642bc8559bd3f2f 100644 (file)
@@ -25,7 +25,7 @@ static const char *fast_export_usage[] = {
 
 static int progress;
 static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT;
-static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT;
+static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ERROR;
 static int fake_missing_tagger;
 static int use_done_feature;
 static int no_data;
@@ -51,7 +51,7 @@ static int parse_opt_tag_of_filtered_mode(const struct option *opt,
                                          const char *arg, int unset)
 {
        if (unset || !strcmp(arg, "abort"))
-               tag_of_filtered_mode = ABORT;
+               tag_of_filtered_mode = ERROR;
        else if (!strcmp(arg, "drop"))
                tag_of_filtered_mode = DROP;
        else if (!strcmp(arg, "rewrite"))
index 412bd327b5b396c159c768b25be189e00531c38e..a4d3e90a86be3242cd9d35baa712cce79faec649 100644 (file)
@@ -15,7 +15,9 @@ static int transfer_unpack_limit = -1;
 static int fetch_unpack_limit = -1;
 static int unpack_limit = 100;
 static int prefer_ofs_delta = 1;
-static int no_done = 0;
+static int no_done;
+static int fetch_fsck_objects = -1;
+static int transfer_fsck_objects = -1;
 static struct fetch_pack_args args = {
        /* .uploadpack = */ "git-upload-pack",
 };
@@ -544,7 +546,7 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
        for (ref = *refs; ref; ref = next) {
                next = ref->next;
                if (!memcmp(ref->name, "refs/", 5) &&
-                   check_ref_format(ref->name + 5))
+                   check_refname_format(ref->name + 5, 0))
                        ; /* trash */
                else if (args.fetch_all &&
                         (!args.depth || prefixcmp(ref->name, "refs/tags/") )) {
@@ -554,11 +556,16 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
                        continue;
                }
                else {
-                       int order = path_match(ref->name, nr_match, match);
-                       if (order) {
-                               return_refs[order-1] = ref;
-                               continue; /* we will link it later */
+                       int i;
+                       for (i = 0; i < nr_match; i++) {
+                               if (!strcmp(ref->name, match[i])) {
+                                       match[i][0] = '\0';
+                                       return_refs[i] = ref;
+                                       break;
+                               }
                        }
+                       if (i < nr_match)
+                               continue; /* we will link it later */
                }
                free(ref);
        }
@@ -729,11 +736,17 @@ static int get_pack(int xd[2], char **pack_lockfile)
        }
        else {
                *av++ = "unpack-objects";
-               if (args.quiet)
+               if (args.quiet || args.no_progress)
                        *av++ = "-q";
        }
        if (*hdr_arg)
                *av++ = hdr_arg;
+       if (fetch_fsck_objects >= 0
+           ? fetch_fsck_objects
+           : transfer_fsck_objects >= 0
+           ? transfer_fsck_objects
+           : 0)
+               *av++ = "--strict";
        *av++ = NULL;
 
        cmd.in = demux.out;
@@ -853,6 +866,16 @@ static int fetch_pack_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (!strcmp(var, "fetch.fsckobjects")) {
+               fetch_fsck_objects = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (!strcmp(var, "transfer.fsckobjects")) {
+               transfer_fsck_objects = git_config_bool(var, value);
+               return 0;
+       }
+
        return git_default_config(var, value, cb);
 }
 
@@ -958,7 +981,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
                                   args.verbose ? CONNECT_VERBOSE : 0);
        }
 
-       get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
+       get_remote_heads(fd[0], &ref, 0, NULL);
 
        ref = fetch_pack(&args, fd, conn, ref, dest,
                nr_heads, heads, pack_lockfile_ptr);
index 59dfba5180d2373f0e52563f4db928a25708822d..8ec4eae3eb78925de41253264380ea5585b0f091 100644 (file)
@@ -13,6 +13,7 @@
 #include "sigchain.h"
 #include "transport.h"
 #include "submodule.h"
+#include "connected.h"
 
 static const char * const builtin_fetch_usage[] = {
        "git fetch [<options>] [<repository> [<refspec>...]]",
@@ -29,7 +30,7 @@ enum {
 };
 
 static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
-static int progress, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
+static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 static int tags = TAGS_DEFAULT;
 static const char *depth;
 static const char *upload_pack;
@@ -77,7 +78,7 @@ static struct option builtin_fetch_options[] = {
        OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
        OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
                    "allow updating of HEAD ref"),
-       OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
+       OPT_BOOL(0, "progress", &progress, "force progress reporting"),
        OPT_STRING(0, "depth", &depth, "depth",
                   "deepen history of shallow clone"),
        { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "dir",
@@ -354,6 +355,18 @@ static int update_local_ref(struct ref *ref,
        }
 }
 
+static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
+{
+       struct ref **rm = cb_data;
+       struct ref *ref = *rm;
+
+       if (!ref)
+               return -1; /* end of the list */
+       *rm = ref->next;
+       hashcpy(sha1, ref->old_sha1);
+       return 0;
+}
+
 static int store_updated_refs(const char *raw_url, const char *remote_name,
                struct ref *ref_map)
 {
@@ -364,6 +377,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
        const char *what, *kind;
        struct ref *rm;
        char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
+       int want_merge;
 
        fp = fopen(filename, "a");
        if (!fp)
@@ -373,94 +387,114 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                url = transport_anonymize_url(raw_url);
        else
                url = xstrdup("foreign");
-       for (rm = ref_map; rm; rm = rm->next) {
-               struct ref *ref = NULL;
 
-               if (rm->peer_ref) {
-                       ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
-                       strcpy(ref->name, rm->peer_ref->name);
-                       hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
-                       hashcpy(ref->new_sha1, rm->old_sha1);
-                       ref->force = rm->peer_ref->force;
-               }
+       rm = ref_map;
+       if (check_everything_connected(iterate_ref_map, 0, &rm)) {
+               rc = error(_("%s did not send all necessary objects\n"), url);
+               goto abort;
+       }
 
-               commit = lookup_commit_reference_gently(rm->old_sha1, 1);
-               if (!commit)
-                       rm->merge = 0;
+       /*
+        * The first pass writes objects to be merged and then the
+        * second pass writes the rest, in order to allow using
+        * FETCH_HEAD as a refname to refer to the ref to be merged.
+        */
+       for (want_merge = 1; 0 <= want_merge; want_merge--) {
+               for (rm = ref_map; rm; rm = rm->next) {
+                       struct ref *ref = NULL;
+
+                       commit = lookup_commit_reference_gently(rm->old_sha1, 1);
+                       if (!commit)
+                               rm->merge = 0;
+
+                       if (rm->merge != want_merge)
+                               continue;
+
+                       if (rm->peer_ref) {
+                               ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
+                               strcpy(ref->name, rm->peer_ref->name);
+                               hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
+                               hashcpy(ref->new_sha1, rm->old_sha1);
+                               ref->force = rm->peer_ref->force;
+                       }
 
-               if (!strcmp(rm->name, "HEAD")) {
-                       kind = "";
-                       what = "";
-               }
-               else if (!prefixcmp(rm->name, "refs/heads/")) {
-                       kind = "branch";
-                       what = rm->name + 11;
-               }
-               else if (!prefixcmp(rm->name, "refs/tags/")) {
-                       kind = "tag";
-                       what = rm->name + 10;
-               }
-               else if (!prefixcmp(rm->name, "refs/remotes/")) {
-                       kind = "remote-tracking branch";
-                       what = rm->name + 13;
-               }
-               else {
-                       kind = "";
-                       what = rm->name;
-               }
 
-               url_len = strlen(url);
-               for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
-                       ;
-               url_len = i + 1;
-               if (4 < i && !strncmp(".git", url + i - 3, 4))
-                       url_len = i - 3;
-
-               strbuf_reset(&note);
-               if (*what) {
-                       if (*kind)
-                               strbuf_addf(&note, "%s ", kind);
-                       strbuf_addf(&note, "'%s' of ", what);
-               }
-               fprintf(fp, "%s\t%s\t%s",
-                       sha1_to_hex(commit ? commit->object.sha1 :
-                                   rm->old_sha1),
-                       rm->merge ? "" : "not-for-merge",
-                       note.buf);
-               for (i = 0; i < url_len; ++i)
-                       if ('\n' == url[i])
-                               fputs("\\n", fp);
-                       else
-                               fputc(url[i], fp);
-               fputc('\n', fp);
-
-               strbuf_reset(&note);
-               if (ref) {
-                       rc |= update_local_ref(ref, what, &note);
-                       free(ref);
-               } else
-                       strbuf_addf(&note, "* %-*s %-*s -> FETCH_HEAD",
-                                   TRANSPORT_SUMMARY_WIDTH,
-                                   *kind ? kind : "branch",
-                                   REFCOL_WIDTH,
-                                   *what ? what : "HEAD");
-               if (note.len) {
-                       if (verbosity >= 0 && !shown_url) {
-                               fprintf(stderr, _("From %.*s\n"),
-                                               url_len, url);
-                               shown_url = 1;
+                       if (!strcmp(rm->name, "HEAD")) {
+                               kind = "";
+                               what = "";
+                       }
+                       else if (!prefixcmp(rm->name, "refs/heads/")) {
+                               kind = "branch";
+                               what = rm->name + 11;
+                       }
+                       else if (!prefixcmp(rm->name, "refs/tags/")) {
+                               kind = "tag";
+                               what = rm->name + 10;
+                       }
+                       else if (!prefixcmp(rm->name, "refs/remotes/")) {
+                               kind = "remote-tracking branch";
+                               what = rm->name + 13;
+                       }
+                       else {
+                               kind = "";
+                               what = rm->name;
+                       }
+
+                       url_len = strlen(url);
+                       for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
+                               ;
+                       url_len = i + 1;
+                       if (4 < i && !strncmp(".git", url + i - 3, 4))
+                               url_len = i - 3;
+
+                       strbuf_reset(&note);
+                       if (*what) {
+                               if (*kind)
+                                       strbuf_addf(&note, "%s ", kind);
+                               strbuf_addf(&note, "'%s' of ", what);
+                       }
+                       fprintf(fp, "%s\t%s\t%s",
+                               sha1_to_hex(rm->old_sha1),
+                               rm->merge ? "" : "not-for-merge",
+                               note.buf);
+                       for (i = 0; i < url_len; ++i)
+                               if ('\n' == url[i])
+                                       fputs("\\n", fp);
+                               else
+                                       fputc(url[i], fp);
+                       fputc('\n', fp);
+
+                       strbuf_reset(&note);
+                       if (ref) {
+                               rc |= update_local_ref(ref, what, &note);
+                               free(ref);
+                       } else
+                               strbuf_addf(&note, "* %-*s %-*s -> FETCH_HEAD",
+                                           TRANSPORT_SUMMARY_WIDTH,
+                                           *kind ? kind : "branch",
+                                           REFCOL_WIDTH,
+                                           *what ? what : "HEAD");
+                       if (note.len) {
+                               if (verbosity >= 0 && !shown_url) {
+                                       fprintf(stderr, _("From %.*s\n"),
+                                                       url_len, url);
+                                       shown_url = 1;
+                               }
+                               if (verbosity >= 0)
+                                       fprintf(stderr, " %s\n", note.buf);
                        }
-                       if (verbosity >= 0)
-                               fprintf(stderr, " %s\n", note.buf);
                }
        }
-       free(url);
-       fclose(fp);
+
        if (rc & STORE_REF_ERROR_DF_CONFLICT)
                error(_("some local refs could not be updated; try running\n"
                      " 'git remote prune %s' to remove any old, conflicting "
                      "branches"), remote_name);
+
+ abort:
        strbuf_release(&note);
+       free(url);
+       fclose(fp);
        return rc;
 }
 
@@ -468,23 +502,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
  * We would want to bypass the object transfer altogether if
  * everything we are going to fetch already exists and is connected
  * locally.
- *
- * The refs we are going to fetch are in ref_map.  If running
- *
- *  $ git rev-list --objects --stdin --not --all
- *
- * (feeding all the refs in ref_map on its standard input)
- * does not error out, that means everything reachable from the
- * refs we are going to fetch exists and is connected to some of
- * our existing refs.
  */
 static int quickfetch(struct ref *ref_map)
 {
-       struct child_process revlist;
-       struct ref *ref;
-       int err;
-       const char *argv[] = {"rev-list",
-               "--quiet", "--objects", "--stdin", "--not", "--all", NULL};
+       struct ref *rm = ref_map;
 
        /*
         * If we are deepening a shallow clone we already have these
@@ -495,47 +516,7 @@ static int quickfetch(struct ref *ref_map)
         */
        if (depth)
                return -1;
-
-       if (!ref_map)
-               return 0;
-
-       memset(&revlist, 0, sizeof(revlist));
-       revlist.argv = argv;
-       revlist.git_cmd = 1;
-       revlist.no_stdout = 1;
-       revlist.no_stderr = 1;
-       revlist.in = -1;
-
-       err = start_command(&revlist);
-       if (err) {
-               error(_("could not run rev-list"));
-               return err;
-       }
-
-       /*
-        * If rev-list --stdin encounters an unknown commit, it terminates,
-        * which will cause SIGPIPE in the write loop below.
-        */
-       sigchain_push(SIGPIPE, SIG_IGN);
-
-       for (ref = ref_map; ref; ref = ref->next) {
-               if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 ||
-                   write_str_in_full(revlist.in, "\n") < 0) {
-                       if (errno != EPIPE && errno != EINVAL)
-                               error(_("failed write to rev-list: %s"), strerror(errno));
-                       err = -1;
-                       break;
-               }
-       }
-
-       if (close(revlist.in)) {
-               error(_("failed to close rev-list's stdin: %s"), strerror(errno));
-               err = -1;
-       }
-
-       sigchain_pop(SIGPIPE);
-
-       return finish_command(&revlist) || err;
+       return check_everything_connected(iterate_ref_map, 1, &rm);
 }
 
 static int fetch_refs(struct transport *transport, struct ref *ref_map)
index 7e2f22589dcb14d5ba95ce1331ef816a458533d0..c81a7fef2680620d521e118d60e8c59893d59234 100644 (file)
@@ -5,32 +5,43 @@
 #include "revision.h"
 #include "tag.h"
 #include "string-list.h"
+#include "branch.h"
+#include "fmt-merge-msg.h"
+#include "gpg-interface.h"
 
 static const char * const fmt_merge_msg_usage[] = {
        "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]",
        NULL
 };
 
-static int shortlog_len;
+static int use_branch_desc;
 
-static int fmt_merge_msg_config(const char *key, const char *value, void *cb)
+int fmt_merge_msg_config(const char *key, const char *value, void *cb)
 {
        if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
                int is_bool;
-               shortlog_len = git_config_bool_or_int(key, value, &is_bool);
-               if (!is_bool && shortlog_len < 0)
+               merge_log_config = git_config_bool_or_int(key, value, &is_bool);
+               if (!is_bool && merge_log_config < 0)
                        return error("%s: negative length %s", key, value);
-               if (is_bool && shortlog_len)
-                       shortlog_len = DEFAULT_MERGE_LOG_LEN;
+               if (is_bool && merge_log_config)
+                       merge_log_config = DEFAULT_MERGE_LOG_LEN;
+       } else if (!strcmp(key, "merge.branchdesc")) {
+               use_branch_desc = git_config_bool(key, value);
        }
        return 0;
 }
 
+/* merge data per repository where the merged tips came from */
 struct src_data {
        struct string_list branch, tag, r_branch, generic;
        int head_status;
 };
 
+struct origin_data {
+       unsigned char sha1[20];
+       unsigned is_local_branch:1;
+};
+
 static void init_src_data(struct src_data *data)
 {
        data->branch.strdup_strings = 1;
@@ -45,7 +56,7 @@ static struct string_list origins = STRING_LIST_INIT_DUP;
 static int handle_line(char *line)
 {
        int i, len = strlen(line);
-       unsigned char *sha1;
+       struct origin_data *origin_data;
        char *src, *origin;
        struct src_data *src_data;
        struct string_list_item *item;
@@ -61,16 +72,23 @@ static int handle_line(char *line)
                return 2;
 
        line[40] = 0;
-       sha1 = xmalloc(20);
-       i = get_sha1(line, sha1);
+       origin_data = xcalloc(1, sizeof(struct origin_data));
+       i = get_sha1(line, origin_data->sha1);
        line[40] = '\t';
-       if (i)
+       if (i) {
+               free(origin_data);
                return 3;
+       }
 
        if (line[len - 1] == '\n')
                line[len - 1] = 0;
        line += 42;
 
+       /*
+        * At this point, line points at the beginning of comment e.g.
+        * "branch 'frotz' of git://that/repository.git".
+        * Find the repository name and point it with src.
+        */
        src = strstr(line, " of ");
        if (src) {
                *src = 0;
@@ -93,6 +111,7 @@ static int handle_line(char *line)
                origin = src;
                src_data->head_status |= 1;
        } else if (!prefixcmp(line, "branch ")) {
+               origin_data->is_local_branch = 1;
                origin = line + 7;
                string_list_append(&src_data->branch, origin);
                src_data->head_status |= 2;
@@ -119,7 +138,9 @@ static int handle_line(char *line)
                sprintf(new_origin, "%s of %s", origin, src);
                origin = new_origin;
        }
-       string_list_append(&origins, origin)->util = sha1;
+       if (strcmp(".", src))
+               origin_data->is_local_branch = 0;
+       string_list_append(&origins, origin)->util = origin_data;
        return 0;
 }
 
@@ -140,9 +161,30 @@ static void print_joined(const char *singular, const char *plural,
        }
 }
 
-static void shortlog(const char *name, unsigned char *sha1,
-               struct commit *head, struct rev_info *rev, int limit,
-               struct strbuf *out)
+static void add_branch_desc(struct strbuf *out, const char *name)
+{
+       struct strbuf desc = STRBUF_INIT;
+
+       if (!read_branch_desc(&desc, name)) {
+               const char *bp = desc.buf;
+               while (*bp) {
+                       const char *ep = strchrnul(bp, '\n');
+                       if (*ep)
+                               ep++;
+                       strbuf_addf(out, "  : %.*s", (int)(ep - bp), bp);
+                       bp = ep;
+               }
+               if (out->buf[out->len - 1] != '\n')
+                       strbuf_addch(out, '\n');
+       }
+       strbuf_release(&desc);
+}
+
+static void shortlog(const char *name,
+                    struct origin_data *origin_data,
+                    struct commit *head,
+                    struct rev_info *rev, int limit,
+                    struct strbuf *out)
 {
        int i, count = 0;
        struct commit *commit;
@@ -150,6 +192,7 @@ static void shortlog(const char *name, unsigned char *sha1,
        struct string_list subjects = STRING_LIST_INIT_DUP;
        int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
        struct strbuf sb = STRBUF_INIT;
+       const unsigned char *sha1 = origin_data->sha1;
 
        branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
        if (!branch || branch->type != OBJ_COMMIT)
@@ -188,6 +231,9 @@ static void shortlog(const char *name, unsigned char *sha1,
        else
                strbuf_addf(out, "\n* %s:\n", name);
 
+       if (origin_data->is_local_branch && use_branch_desc)
+               add_branch_desc(out, name);
+
        for (i = 0; i < subjects.nr; i++)
                if (i >= limit)
                        strbuf_addf(out, "  ...\n");
@@ -203,7 +249,7 @@ static void shortlog(const char *name, unsigned char *sha1,
        string_list_clear(&subjects, 0);
 }
 
-static void do_fmt_merge_msg_title(struct strbuf *out,
+static void fmt_merge_msg_title(struct strbuf *out,
        const char *current_branch) {
        int i = 0;
        char *sep = "";
@@ -256,14 +302,81 @@ static void do_fmt_merge_msg_title(struct strbuf *out,
                strbuf_addf(out, " into %s\n", current_branch);
 }
 
-static int do_fmt_merge_msg(int merge_title, struct strbuf *in,
-       struct strbuf *out, int shortlog_len) {
+static void fmt_tag_signature(struct strbuf *tagbuf,
+                             struct strbuf *sig,
+                             const char *buf,
+                             unsigned long len)
+{
+       const char *tag_body = strstr(buf, "\n\n");
+       if (tag_body) {
+               tag_body += 2;
+               strbuf_add(tagbuf, tag_body, buf + len - tag_body);
+       }
+       strbuf_complete_line(tagbuf);
+       strbuf_add_lines(tagbuf, "# ", sig->buf, sig->len);
+}
+
+static void fmt_merge_msg_sigs(struct strbuf *out)
+{
+       int i, tag_number = 0, first_tag = 0;
+       struct strbuf tagbuf = STRBUF_INIT;
+
+       for (i = 0; i < origins.nr; i++) {
+               unsigned char *sha1 = origins.items[i].util;
+               enum object_type type;
+               unsigned long size, len;
+               char *buf = read_sha1_file(sha1, &type, &size);
+               struct strbuf sig = STRBUF_INIT;
+
+               if (!buf || type != OBJ_TAG)
+                       goto next;
+               len = parse_signature(buf, size);
+
+               if (size == len)
+                       ; /* merely annotated */
+               else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig)) {
+                       if (!sig.len)
+                               strbuf_addstr(&sig, "gpg verification failed.\n");
+               }
+
+               if (!tag_number++) {
+                       fmt_tag_signature(&tagbuf, &sig, buf, len);
+                       first_tag = i;
+               } else {
+                       if (tag_number == 2) {
+                               struct strbuf tagline = STRBUF_INIT;
+                               strbuf_addf(&tagline, "\n# %s\n",
+                                           origins.items[first_tag].string);
+                               strbuf_insert(&tagbuf, 0, tagline.buf,
+                                             tagline.len);
+                               strbuf_release(&tagline);
+                       }
+                       strbuf_addf(&tagbuf, "\n# %s\n",
+                                   origins.items[i].string);
+                       fmt_tag_signature(&tagbuf, &sig, buf, len);
+               }
+               strbuf_release(&sig);
+       next:
+               free(buf);
+       }
+       if (tagbuf.len) {
+               strbuf_addch(out, '\n');
+               strbuf_addbuf(out, &tagbuf);
+       }
+       strbuf_release(&tagbuf);
+}
+
+int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
+                 struct fmt_merge_msg_opts *opts)
+{
        int i = 0, pos = 0;
        unsigned char head_sha1[20];
        const char *current_branch;
+       void *current_branch_to_free;
 
        /* get current branch */
-       current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
+       current_branch = current_branch_to_free =
+               resolve_refdup("HEAD", head_sha1, 1, NULL);
        if (!current_branch)
                die("No current branch");
        if (!prefixcmp(current_branch, "refs/heads/"))
@@ -283,13 +396,13 @@ static int do_fmt_merge_msg(int merge_title, struct strbuf *in,
                        die ("Error in line %d: %.*s", i, len, p);
        }
 
-       if (!srcs.nr)
-               return 0;
+       if (opts->add_title && srcs.nr)
+               fmt_merge_msg_title(out, current_branch);
 
-       if (merge_title)
-               do_fmt_merge_msg_title(out, current_branch);
+       if (origins.nr)
+               fmt_merge_msg_sigs(out);
 
-       if (shortlog_len) {
+       if (opts->shortlog_len) {
                struct commit *head;
                struct rev_info rev;
 
@@ -303,21 +416,21 @@ static int do_fmt_merge_msg(int merge_title, struct strbuf *in,
                        strbuf_addch(out, '\n');
 
                for (i = 0; i < origins.nr; i++)
-                       shortlog(origins.items[i].string, origins.items[i].util,
-                                       head, &rev, shortlog_len, out);
+                       shortlog(origins.items[i].string,
+                                origins.items[i].util,
+                                head, &rev, opts->shortlog_len, out);
        }
-       return 0;
-}
 
-int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
-                 int merge_title, int shortlog_len) {
-       return do_fmt_merge_msg(merge_title, in, out, shortlog_len);
+       strbuf_complete_line(out);
+       free(current_branch_to_free);
+       return 0;
 }
 
 int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
 {
        const char *inpath = NULL;
        const char *message = NULL;
+       int shortlog_len = -1;
        struct option options[] = {
                { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
                  "populate log with at most <n> entries from shortlog",
@@ -335,20 +448,15 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
        FILE *in = stdin;
        struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
        int ret;
+       struct fmt_merge_msg_opts opts;
 
        git_config(fmt_merge_msg_config, NULL);
        argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage,
                             0);
        if (argc > 0)
                usage_with_options(fmt_merge_msg_usage, options);
-       if (message && !shortlog_len) {
-               char nl = '\n';
-               write_in_full(STDOUT_FILENO, message, strlen(message));
-               write_in_full(STDOUT_FILENO, &nl, 1);
-               return 0;
-       }
        if (shortlog_len < 0)
-               die("Negative --log=%d", shortlog_len);
+               shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
 
        if (inpath && strcmp(inpath, "-")) {
                in = fopen(inpath, "r");
@@ -361,10 +469,12 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
 
        if (message)
                strbuf_addstr(&output, message);
-       ret = fmt_merge_msg(&input, &output,
-                           message ? 0 : 1,
-                           shortlog_len);
 
+       memset(&opts, 0, sizeof(opts));
+       opts.add_title = !message;
+       opts.shortlog_len = shortlog_len;
+
+       ret = fmt_merge_msg(&input, &output, &opts);
        if (ret)
                return ret;
        write_in_full(STDOUT_FILENO, output.buf, output.len);
index 89e75c6894e6fdb2abbe464d825f5c7186a14141..b01d76a24323e86e9c9cbf1cd3adc9c0d1b2c6d8 100644 (file)
@@ -69,6 +69,9 @@ static struct {
        { "subject" },
        { "body" },
        { "contents" },
+       { "contents:subject" },
+       { "contents:body" },
+       { "contents:signature" },
        { "upstream" },
        { "symref" },
        { "flag" },
@@ -361,6 +364,18 @@ static const char *copy_email(const char *buf)
        return xmemdupz(email, eoemail + 1 - email);
 }
 
+static char *copy_subject(const char *buf, unsigned long len)
+{
+       char *r = xmemdupz(buf, len);
+       int i;
+
+       for (i = 0; i < len; i++)
+               if (r[i] == '\n')
+                       r[i] = ' ';
+
+       return r;
+}
+
 static void grab_date(const char *buf, struct atom_value *v, const char *atomname)
 {
        const char *eoemail = strstr(buf, "> ");
@@ -458,38 +473,56 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru
        }
 }
 
-static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+static void find_subpos(const char *buf, unsigned long sz,
+                       const char **sub, unsigned long *sublen,
+                       const char **body, unsigned long *bodylen,
+                       unsigned long *nonsiglen,
+                       const char **sig, unsigned long *siglen)
 {
-       while (*buf) {
-               const char *eol = strchr(buf, '\n');
-               if (!eol)
-                       return;
-               if (eol[1] == '\n') {
-                       buf = eol + 1;
-                       break; /* found end of header */
-               }
-               buf = eol + 1;
+       const char *eol;
+       /* skip past header until we hit empty line */
+       while (*buf && *buf != '\n') {
+               eol = strchrnul(buf, '\n');
+               if (*eol)
+                       eol++;
+               buf = eol;
        }
+       /* skip any empty lines */
        while (*buf == '\n')
                buf++;
-       if (!*buf)
-               return;
-       *sub = buf; /* first non-empty line */
-       buf = strchr(buf, '\n');
-       if (!buf) {
-               *body = "";
-               return; /* no body */
+
+       /* parse signature first; we might not even have a subject line */
+       *sig = buf + parse_signature(buf, strlen(buf));
+       *siglen = strlen(*sig);
+
+       /* subject is first non-empty line */
+       *sub = buf;
+       /* subject goes to first empty line */
+       while (buf < *sig && *buf && *buf != '\n') {
+               eol = strchrnul(buf, '\n');
+               if (*eol)
+                       eol++;
+               buf = eol;
        }
+       *sublen = buf - *sub;
+       /* drop trailing newline, if present */
+       if (*sublen && (*sub)[*sublen - 1] == '\n')
+               *sublen -= 1;
+
+       /* skip any empty lines */
        while (*buf == '\n')
-               buf++; /* skip blank between subject and body */
+               buf++;
        *body = buf;
+       *bodylen = strlen(buf);
+       *nonsiglen = *sig - buf;
 }
 
 /* See grab_values */
 static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
 {
        int i;
-       const char *subpos = NULL, *bodypos = NULL;
+       const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
+       unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;
 
        for (i = 0; i < used_atom_cnt; i++) {
                const char *name = used_atom[i];
@@ -500,17 +533,27 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
                        name++;
                if (strcmp(name, "subject") &&
                    strcmp(name, "body") &&
-                   strcmp(name, "contents"))
+                   strcmp(name, "contents") &&
+                   strcmp(name, "contents:subject") &&
+                   strcmp(name, "contents:body") &&
+                   strcmp(name, "contents:signature"))
                        continue;
                if (!subpos)
-                       find_subpos(buf, sz, &subpos, &bodypos);
-               if (!subpos)
-                       return;
+                       find_subpos(buf, sz,
+                                   &subpos, &sublen,
+                                   &bodypos, &bodylen, &nonsiglen,
+                                   &sigpos, &siglen);
 
                if (!strcmp(name, "subject"))
-                       v->s = copy_line(subpos);
+                       v->s = copy_subject(subpos, sublen);
+               else if (!strcmp(name, "contents:subject"))
+                       v->s = copy_subject(subpos, sublen);
                else if (!strcmp(name, "body"))
-                       v->s = xstrdup(bodypos);
+                       v->s = xmemdupz(bodypos, bodylen);
+               else if (!strcmp(name, "contents:body"))
+                       v->s = xmemdupz(bodypos, nonsiglen);
+               else if (!strcmp(name, "contents:signature"))
+                       v->s = xmemdupz(sigpos, siglen);
                else if (!strcmp(name, "contents"))
                        v->s = xstrdup(subpos);
        }
@@ -585,11 +628,8 @@ static void populate_value(struct refinfo *ref)
 
        if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
                unsigned char unused1[20];
-               const char *symref;
-               symref = resolve_ref(ref->refname, unused1, 1, NULL);
-               if (symref)
-                       ref->symref = xstrdup(symref);
-               else
+               ref->symref = resolve_refdup(ref->refname, unused1, 1, NULL);
+               if (!ref->symref)
                        ref->symref = "";
        }
 
index df1a88b51ae7773a15276d144113c0002fddb1cd..8c479a791b792ebda334a7e3816523af3802b5bc 100644 (file)
@@ -11,6 +11,7 @@
 #include "fsck.h"
 #include "parse-options.h"
 #include "dir.h"
+#include "progress.h"
 
 #define REACHABLE 0x0001
 #define SEEN      0x0002
@@ -27,8 +28,10 @@ static const char *head_points_at;
 static int errors_found;
 static int write_lost_and_found;
 static int verbose;
+static int show_progress = -1;
 #define ERROR_OBJECT 01
 #define ERROR_REACHABLE 02
+#define ERROR_PACK 04
 
 #ifdef NO_D_INO_IN_DIRENT
 #define SORT_DIRENT 0
@@ -137,7 +140,11 @@ static int traverse_one_object(struct object *obj)
 
 static int traverse_reachable(void)
 {
+       struct progress *progress = NULL;
+       unsigned int nr = 0;
        int result = 0;
+       if (show_progress)
+               progress = start_progress_delay("Checking connectivity", 0, 0, 2);
        while (pending.nr) {
                struct object_array_entry *entry;
                struct object *obj;
@@ -145,7 +152,9 @@ static int traverse_reachable(void)
                entry = pending.objects + --pending.nr;
                obj = entry->item;
                result |= traverse_one_object(obj);
+               display_progress(progress, ++nr);
        }
+       stop_progress(&progress);
        return !!result;
 }
 
@@ -281,14 +290,8 @@ static void check_connectivity(void)
        }
 }
 
-static int fsck_sha1(const unsigned char *sha1)
+static int fsck_obj(struct object *obj)
 {
-       struct object *obj = parse_object(sha1);
-       if (!obj) {
-               errors_found |= ERROR_OBJECT;
-               return error("%s: object corrupt or missing",
-                            sha1_to_hex(sha1));
-       }
        if (obj->flags & SEEN)
                return 0;
        obj->flags |= SEEN;
@@ -331,6 +334,29 @@ static int fsck_sha1(const unsigned char *sha1)
        return 0;
 }
 
+static int fsck_sha1(const unsigned char *sha1)
+{
+       struct object *obj = parse_object(sha1);
+       if (!obj) {
+               errors_found |= ERROR_OBJECT;
+               return error("%s: object corrupt or missing",
+                            sha1_to_hex(sha1));
+       }
+       return fsck_obj(obj);
+}
+
+static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type,
+                          unsigned long size, void *buffer, int *eaten)
+{
+       struct object *obj;
+       obj = parse_object_buffer(sha1, type, size, buffer, eaten);
+       if (!obj) {
+               errors_found |= ERROR_OBJECT;
+               return error("%s: object corrupt or missing", sha1_to_hex(sha1));
+       }
+       return fsck_obj(obj);
+}
+
 /*
  * This is the sorting chunk size: make it reasonably
  * big so that we can sort well..
@@ -512,15 +538,20 @@ static void get_default_heads(void)
 static void fsck_object_dir(const char *path)
 {
        int i;
+       struct progress *progress = NULL;
 
        if (verbose)
                fprintf(stderr, "Checking object directory\n");
 
+       if (show_progress)
+               progress = start_progress("Checking object directories", 256);
        for (i = 0; i < 256; i++) {
                static char dir[4096];
                sprintf(dir, "%s/%02x", path, i);
                fsck_dir(i, dir);
+               display_progress(progress, i+1);
        }
+       stop_progress(&progress);
        fsck_sha1_list();
 }
 
@@ -532,7 +563,7 @@ static int fsck_head_link(void)
        if (verbose)
                fprintf(stderr, "Checking HEAD link\n");
 
-       head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag);
+       head_points_at = resolve_ref_unsafe("HEAD", head_sha1, 0, &flag);
        if (!head_points_at)
                return error("Invalid HEAD");
        if (!strcmp(head_points_at, "HEAD"))
@@ -591,6 +622,7 @@ static struct option fsck_opts[] = {
        OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"),
        OPT_BOOLEAN(0, "lost-found", &write_lost_and_found,
                                "write dangling objects in .git/lost-found"),
+       OPT_BOOL(0, "progress", &show_progress, "show progress"),
        OPT_END(),
 };
 
@@ -603,6 +635,12 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
        read_replace_refs = 0;
 
        argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);
+
+       if (show_progress == -1)
+               show_progress = isatty(2);
+       if (verbose)
+               show_progress = 0;
+
        if (write_lost_and_found) {
                check_full = 1;
                include_reflogs = 0;
@@ -622,20 +660,28 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
 
        if (check_full) {
                struct packed_git *p;
+               uint32_t total = 0, count = 0;
+               struct progress *progress = NULL;
 
                prepare_packed_git();
-               for (p = packed_git; p; p = p->next)
-                       /* verify gives error messages itself */
-                       verify_pack(p);
 
+               if (show_progress) {
+                       for (p = packed_git; p; p = p->next) {
+                               if (open_pack_index(p))
+                                       continue;
+                               total += p->num_objects;
+                       }
+
+                       progress = start_progress("Checking objects", total);
+               }
                for (p = packed_git; p; p = p->next) {
-                       uint32_t j, num;
-                       if (open_pack_index(p))
-                               continue;
-                       num = p->num_objects;
-                       for (j = 0; j < num; j++)
-                               fsck_sha1(nth_packed_object_sha1(p, j));
+                       /* verify gives error messages itself */
+                       if (verify_pack(p, fsck_obj_buffer,
+                                       progress, count))
+                               errors_found |= ERROR_PACK;
+                       count += p->num_objects;
                }
+               stop_progress(&progress);
        }
 
        heads = 0;
index 0498094711d1addd40f526f0c76dd8ddb76ef550..271376d82b4391318fda9d5cfd5a0764d3768117 100644 (file)
@@ -32,7 +32,7 @@ static const char *prune_expire = "2.weeks.ago";
 static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL};
 static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL};
 static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL};
-static const char *argv_prune[] = {"prune", "--expire", NULL, NULL};
+static const char *argv_prune[] = {"prune", "--expire", NULL, NULL, NULL};
 static const char *argv_rerere[] = {"rerere", "gc", NULL};
 
 static int gc_config(const char *var, const char *value, void *cb)
@@ -243,6 +243,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
 
        if (prune_expire) {
                argv_prune[2] = prune_expire;
+               if (quiet)
+                       argv_prune[3] = "--no-progress";
                if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
                        return error(FAILED_RUN, argv_prune[0]);
        }
index a286692e467710d92346ab6900e98f1126cb967d..9fc3e95cc610c9df7d2b17eae59ac2c161443982 100644 (file)
@@ -17,7 +17,6 @@
 #include "grep.h"
 #include "quote.h"
 #include "dir.h"
-#include "thread-utils.h"
 
 static char const * const grep_usage[] = {
        "git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]",
@@ -30,25 +29,12 @@ static int use_threads = 1;
 #define THREADS 8
 static pthread_t threads[THREADS];
 
-static void *load_sha1(const unsigned char *sha1, unsigned long *size,
-                      const char *name);
-static void *load_file(const char *filename, size_t *sz);
-
-enum work_type {WORK_SHA1, WORK_FILE};
-
 /* We use one producer thread and THREADS consumer
  * threads. The producer adds struct work_items to 'todo' and the
  * consumers pick work items from the same array.
  */
 struct work_item {
-       enum work_type type;
-       char *name;
-
-       /* if type == WORK_SHA1, then 'identifier' is a SHA1,
-        * otherwise type == WORK_FILE, and 'identifier' is a NUL
-        * terminated filename.
-        */
-       void *identifier;
+       struct grep_source source;
        char done;
        struct strbuf out;
 };
@@ -74,13 +60,17 @@ static int all_work_added;
 /* This lock protects all the variables above. */
 static pthread_mutex_t grep_mutex;
 
-/* Used to serialize calls to read_sha1_file. */
-static pthread_mutex_t read_sha1_mutex;
+static inline void grep_lock(void)
+{
+       if (use_threads)
+               pthread_mutex_lock(&grep_mutex);
+}
 
-#define grep_lock() pthread_mutex_lock(&grep_mutex)
-#define grep_unlock() pthread_mutex_unlock(&grep_mutex)
-#define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex)
-#define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex)
+static inline void grep_unlock(void)
+{
+       if (use_threads)
+               pthread_mutex_unlock(&grep_mutex);
+}
 
 /* Signalled when a new work_item is added to todo. */
 static pthread_cond_t cond_add;
@@ -95,7 +85,8 @@ static pthread_cond_t cond_result;
 
 static int skip_first_line;
 
-static void add_work(enum work_type type, char *name, void *id)
+static void add_work(struct grep_opt *opt, enum grep_source_type type,
+                    const char *name, const void *id)
 {
        grep_lock();
 
@@ -103,9 +94,9 @@ static void add_work(enum work_type type, char *name, void *id)
                pthread_cond_wait(&cond_write, &grep_mutex);
        }
 
-       todo[todo_end].type = type;
-       todo[todo_end].name = name;
-       todo[todo_end].identifier = id;
+       grep_source_init(&todo[todo_end].source, type, name, id);
+       if (opt->binary != GREP_BINARY_TEXT)
+               grep_source_load_driver(&todo[todo_end].source);
        todo[todo_end].done = 0;
        strbuf_reset(&todo[todo_end].out);
        todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
@@ -133,21 +124,6 @@ static struct work_item *get_work(void)
        return ret;
 }
 
-static void grep_sha1_async(struct grep_opt *opt, char *name,
-                           const unsigned char *sha1)
-{
-       unsigned char *s;
-       s = xmalloc(20);
-       memcpy(s, sha1, 20);
-       add_work(WORK_SHA1, name, s);
-}
-
-static void grep_file_async(struct grep_opt *opt, char *name,
-                           const char *filename)
-{
-       add_work(WORK_FILE, name, xstrdup(filename));
-}
-
 static void work_done(struct work_item *w)
 {
        int old_done;
@@ -174,8 +150,7 @@ static void work_done(struct work_item *w)
 
                        write_or_die(1, p, len);
                }
-               free(w->name);
-               free(w->identifier);
+               grep_source_clear(&w->source);
        }
 
        if (old_done != todo_done)
@@ -198,25 +173,8 @@ static void *run(void *arg)
                        break;
 
                opt->output_priv = w;
-               if (w->type == WORK_SHA1) {
-                       unsigned long sz;
-                       void* data = load_sha1(w->identifier, &sz, w->name);
-
-                       if (data) {
-                               hit |= grep_buffer(opt, w->name, data, sz);
-                               free(data);
-                       }
-               } else if (w->type == WORK_FILE) {
-                       size_t sz;
-                       void* data = load_file(w->identifier, &sz);
-                       if (data) {
-                               hit |= grep_buffer(opt, w->name, data, sz);
-                               free(data);
-                       }
-               } else {
-                       assert(0);
-               }
-
+               hit |= grep_source(opt, &w->source);
+               grep_source_clear_data(&w->source);
                work_done(w);
        }
        free_grep_patterns(arg);
@@ -236,10 +194,12 @@ static void start_threads(struct grep_opt *opt)
        int i;
 
        pthread_mutex_init(&grep_mutex, NULL);
-       pthread_mutex_init(&read_sha1_mutex, NULL);
+       pthread_mutex_init(&grep_read_mutex, NULL);
+       pthread_mutex_init(&grep_attr_mutex, NULL);
        pthread_cond_init(&cond_add, NULL);
        pthread_cond_init(&cond_write, NULL);
        pthread_cond_init(&cond_result, NULL);
+       grep_use_locks = 1;
 
        for (i = 0; i < ARRAY_SIZE(todo); i++) {
                strbuf_init(&todo[i].out, 0);
@@ -283,16 +243,16 @@ static int wait_all(void)
        }
 
        pthread_mutex_destroy(&grep_mutex);
-       pthread_mutex_destroy(&read_sha1_mutex);
+       pthread_mutex_destroy(&grep_read_mutex);
+       pthread_mutex_destroy(&grep_attr_mutex);
        pthread_cond_destroy(&cond_add);
        pthread_cond_destroy(&cond_write);
        pthread_cond_destroy(&cond_result);
+       grep_use_locks = 0;
 
        return hit;
 }
 #else /* !NO_PTHREADS */
-#define read_sha1_lock()
-#define read_sha1_unlock()
 
 static int wait_all(void)
 {
@@ -354,25 +314,9 @@ static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type
 {
        void *data;
 
-       if (use_threads) {
-               read_sha1_lock();
-               data = read_sha1_file(sha1, type, size);
-               read_sha1_unlock();
-       } else {
-               data = read_sha1_file(sha1, type, size);
-       }
-       return data;
-}
-
-static void *load_sha1(const unsigned char *sha1, unsigned long *size,
-                      const char *name)
-{
-       enum object_type type;
-       void *data = lock_and_read_sha1_file(sha1, &type, size);
-
-       if (!data)
-               error(_("'%s': unable to read %s"), name, sha1_to_hex(sha1));
-
+       grep_read_lock();
+       data = read_sha1_file(sha1, type, size);
+       grep_read_unlock();
        return data;
 }
 
@@ -380,7 +324,6 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
                     const char *filename, int tree_name_len)
 {
        struct strbuf pathbuf = STRBUF_INIT;
-       char *name;
 
        if (opt->relative && opt->prefix_length) {
                quote_path_relative(filename + tree_name_len, -1, &pathbuf,
@@ -390,87 +333,51 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
                strbuf_addstr(&pathbuf, filename);
        }
 
-       name = strbuf_detach(&pathbuf, NULL);
-
 #ifndef NO_PTHREADS
        if (use_threads) {
-               grep_sha1_async(opt, name, sha1);
+               add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, sha1);
+               strbuf_release(&pathbuf);
                return 0;
        } else
 #endif
        {
+               struct grep_source gs;
                int hit;
-               unsigned long sz;
-               void *data = load_sha1(sha1, &sz, name);
-               if (!data)
-                       hit = 0;
-               else
-                       hit = grep_buffer(opt, name, data, sz);
-
-               free(data);
-               free(name);
-               return hit;
-       }
-}
 
-static void *load_file(const char *filename, size_t *sz)
-{
-       struct stat st;
-       char *data;
-       int i;
+               grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, sha1);
+               strbuf_release(&pathbuf);
+               hit = grep_source(opt, &gs);
 
-       if (lstat(filename, &st) < 0) {
-       err_ret:
-               if (errno != ENOENT)
-                       error(_("'%s': %s"), filename, strerror(errno));
-               return NULL;
-       }
-       if (!S_ISREG(st.st_mode))
-               return NULL;
-       *sz = xsize_t(st.st_size);
-       i = open(filename, O_RDONLY);
-       if (i < 0)
-               goto err_ret;
-       data = xmalloc(*sz + 1);
-       if (st.st_size != read_in_full(i, data, *sz)) {
-               error(_("'%s': short read %s"), filename, strerror(errno));
-               close(i);
-               free(data);
-               return NULL;
+               grep_source_clear(&gs);
+               return hit;
        }
-       close(i);
-       data[*sz] = 0;
-       return data;
 }
 
 static int grep_file(struct grep_opt *opt, const char *filename)
 {
        struct strbuf buf = STRBUF_INIT;
-       char *name;
 
        if (opt->relative && opt->prefix_length)
                quote_path_relative(filename, -1, &buf, opt->prefix);
        else
                strbuf_addstr(&buf, filename);
-       name = strbuf_detach(&buf, NULL);
 
 #ifndef NO_PTHREADS
        if (use_threads) {
-               grep_file_async(opt, name, filename);
+               add_work(opt, GREP_SOURCE_FILE, buf.buf, filename);
+               strbuf_release(&buf);
                return 0;
        } else
 #endif
        {
+               struct grep_source gs;
                int hit;
-               size_t sz;
-               void *data = load_file(filename, &sz);
-               if (!data)
-                       hit = 0;
-               else
-                       hit = grep_buffer(opt, name, data, sz);
 
-               free(data);
-               free(name);
+               grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename);
+               strbuf_release(&buf);
+               hit = grep_source(opt, &gs);
+
+               grep_source_clear(&gs);
                return hit;
        }
 }
@@ -542,18 +449,19 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int
 static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
                     struct tree_desc *tree, struct strbuf *base, int tn_len)
 {
-       int hit = 0, match = 0;
+       int hit = 0;
+       enum interesting match = entry_not_interesting;
        struct name_entry entry;
        int old_baselen = base->len;
 
        while (tree_entry(tree, &entry)) {
-               int te_len = tree_entry_len(entry.path, entry.sha1);
+               int te_len = tree_entry_len(&entry);
 
-               if (match != 2) {
+               if (match != all_entries_interesting) {
                        match = tree_entry_interesting(&entry, base, tn_len, pathspec);
-                       if (match < 0)
+                       if (match == all_entries_not_interesting)
                                break;
-                       if (match == 0)
+                       if (match == entry_not_interesting)
                                continue;
                }
 
@@ -598,10 +506,10 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
                struct strbuf base;
                int hit, len;
 
-               read_sha1_lock();
+               grep_read_lock();
                data = read_object_with_reference(obj->sha1, tree_type,
                                                  &size, NULL);
-               read_sha1_unlock();
+               grep_read_unlock();
 
                if (!data)
                        die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1));
@@ -640,13 +548,15 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
        return hit;
 }
 
-static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec)
+static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
+                         int exc_std)
 {
        struct dir_struct dir;
        int i, hit = 0;
 
        memset(&dir, 0, sizeof(dir));
-       setup_standard_excludes(&dir);
+       if (exc_std)
+               setup_standard_excludes(&dir);
 
        fill_directory(&dir, pathspec->raw);
        for (i = 0; i < dir.nr; i++) {
@@ -753,7 +663,7 @@ static int help_callback(const struct option *opt, const char *arg, int unset)
 int cmd_grep(int argc, const char **argv, const char *prefix)
 {
        int hit = 0;
-       int cached = 0;
+       int cached = 0, untracked = 0, opt_exclude = -1;
        int seen_dashdash = 0;
        int external_grep_allowed__ignored;
        const char *show_in_pager = NULL, *default_pager = "dummy";
@@ -777,8 +687,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        struct option options[] = {
                OPT_BOOLEAN(0, "cached", &cached,
                        "search in index instead of in the work tree"),
-               OPT_BOOLEAN(0, "index", &use_index,
-                       "--no-index finds in contents not managed by git"),
+               { OPTION_BOOLEAN, 0, "index", &use_index, NULL,
+                       "finds in contents not managed by git",
+                       PARSE_OPT_NOARG | PARSE_OPT_NEGHELP },
+               OPT_BOOLEAN(0, "untracked", &untracked,
+                       "search in both tracked and untracked files"),
+               OPT_SET_INT(0, "exclude-standard", &opt_exclude,
+                           "search also in ignored files", 1),
                OPT_GROUP(""),
                OPT_BOOLEAN('v', "invert-match", &opt.invert,
                        "show non-matching lines"),
@@ -978,20 +893,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        if (!opt.fixed && opt.ignore_case)
                opt.regflags |= REG_ICASE;
 
-#ifndef NO_PTHREADS
-       if (online_cpus() == 1 || !grep_threads_ok(&opt))
-               use_threads = 0;
-
-       if (use_threads) {
-               if (opt.pre_context || opt.post_context || opt.file_break ||
-                   opt.funcbody)
-                       skip_first_line = 1;
-               start_threads(&opt);
-       }
-#else
-       use_threads = 0;
-#endif
-
        compile_grep_patterns(&opt);
 
        /* Check revs and then paths */
@@ -1013,6 +914,23 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                break;
        }
 
+#ifndef NO_PTHREADS
+       if (list.nr || cached || online_cpus() == 1)
+               use_threads = 0;
+#else
+       use_threads = 0;
+#endif
+
+#ifndef NO_PTHREADS
+       if (use_threads) {
+               if (!(opt.name_only || opt.unmatch_name_only || opt.count)
+                   && (opt.pre_context || opt.post_context ||
+                       opt.file_break || opt.funcbody))
+                       skip_first_line = 1;
+               start_threads(&opt);
+       }
+#endif
+
        /* The rest are paths */
        if (!seen_dashdash) {
                int j;
@@ -1048,13 +966,16 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        if (!show_in_pager)
                setup_pager();
 
+       if (!use_index && (untracked || cached))
+               die(_("--cached or --untracked cannot be used with --no-index."));
 
-       if (!use_index) {
-               if (cached)
-                       die(_("--cached cannot be used with --no-index."));
+       if (!use_index || untracked) {
+               int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
                if (list.nr)
-                       die(_("--no-index cannot be used with revs."));
-               hit = grep_directory(&opt, &pathspec);
+                       die(_("--no-index or --untracked cannot be used with revs."));
+               hit = grep_directory(&opt, &pathspec, use_exclude);
+       } else if (0 <= opt_exclude) {
+               die(_("--[no-]exclude-standard cannot be used for tracked contents."));
        } else if (!list.nr) {
                if (!cached)
                        setup_work_tree();
index 0945adbb3bb188b612341c31c8986fabb491928d..af7dc37a441d12bc4283ac730524e07a582d014f 100644 (file)
@@ -172,10 +172,10 @@ static const char *open_pack_file(const char *pack_name)
        if (from_stdin) {
                input_fd = 0;
                if (!pack_name) {
-                       static char tmpfile[PATH_MAX];
-                       output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+                       static char tmp_file[PATH_MAX];
+                       output_fd = odb_mkstemp(tmp_file, sizeof(tmp_file),
                                                "pack/tmp_pack_XXXXXX");
-                       pack_name = xstrdup(tmpfile);
+                       pack_name = xstrdup(tmp_file);
                } else
                        output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
                if (output_fd < 0)
@@ -1122,8 +1122,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                if (!index_name)
                        die("--verify with no packfile name given");
                read_idx_option(&opts, index_name);
-               opts.flags |= WRITE_IDX_VERIFY;
+               opts.flags |= WRITE_IDX_VERIFY | WRITE_IDX_STRICT;
        }
+       if (strict)
+               opts.flags |= WRITE_IDX_STRICT;
 
        curr_pack = open_pack_file(pack_name);
        parse_pack_header();
index d07554c8844a9b7dd3d4ae2b5efe2cbde623e4af..0dacb8b79c57cae2b789eb84d7cfbdb1654ba52f 100644 (file)
@@ -351,7 +351,7 @@ static void separate_git_dir(const char *git_dir)
                else if (S_ISDIR(st.st_mode))
                        src = git_link;
                else
-                       die(_("unable to handle file type %d"), st.st_mode);
+                       die(_("unable to handle file type %d"), (int)st.st_mode);
 
                if (rename(src, git_dir))
                        die_errno(_("unable to move %s to %s"), src, git_dir);
index f5d49305903911eb7aa0fb3f73e0fd950b896228..7d1f6f88a0e0f76bc8ee35155a532b641c14a845 100644 (file)
@@ -19,6 +19,7 @@
 #include "remote.h"
 #include "string-list.h"
 #include "parse-options.h"
+#include "branch.h"
 
 /* Set a default date-time format for git log ("log.date" config variable) */
 static const char *default_date_mode = NULL;
@@ -72,8 +73,6 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse
 
 static void cmd_log_init_defaults(struct rev_info *rev)
 {
-       rev->abbrev = DEFAULT_ABBREV;
-       rev->commit_format = CMIT_FMT_DEFAULT;
        if (fmt_pretty)
                get_commit_format(fmt_pretty, rev);
        rev->verbose_header = 1;
@@ -746,10 +745,24 @@ static void print_signature(void)
                printf("-- \n%s\n\n", signature);
 }
 
+static void add_branch_description(struct strbuf *buf, const char *branch_name)
+{
+       struct strbuf desc = STRBUF_INIT;
+       if (!branch_name || !*branch_name)
+               return;
+       read_branch_desc(&desc, branch_name);
+       if (desc.len) {
+               strbuf_addch(buf, '\n');
+               strbuf_add(buf, desc.buf, desc.len);
+               strbuf_addch(buf, '\n');
+       }
+}
+
 static void make_cover_letter(struct rev_info *rev, int use_stdout,
                              int numbered, int numbered_files,
                              struct commit *origin,
                              int nr, struct commit **list, struct commit *head,
+                             const char *branch_name,
                              int quiet)
 {
        const char *committer;
@@ -807,6 +820,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
        pp_user_info(&pp, NULL, &sb, committer, encoding);
        pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
        pp_remainder(&pp, &msg, &sb, 0);
+       add_branch_description(&sb, branch_name);
        printf("%s\n", sb.buf);
 
        strbuf_release(&sb);
@@ -1006,6 +1020,35 @@ static int cc_callback(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
+static char *find_branch_name(struct rev_info *rev)
+{
+       int i, positive = -1;
+       unsigned char branch_sha1[20];
+       struct strbuf buf = STRBUF_INIT;
+       const char *branch;
+
+       for (i = 0; i < rev->cmdline.nr; i++) {
+               if (rev->cmdline.rev[i].flags & UNINTERESTING)
+                       continue;
+               if (positive < 0)
+                       positive = i;
+               else
+                       return NULL;
+       }
+       if (positive < 0)
+               return NULL;
+       strbuf_addf(&buf, "refs/heads/%s", rev->cmdline.rev[positive].name);
+       branch = resolve_ref_unsafe(buf.buf, branch_sha1, 1, NULL);
+       if (!branch ||
+           prefixcmp(branch, "refs/heads/") ||
+           hashcmp(rev->cmdline.rev[positive].item->sha1, branch_sha1))
+               branch = NULL;
+       strbuf_release(&buf);
+       if (branch)
+               return xstrdup(rev->cmdline.rev[positive].name);
+       return NULL;
+}
+
 int cmd_format_patch(int argc, const char **argv, const char *prefix)
 {
        struct commit *commit;
@@ -1027,6 +1070,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        struct strbuf buf = STRBUF_INIT;
        int use_patch_format = 0;
        int quiet = 0;
+       char *branch_name = NULL;
        const struct option builtin_format_patch_options[] = {
                { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
                            "use [PATCH n/m] even with a single patch",
@@ -1217,8 +1261,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                         * origin" that prepares what the origin side still
                         * does not have.
                         */
+                       unsigned char sha1[20];
+                       const char *ref;
+
                        rev.pending.objects[0].item->flags |= UNINTERESTING;
                        add_head_to_pending(&rev);
+                       ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
+                       if (ref && !prefixcmp(ref, "refs/heads/"))
+                               branch_name = xstrdup(ref + strlen("refs/heads/"));
+                       else
+                               branch_name = xstrdup(""); /* no branch */
                }
                /*
                 * Otherwise, it is "format-patch -22 HEAD", and/or
@@ -1234,16 +1286,26 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        rev.show_root_diff = 1;
 
        if (cover_letter) {
-               /* remember the range */
+               /*
+                * NEEDSWORK:randomly pick one positive commit to show
+                * diffstat; this is often the tip and the command
+                * happens to do the right thing in most cases, but a
+                * complex command like "--cover-letter a b c ^bottom"
+                * picks "c" and shows diffstat between bottom..c
+                * which may not match what the series represents at
+                * all and totally broken.
+                */
                int i;
                for (i = 0; i < rev.pending.nr; i++) {
                        struct object *o = rev.pending.objects[i].item;
                        if (!(o->flags & UNINTERESTING))
                                head = (struct commit *)o;
                }
-               /* We can't generate a cover letter without any patches */
+               /* There is nothing to show; it is not an error, though. */
                if (!head)
                        return 0;
+               if (!branch_name)
+                       branch_name = find_branch_name(&rev);
        }
 
        if (ignore_if_in_upstream) {
@@ -1294,7 +1356,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                if (thread)
                        gen_message_id(&rev, "cover");
                make_cover_letter(&rev, use_stdout, numbered, numbered_files,
-                                 origin, nr, list, head, quiet);
+                                 origin, nr, list, head, branch_name, quiet);
                total++;
                start_number--;
        }
@@ -1366,6 +1428,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                        fclose(stdout);
        }
        free(list);
+       free(branch_name);
        string_list_clear(&extra_to, 0);
        string_list_clear(&extra_cc, 0);
        string_list_clear(&extra_hdr, 0);
index e8a800d3ac42bfce328bea786d8e1efa4ff695c7..7cff175745d680d9cde24280e569b4f513d28673 100644 (file)
@@ -545,7 +545,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                strip_trailing_slash_from_submodules();
 
        /* Find common prefix for all pathspec's */
-       max_prefix = pathspec_prefix(prefix, pathspec);
+       max_prefix = common_prefix(pathspec);
        max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
 
        /* Treat unmatching pathspec elements as errors */
index 10223092a9ebfed4092db680529a42ca8886a9f2..41c88a98a2de1ce817351243f9aa9e2811604097 100644 (file)
@@ -43,6 +43,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
        struct transport *transport;
        const struct ref *ref;
 
+       if (argc == 2 && !strcmp("-h", argv[1]))
+               usage(ls_remote_usage);
+
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
 
index b22a8422381e73fa410d0c27a70bb9ef6cdc9232..8018a144688541b6ea178e96fe83206209a2902c 100644 (file)
@@ -26,6 +26,8 @@
 #include "merge-recursive.h"
 #include "resolve-undo.h"
 #include "remote.h"
+#include "fmt-merge-msg.h"
+#include "gpg-interface.h"
 
 #define DEFAULT_TWOHEAD (1<<0)
 #define DEFAULT_OCTOPUS (1<<1)
@@ -44,11 +46,12 @@ static const char * const builtin_merge_usage[] = {
        NULL
 };
 
-static int show_diffstat = 1, shortlog_len, squash;
+static int show_diffstat = 1, shortlog_len = -1, squash;
 static int option_commit = 1, allow_fast_forward = 1;
-static int fast_forward_only;
+static int fast_forward_only, option_edit = -1;
 static int allow_trivial = 1, have_message;
-static struct strbuf merge_msg;
+static int overwrite_ignore = 1;
+static struct strbuf merge_msg = STRBUF_INIT;
 static struct commit_list *remoteheads;
 static struct strategy **use_strategies;
 static size_t use_strategies_nr, use_strategies_alloc;
@@ -62,6 +65,7 @@ static int allow_rerere_auto;
 static int abort_current_merge;
 static int show_progress = -1;
 static int default_to_upstream;
+static const char *sign_commit;
 
 static struct strategy all_strategy[] = {
        { "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -189,6 +193,8 @@ static struct option builtin_merge_options[] = {
                "create a single commit instead of doing a merge"),
        OPT_BOOLEAN(0, "commit", &option_commit,
                "perform a commit if the merge succeeds (default)"),
+       OPT_BOOL('e', "edit", &option_edit,
+               "edit message before committing"),
        OPT_BOOLEAN(0, "ff", &allow_fast_forward,
                "allow fast-forward (default)"),
        OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
@@ -205,6 +211,9 @@ static struct option builtin_merge_options[] = {
        OPT_BOOLEAN(0, "abort", &abort_current_merge,
                "abort the current in-progress merge"),
        OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
+       { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+         "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+       OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"),
        OPT_END()
 };
 
@@ -314,13 +323,15 @@ static void squash_message(struct commit *commit)
        struct rev_info rev;
        struct strbuf out = STRBUF_INIT;
        struct commit_list *j;
+       const char *filename;
        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);
+       filename = git_path("SQUASH_MSG");
+       fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
-               die_errno(_("Could not write to '%s'"), git_path("SQUASH_MSG"));
+               die_errno(_("Could not write to '%s'"), filename);
 
        init_revisions(&rev, NULL);
        rev.ignore_merges = 1;
@@ -407,8 +418,8 @@ static void finish(struct commit *head_commit,
 /* Get the name for the merge commit's message. */
 static void merge_name(const char *remote, struct strbuf *msg)
 {
-       struct object *remote_head;
-       unsigned char branch_head[20], buf_sha[20];
+       struct commit *remote_head;
+       unsigned char branch_head[20];
        struct strbuf buf = STRBUF_INIT;
        struct strbuf bname = STRBUF_INIT;
        const char *ptr;
@@ -419,7 +430,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
        remote = bname.buf;
 
        memset(branch_head, 0, sizeof(branch_head));
-       remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+       remote_head = get_merge_parent(remote);
        if (!remote_head)
                die(_("'%s' does not point to a commit"), remote);
 
@@ -429,6 +440,11 @@ static void merge_name(const char *remote, struct strbuf *msg)
                                    sha1_to_hex(branch_head), remote);
                        goto cleanup;
                }
+               if (!prefixcmp(found_ref, "refs/tags/")) {
+                       strbuf_addf(msg, "%s\t\ttag '%s' of .\n",
+                                   sha1_to_hex(branch_head), remote);
+                       goto cleanup;
+               }
                if (!prefixcmp(found_ref, "refs/remotes/")) {
                        strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n",
                                    sha1_to_hex(branch_head), remote);
@@ -467,10 +483,10 @@ static void merge_name(const char *remote, struct strbuf *msg)
                strbuf_addstr(&truname, "refs/heads/");
                strbuf_addstr(&truname, remote);
                strbuf_setlen(&truname, truname.len - len);
-               if (resolve_ref(truname.buf, buf_sha, 1, NULL)) {
+               if (ref_exists(truname.buf)) {
                        strbuf_addf(msg,
                                    "%s\t\tbranch '%s'%s of .\n",
-                                   sha1_to_hex(remote_head->sha1),
+                                   sha1_to_hex(remote_head->object.sha1),
                                    truname.buf + 11,
                                    (early ? " (early part)" : ""));
                        strbuf_release(&truname);
@@ -480,14 +496,16 @@ static void merge_name(const char *remote, struct strbuf *msg)
 
        if (!strcmp(remote, "FETCH_HEAD") &&
                        !access(git_path("FETCH_HEAD"), R_OK)) {
+               const char *filename;
                FILE *fp;
                struct strbuf line = STRBUF_INIT;
                char *ptr;
 
-               fp = fopen(git_path("FETCH_HEAD"), "r");
+               filename = git_path("FETCH_HEAD");
+               fp = fopen(filename, "r");
                if (!fp)
                        die_errno(_("could not open '%s' for reading"),
-                                 git_path("FETCH_HEAD"));
+                                 filename);
                strbuf_getline(&line, fp, '\n');
                fclose(fp);
                ptr = strstr(line.buf, "\tnot-for-merge\t");
@@ -498,7 +516,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
                goto cleanup;
        }
        strbuf_addf(msg, "%s\t\tcommit '%s'\n",
-               sha1_to_hex(remote_head->sha1), remote);
+               sha1_to_hex(remote_head->object.sha1), remote);
 cleanup:
        strbuf_release(&buf);
        strbuf_release(&bname);
@@ -526,6 +544,8 @@ static void parse_branch_merge_options(char *bmo)
 
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
+       int status;
+
        if (branch && !prefixcmp(k, "branch.") &&
                !prefixcmp(k + 7, branch) &&
                !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
@@ -542,15 +562,7 @@ static int git_merge_config(const char *k, const char *v, void *cb)
                return git_config_string(&pull_octopus, k, v);
        else if (!strcmp(k, "merge.renormalize"))
                option_renormalize = git_config_bool(k, v);
-       else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) {
-               int is_bool;
-               shortlog_len = git_config_bool_or_int(k, v, &is_bool);
-               if (!is_bool && shortlog_len < 0)
-                       return error(_("%s: negative length %s"), k, v);
-               if (is_bool && shortlog_len)
-                       shortlog_len = DEFAULT_MERGE_LOG_LEN;
-               return 0;
-       } else if (!strcmp(k, "merge.ff")) {
+       else if (!strcmp(k, "merge.ff")) {
                int boolval = git_config_maybe_bool(k, v);
                if (0 <= boolval) {
                        allow_fast_forward = boolval;
@@ -563,6 +575,13 @@ static int git_merge_config(const char *k, const char *v, void *cb)
                default_to_upstream = git_config_bool(k, v);
                return 0;
        }
+
+       status = fmt_merge_msg_config(k, v, cb);
+       if (status)
+               return status;
+       status = git_gpg_config(k, v, NULL);
+       if (status)
+               return status;
        return git_diff_ui_config(k, v, cb);
 }
 
@@ -702,7 +721,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                                die(_("Unknown option for merge-recursive: -X%s"), xopts[x]);
 
                o.branch1 = head_arg;
-               o.branch2 = remoteheads->item->util;
+               o.branch2 = merge_remote_util(remoteheads->item)->name;
 
                for (j = common; j; j = j->next)
                        commit_list_insert(j->item, &reversed);
@@ -757,10 +776,12 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote
        memset(&trees, 0, sizeof(trees));
        memset(&opts, 0, sizeof(opts));
        memset(&t, 0, sizeof(t));
-       memset(&dir, 0, sizeof(dir));
-       dir.flags |= DIR_SHOW_IGNORED;
-       setup_standard_excludes(&dir);
-       opts.dir = &dir;
+       if (overwrite_ignore) {
+               memset(&dir, 0, sizeof(dir));
+               dir.flags |= DIR_SHOW_IGNORED;
+               setup_standard_excludes(&dir);
+               opts.dir = &dir;
+       }
 
        opts.head_idx = 1;
        opts.src_index = &the_index;
@@ -833,30 +854,66 @@ static void add_strategies(const char *string, unsigned attr)
 
 }
 
-static void write_merge_msg(void)
+static void write_merge_msg(struct strbuf *msg)
 {
-       int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+       const char *filename = git_path("MERGE_MSG");
+       int fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
                die_errno(_("Could not open '%s' for writing"),
-                         git_path("MERGE_MSG"));
-       if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
-               die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG"));
+                         filename);
+       if (write_in_full(fd, msg->buf, msg->len) != msg->len)
+               die_errno(_("Could not write to '%s'"), filename);
        close(fd);
 }
 
-static void read_merge_msg(void)
+static void read_merge_msg(struct strbuf *msg)
+{
+       const char *filename = git_path("MERGE_MSG");
+       strbuf_reset(msg);
+       if (strbuf_read_file(msg, filename, 0) < 0)
+               die_errno(_("Could not read from '%s'"), filename);
+}
+
+static void write_merge_state(void);
+static void abort_commit(const char *err_msg)
 {
-       strbuf_reset(&merge_msg);
-       if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
-               die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
+       if (err_msg)
+               error("%s", err_msg);
+       fprintf(stderr,
+               _("Not committing merge; use 'git commit' to complete the merge.\n"));
+       write_merge_state();
+       exit(1);
 }
 
-static void run_prepare_commit_msg(void)
+static const char merge_editor_comment[] =
+N_("Please enter a commit message to explain why this merge is necessary,\n"
+   "especially if it merges an updated upstream into a topic branch.\n"
+   "\n"
+   "Lines starting with '#' will be ignored, and an empty message aborts\n"
+   "the commit.\n");
+
+static void prepare_to_commit(void)
 {
-       write_merge_msg();
+       struct strbuf msg = STRBUF_INIT;
+       const char *comment = _(merge_editor_comment);
+       strbuf_addbuf(&msg, &merge_msg);
+       strbuf_addch(&msg, '\n');
+       if (0 < option_edit)
+               strbuf_add_lines(&msg, "# ", comment, strlen(comment));
+       write_merge_msg(&msg);
        run_hook(get_index_file(), "prepare-commit-msg",
                 git_path("MERGE_MSG"), "merge", NULL, NULL);
-       read_merge_msg();
+       if (option_edit) {
+               if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
+                       abort_commit(NULL);
+       }
+       read_merge_msg(&msg);
+       stripspace(&msg, option_edit);
+       if (!msg.len)
+               abort_commit(_("Empty commit message."));
+       strbuf_release(&merge_msg);
+       strbuf_addbuf(&merge_msg, &msg);
+       strbuf_release(&msg);
 }
 
 static int merge_trivial(struct commit *head)
@@ -870,8 +927,10 @@ static int merge_trivial(struct commit *head)
        parent->next = xmalloc(sizeof(*parent->next));
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
-       run_prepare_commit_msg();
-       commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
+       prepare_to_commit();
+       if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL,
+                       sign_commit))
+               die(_("failed to write commit object"));
        finish(head, result_commit, "In-index merge");
        drop_save();
        return 0;
@@ -899,10 +958,12 @@ static int finish_automerge(struct commit *head,
                for (j = remoteheads; j; j = j->next)
                        pptr = &commit_list_insert(j->item, pptr)->next;
        }
-       free_commit_list(remoteheads);
        strbuf_addch(&merge_msg, '\n');
-       run_prepare_commit_msg();
-       commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
+       prepare_to_commit();
+       free_commit_list(remoteheads);
+       if (commit_tree(&merge_msg, result_tree, parents, result_commit,
+                       NULL, sign_commit))
+               die(_("failed to write commit object"));
        strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
        finish(head, result_commit, buf.buf);
        strbuf_release(&buf);
@@ -912,13 +973,14 @@ static int finish_automerge(struct commit *head,
 
 static int suggest_conflicts(int renormalizing)
 {
+       const char *filename;
        FILE *fp;
        int pos;
 
-       fp = fopen(git_path("MERGE_MSG"), "a");
+       filename = git_path("MERGE_MSG");
+       fp = fopen(filename, "a");
        if (!fp)
-               die_errno(_("Could not open '%s' for writing"),
-                         git_path("MERGE_MSG"));
+               die_errno(_("Could not open '%s' for writing"), filename);
        fprintf(fp, "\nConflicts:\n");
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
@@ -1008,6 +1070,45 @@ static int setup_with_upstream(const char ***argv)
        return i;
 }
 
+static void write_merge_state(void)
+{
+       const char *filename;
+       int fd;
+       struct commit_list *j;
+       struct strbuf buf = STRBUF_INIT;
+
+       for (j = remoteheads; j; j = j->next) {
+               unsigned const char *sha1;
+               struct commit *c = j->item;
+               if (c->util && merge_remote_util(c)->obj) {
+                       sha1 = merge_remote_util(c)->obj->sha1;
+               } else {
+                       sha1 = c->object.sha1;
+               }
+               strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
+       }
+       filename = git_path("MERGE_HEAD");
+       fd = open(filename, O_WRONLY | O_CREAT, 0666);
+       if (fd < 0)
+               die_errno(_("Could not open '%s' for writing"), filename);
+       if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+               die_errno(_("Could not write to '%s'"), filename);
+       close(fd);
+       strbuf_addch(&merge_msg, '\n');
+       write_merge_msg(&merge_msg);
+
+       filename = git_path("MERGE_MODE");
+       fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+       if (fd < 0)
+               die_errno(_("Could not open '%s' for writing"), filename);
+       strbuf_reset(&buf);
+       if (!allow_fast_forward)
+               strbuf_addf(&buf, "no-ff");
+       if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+               die_errno(_("Could not write to '%s'"), filename);
+       close(fd);
+}
+
 int cmd_merge(int argc, const char **argv, const char *prefix)
 {
        unsigned char result_tree[20];
@@ -1016,11 +1117,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        struct commit *head_commit;
        struct strbuf buf = STRBUF_INIT;
        const char *head_arg;
-       int flag, i;
+       int flag, i, ret = 0;
        int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
        struct commit_list *common = NULL;
        const char *best_strategy = NULL, *wt_strategy = NULL;
        struct commit_list **remotes = &remoteheads;
+       void *branch_to_free;
 
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_merge_usage, builtin_merge_options);
@@ -1029,7 +1131,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
-       branch = resolve_ref("HEAD", head_sha1, 0, &flag);
+       branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag);
        if (branch && !prefixcmp(branch, "refs/heads/"))
                branch += 11;
        if (!branch || is_null_sha1(head_sha1))
@@ -1043,6 +1145,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                parse_branch_merge_options(branch_mergeoptions);
        argc = parse_options(argc, argv, prefix, builtin_merge_options,
                        builtin_merge_usage, 0);
+       if (shortlog_len < 0)
+               shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
 
        if (verbosity < 0 && show_progress == -1)
                show_progress = 0;
@@ -1055,7 +1159,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                        die(_("There is no merge to abort (MERGE_HEAD missing)."));
 
                /* Invoke 'git reset --merge' */
-               return cmd_reset(nargc, nargv, prefix);
+               ret = cmd_reset(nargc, nargv, prefix);
+               goto done;
        }
 
        if (read_cache_unmerged())
@@ -1094,9 +1199,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                die(_("You cannot combine --no-ff with --ff-only."));
 
        if (!abort_current_merge) {
-               if (!argc && default_to_upstream)
-                       argc = setup_with_upstream(&argv);
-               else if (argc == 1 && !strcmp(argv[0], "-"))
+               if (!argc) {
+                       if (default_to_upstream)
+                               argc = setup_with_upstream(&argv);
+                       else
+                               die(_("No commit specified and merge.defaultToUpstream not set."));
+               } else if (argc == 1 && !strcmp(argv[0], "-"))
                        argv[0] = "@{-1}";
        }
        if (!argc)
@@ -1119,7 +1227,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                argv += 2;
                argc -= 2;
        } else if (!head_commit) {
-               struct object *remote_head;
+               struct commit *remote_head;
                /*
                 * If the merged head is a valid one there is no reason
                 * to forbid "git merge" into a branch yet to be born.
@@ -1133,13 +1241,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                if (!allow_fast_forward)
                        die(_("Non-fast-forward commit does not make sense into "
                            "an empty head"));
-               remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+               remote_head = get_merge_parent(argv[0]);
                if (!remote_head)
                        die(_("%s - not something we can merge"), argv[0]);
-               read_empty(remote_head->sha1, 0);
-               update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
-                               DIE_ON_ERR);
-               return 0;
+               read_empty(remote_head->object.sha1, 0);
+               update_ref("initial pull", "HEAD", remote_head->object.sha1,
+                          NULL, 0, DIE_ON_ERR);
+               goto done;
        } else {
                struct strbuf merge_names = STRBUF_INIT;
 
@@ -1147,19 +1255,20 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                head_arg = "HEAD";
 
                /*
-                * All the rest are the commits being merged;
-                * prepare the standard merge summary message to
-                * be appended to the given message.  If remote
-                * is invalid we will die later in the common
-                * codepath so we discard the error in this
-                * loop.
+                * All the rest are the commits being merged; prepare
+                * the standard merge summary message to be appended
+                * to the given message.
                 */
                for (i = 0; i < argc; i++)
                        merge_name(argv[i], &merge_names);
 
                if (!have_message || shortlog_len) {
-                       fmt_merge_msg(&merge_names, &merge_msg, !have_message,
-                                     shortlog_len);
+                       struct fmt_merge_msg_opts opts;
+                       memset(&opts, 0, sizeof(opts));
+                       opts.add_title = !have_message;
+                       opts.shortlog_len = shortlog_len;
+
+                       fmt_merge_msg(&merge_names, &merge_msg, &opts);
                        if (merge_msg.len)
                                strbuf_setlen(&merge_msg, merge_msg.len - 1);
                }
@@ -1176,21 +1285,27 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        strbuf_reset(&buf);
 
        for (i = 0; i < argc; i++) {
-               struct object *o;
-               struct commit *commit;
-
-               o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
-               if (!o)
+               struct commit *commit = get_merge_parent(argv[i]);
+               if (!commit)
                        die(_("%s - not something we can merge"), argv[i]);
-               commit = lookup_commit(o->sha1);
-               commit->util = (void *)argv[i];
                remotes = &commit_list_insert(commit, remotes)->next;
-
-               strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+               strbuf_addf(&buf, "GITHEAD_%s",
+                           sha1_to_hex(commit->object.sha1));
                setenv(buf.buf, argv[i], 1);
                strbuf_reset(&buf);
+               if (!fast_forward_only &&
+                   merge_remote_util(commit) &&
+                   merge_remote_util(commit)->obj &&
+                   merge_remote_util(commit)->obj->type == OBJ_TAG) {
+                       if (option_edit < 0)
+                               option_edit = 1;
+                       allow_fast_forward = 0;
+               }
        }
 
+       if (option_edit < 0)
+               option_edit = 0;
+
        if (!use_strategies) {
                if (!remoteheads->next)
                        add_strategies(pull_twohead, DEFAULT_TWOHEAD);
@@ -1226,13 +1341,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                 * but first the most common case of merging one remote.
                 */
                finish_up_to_date("Already up-to-date.");
-               return 0;
+               goto done;
        } else if (allow_fast_forward && !remoteheads->next &&
                        !common->next &&
                        !hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
                /* Again the most common case of merging one remote. */
                struct strbuf msg = STRBUF_INIT;
-               struct object *o;
+               struct commit *commit;
                char hex[41];
 
                strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV));
@@ -1246,17 +1361,21 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                if (have_message)
                        strbuf_addstr(&msg,
                                " (no commit created; -m option ignored)");
-               o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
-                       0, NULL, OBJ_COMMIT);
-               if (!o)
-                       return 1;
+               commit = remoteheads->item;
+               if (!commit) {
+                       ret = 1;
+                       goto done;
+               }
 
-               if (checkout_fast_forward(head_commit->object.sha1, remoteheads->item->object.sha1))
-                       return 1;
+               if (checkout_fast_forward(head_commit->object.sha1,
+                                         commit->object.sha1)) {
+                       ret = 1;
+                       goto done;
+               }
 
-               finish(head_commit, o->sha1, msg.buf);
+               finish(head_commit, commit->object.sha1, msg.buf);
                drop_save();
-               return 0;
+               goto done;
        } else if (!remoteheads->next && common->next)
                ;
                /*
@@ -1274,8 +1393,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                        git_committer_info(IDENT_ERROR_ON_NO_NAME);
                        printf(_("Trying really trivial in-index merge...\n"));
                        if (!read_tree_trivial(common->item->object.sha1,
-                                       head_commit->object.sha1, remoteheads->item->object.sha1))
-                               return merge_trivial(head_commit);
+                                              head_commit->object.sha1,
+                                              remoteheads->item->object.sha1)) {
+                               ret = merge_trivial(head_commit);
+                               goto done;
+                       }
                        printf(_("Nope.\n"));
                }
        } else {
@@ -1303,7 +1425,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                }
                if (up_to_date) {
                        finish_up_to_date("Already up-to-date. Yeeah!");
-                       return 0;
+                       goto done;
                }
        }
 
@@ -1385,9 +1507,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * If we have a resulting tree, that means the strategy module
         * auto resolved the merge cleanly.
         */
-       if (automerge_was_ok)
-               return finish_automerge(head_commit, common, result_tree,
-                                       wt_strategy);
+       if (automerge_was_ok) {
+               ret = finish_automerge(head_commit, common, result_tree,
+                                      wt_strategy);
+               goto done;
+       }
 
        /*
         * Pick the result from the best strategy and have the user fix
@@ -1401,7 +1525,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                else
                        fprintf(stderr, _("Merge with strategy %s failed.\n"),
                                use_strategies[0]->name);
-               return 2;
+               ret = 2;
+               goto done;
        } else if (best_strategy == wt_strategy)
                ; /* We already have its result in the working tree. */
        else {
@@ -1414,38 +1539,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 
        if (squash)
                finish(head_commit, NULL, NULL);
-       else {
-               int fd;
-               struct commit_list *j;
-
-               for (j = remoteheads; j; j = j->next)
-                       strbuf_addf(&buf, "%s\n",
-                               sha1_to_hex(j->item->object.sha1));
-               fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
-               if (fd < 0)
-                       die_errno(_("Could not open '%s' for writing"),
-                                 git_path("MERGE_HEAD"));
-               if (write_in_full(fd, buf.buf, buf.len) != buf.len)
-                       die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD"));
-               close(fd);
-               strbuf_addch(&merge_msg, '\n');
-               write_merge_msg();
-               fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
-               if (fd < 0)
-                       die_errno(_("Could not open '%s' for writing"),
-                                 git_path("MERGE_MODE"));
-               strbuf_reset(&buf);
-               if (!allow_fast_forward)
-                       strbuf_addf(&buf, "no-ff");
-               if (write_in_full(fd, buf.buf, buf.len) != buf.len)
-                       die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE"));
-               close(fd);
-       }
+       else
+               write_merge_state();
 
-       if (merge_was_ok) {
+       if (merge_was_ok)
                fprintf(stderr, _("Automatic merge went well; "
                        "stopped before committing as requested\n"));
-               return 0;
-       } else
-               return suggest_conflicts(option_renormalize);
+       else
+               ret = suggest_conflicts(option_renormalize);
+
+done:
+       free(branch_to_free);
+       return ret;
 }
index 098395fda1932674e29f8d2cb332c722a6cb275f..4ae1c412d47bd581f22ec8b821a1111224dddd28 100644 (file)
@@ -60,6 +60,7 @@ static void write_tree(unsigned char *sha1)
        }
 
        write_sha1_file(buf.buf, buf.len, tree_type, sha1);
+       strbuf_release(&buf);
 }
 
 static const char *mktree_usage[] = {
index 5efe6c5760c43d0059a940ab9d4610b97092c8bd..2a144b011caa8ecb70f55976bdec60cae89fad9e 100644 (file)
@@ -59,6 +59,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
        int i, newfd;
        int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
        struct option builtin_mv_options[] = {
+               OPT__VERBOSE(&verbose, "be verbose"),
                OPT__DRY_RUN(&show_only, "dry run"),
                OPT__FORCE(&force, "force move/rename even if target exists"),
                OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
@@ -93,7 +94,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                destination = copy_pathspec(dest_path[0], argv, argc, 1);
        } else {
                if (argc != 1)
-                       usage_with_options(builtin_mv_usage, builtin_mv_options);
+                       die("destination '%s' is not a directory", dest_path[0]);
                destination = dest_path;
        }
 
@@ -176,7 +177,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                                 * check both source and destination
                                 */
                                if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
-                                       warning(_("%s; will overwrite!"), bad);
+                                       if (verbose)
+                                               warning(_("overwriting '%s'"), dst);
                                        bad = NULL;
                                } else
                                        bad = _("Cannot overwrite");
index e8862b5de2e263ca0e1ccee8c69b3881816f41c5..1b374583c2751801dfae1d0d1861d28f81c7ad7b 100644 (file)
@@ -172,7 +172,9 @@ static void show_name(const struct object *obj,
 }
 
 static char const * const name_rev_usage[] = {
-       "git name-rev [options] ( --all | --stdin | <commit>... )",
+       "git name-rev [options] <commit>...",
+       "git name-rev [options] --all",
+       "git name-rev [options] --stdin",
        NULL
 };
 
index f8e437db0156043f1586e66adf343e34ef6cf4dc..3644d140ece9f2cfc6381e1a60c5b4fc8f645e2a 100644 (file)
@@ -301,12 +301,12 @@ void commit_notes(struct notes_tree *t, const char *msg)
                return; /* don't have to commit an unchanged tree */
 
        /* Prepare commit message and reflog message */
-       strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
        strbuf_addstr(&buf, msg);
        if (buf.buf[buf.len - 1] != '\n')
                strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
 
-       create_notes_commit(t, NULL, buf.buf + 7, commit_sha1);
+       create_notes_commit(t, NULL, &buf, commit_sha1);
+       strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */
        update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
 
        strbuf_release(&buf);
@@ -804,6 +804,8 @@ static int merge_commit(struct notes_merge_options *o)
        struct notes_tree *t;
        struct commit *partial;
        struct pretty_print_context pretty_ctx;
+       void *local_ref_to_free;
+       int ret;
 
        /*
         * Read partial merge result from .git/NOTES_MERGE_PARTIAL,
@@ -825,7 +827,8 @@ static int merge_commit(struct notes_merge_options *o)
        t = xcalloc(1, sizeof(struct notes_tree));
        init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
 
-       o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, NULL);
+       o->local_ref = local_ref_to_free =
+               resolve_refdup("NOTES_MERGE_REF", sha1, 0, NULL);
        if (!o->local_ref)
                die("Failed to resolve NOTES_MERGE_REF");
 
@@ -843,7 +846,9 @@ static int merge_commit(struct notes_merge_options *o)
 
        free_notes(t);
        strbuf_release(&msg);
-       return merge_abort(o);
+       ret = merge_abort(o);
+       free(local_ref_to_free);
+       return ret;
 }
 
 static int merge(int argc, const char **argv, const char *prefix)
index 638d43da0e61c67a374ee124faf9391180171b21..0f2e7b8f5cb26910679c39550d59ef23353a278f 100644 (file)
@@ -76,7 +76,7 @@ static struct pack_idx_option pack_idx_opts;
 static const char *base_name;
 static int progress = 1;
 static int window = 10;
-static unsigned long pack_size_limit, pack_size_limit_cfg;
+static unsigned long pack_size_limit;
 static int depth = 50;
 static int delta_search_threads;
 static int pack_to_stdout;
@@ -409,25 +409,56 @@ static unsigned long write_object(struct sha1file *f,
        return hdrlen + datalen;
 }
 
-static int write_one(struct sha1file *f,
-                              struct object_entry *e,
-                              off_t *offset)
+enum write_one_status {
+       WRITE_ONE_SKIP = -1, /* already written */
+       WRITE_ONE_BREAK = 0, /* writing this will bust the limit; not written */
+       WRITE_ONE_WRITTEN = 1, /* normal */
+       WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */
+};
+
+static enum write_one_status write_one(struct sha1file *f,
+                                      struct object_entry *e,
+                                      off_t *offset)
 {
        unsigned long size;
+       int recursing;
 
-       /* offset is non zero if object is written already. */
-       if (e->idx.offset || e->preferred_base)
-               return -1;
+       /*
+        * we set offset to 1 (which is an impossible value) to mark
+        * the fact that this object is involved in "write its base
+        * first before writing a deltified object" recursion.
+        */
+       recursing = (e->idx.offset == 1);
+       if (recursing) {
+               warning("recursive delta detected for object %s",
+                       sha1_to_hex(e->idx.sha1));
+               return WRITE_ONE_RECURSIVE;
+       } else if (e->idx.offset || e->preferred_base) {
+               /* offset is non zero if object is written already. */
+               return WRITE_ONE_SKIP;
+       }
 
        /* if we are deltified, write out base object first. */
-       if (e->delta && !write_one(f, e->delta, offset))
-               return 0;
+       if (e->delta) {
+               e->idx.offset = 1; /* now recurse */
+               switch (write_one(f, e->delta, offset)) {
+               case WRITE_ONE_RECURSIVE:
+                       /* we cannot depend on this one */
+                       e->delta = NULL;
+                       break;
+               default:
+                       break;
+               case WRITE_ONE_BREAK:
+                       e->idx.offset = recursing;
+                       return WRITE_ONE_BREAK;
+               }
+       }
 
        e->idx.offset = *offset;
        size = write_object(f, e, *offset);
        if (!size) {
-               e->idx.offset = 0;
-               return 0;
+               e->idx.offset = recursing;
+               return WRITE_ONE_BREAK;
        }
        written_list[nr_written++] = &e->idx;
 
@@ -435,7 +466,7 @@ static int write_one(struct sha1file *f,
        if (signed_add_overflows(*offset, size))
                die("pack too large for current definition of off_t");
        *offset += size;
-       return 1;
+       return WRITE_ONE_WRITTEN;
 }
 
 static int mark_tagged(const char *path, const unsigned char *sha1, int flag,
@@ -607,7 +638,6 @@ static void write_pack_file(void)
        uint32_t i = 0, j;
        struct sha1file *f;
        off_t offset;
-       struct pack_header hdr;
        uint32_t nr_remaining = nr_result;
        time_t last_mtime = 0;
        struct object_entry **write_order;
@@ -621,26 +651,18 @@ static void write_pack_file(void)
                unsigned char sha1[20];
                char *pack_tmp_name = NULL;
 
-               if (pack_to_stdout) {
+               if (pack_to_stdout)
                        f = sha1fd_throughput(1, "<stdout>", progress_state);
-               } else {
-                       char tmpname[PATH_MAX];
-                       int fd;
-                       fd = odb_mkstemp(tmpname, sizeof(tmpname),
-                                        "pack/tmp_pack_XXXXXX");
-                       pack_tmp_name = xstrdup(tmpname);
-                       f = sha1fd(fd, pack_tmp_name);
-               }
-
-               hdr.hdr_signature = htonl(PACK_SIGNATURE);
-               hdr.hdr_version = htonl(PACK_VERSION);
-               hdr.hdr_entries = htonl(nr_remaining);
-               sha1write(f, &hdr, sizeof(hdr));
-               offset = sizeof(hdr);
+               else
+                       f = create_tmp_packfile(&pack_tmp_name);
+
+               offset = write_pack_header(f, nr_remaining);
+               if (!offset)
+                       die_errno("unable to write pack header");
                nr_written = 0;
                for (; i < nr_objects; i++) {
                        struct object_entry *e = write_order[i];
-                       if (!write_one(f, e, &offset))
+                       if (write_one(f, e, &offset) == WRITE_ONE_BREAK)
                                break;
                        display_progress(progress_state, written);
                }
@@ -662,20 +684,8 @@ static void write_pack_file(void)
 
                if (!pack_to_stdout) {
                        struct stat st;
-                       const char *idx_tmp_name;
                        char tmpname[PATH_MAX];
 
-                       idx_tmp_name = write_idx_file(NULL, written_list, nr_written,
-                                                     &pack_idx_opts, sha1);
-
-                       snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
-                                base_name, sha1_to_hex(sha1));
-                       free_pack_by_name(tmpname);
-                       if (adjust_shared_perm(pack_tmp_name))
-                               die_errno("unable to make temporary pack file readable");
-                       if (rename(pack_tmp_name, tmpname))
-                               die_errno("unable to rename temporary pack file");
-
                        /*
                         * Packs are runtime accessed in their mtime
                         * order since newer packs are more likely to contain
@@ -683,28 +693,27 @@ static void write_pack_file(void)
                         * packs then we should modify the mtime of later ones
                         * to preserve this property.
                         */
-                       if (stat(tmpname, &st) < 0) {
+                       if (stat(pack_tmp_name, &st) < 0) {
                                warning("failed to stat %s: %s",
-                                       tmpname, strerror(errno));
+                                       pack_tmp_name, strerror(errno));
                        } else if (!last_mtime) {
                                last_mtime = st.st_mtime;
                        } else {
                                struct utimbuf utb;
                                utb.actime = st.st_atime;
                                utb.modtime = --last_mtime;
-                               if (utime(tmpname, &utb) < 0)
+                               if (utime(pack_tmp_name, &utb) < 0)
                                        warning("failed utime() on %s: %s",
                                                tmpname, strerror(errno));
                        }
 
-                       snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
-                                base_name, sha1_to_hex(sha1));
-                       if (adjust_shared_perm(idx_tmp_name))
-                               die_errno("unable to make temporary index file readable");
-                       if (rename(idx_tmp_name, tmpname))
-                               die_errno("unable to rename temporary index file");
-
-                       free((void *) idx_tmp_name);
+                       /* Enough space for "-<sha-1>.pack"? */
+                       if (sizeof(tmpname) <= strlen(base_name) + 50)
+                               die("pack base name '%s' too long", base_name);
+                       snprintf(tmpname, sizeof(tmpname), "%s-", base_name);
+                       finish_tmp_packfile(tmpname, pack_tmp_name,
+                                           written_list, nr_written,
+                                           &pack_idx_opts, sha1);
                        free(pack_tmp_name);
                        puts(sha1_to_hex(sha1));
                }
@@ -840,6 +849,10 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type,
                off_t offset = find_pack_entry_one(sha1, p);
                if (offset) {
                        if (!found_pack) {
+                               if (!is_pack_valid(p)) {
+                                       warning("packfile %s cannot be accessed", p->pack_name);
+                                       continue;
+                               }
                                found_offset = offset;
                                found_pack = p;
                        }
@@ -1011,7 +1024,7 @@ static void add_pbase_object(struct tree_desc *tree,
        while (tree_entry(tree,&entry)) {
                if (S_ISGITLINK(entry.mode))
                        continue;
-               cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 :
+               cmp = tree_entry_len(&entry) != cmplen ? 1 :
                      memcmp(name, entry.path, cmplen);
                if (cmp > 0)
                        continue;
@@ -2068,10 +2081,6 @@ static int git_pack_config(const char *k, const char *v, void *cb)
                            pack_idx_opts.version);
                return 0;
        }
-       if (!strcmp(k, "pack.packsizelimit")) {
-               pack_size_limit_cfg = git_config_ulong(k, v);
-               return 0;
-       }
        return git_default_config(k, v, cb);
 }
 
@@ -2114,7 +2123,9 @@ static void show_commit(struct commit *commit, void *data)
        commit->object.flags |= OBJECT_ADDED;
 }
 
-static void show_object(struct object *obj, const struct name_path *path, const char *last)
+static void show_object(struct object *obj,
+                       const struct name_path *path, const char *last,
+                       void *data)
 {
        char *name = path_name(path, last);
 
index e65690ba370511072dfa1e64838eef5e5686aac9..58d7cb83240ecef7ab3ff82f3aa92959ec7a62fe 100644 (file)
@@ -5,6 +5,7 @@
 #include "builtin.h"
 #include "reachable.h"
 #include "parse-options.h"
+#include "progress.h"
 #include "dir.h"
 
 static const char * const prune_usage[] = {
@@ -14,6 +15,7 @@ static const char * const prune_usage[] = {
 static int show_only;
 static int verbose;
 static unsigned long expire;
+static int show_progress = -1;
 
 static int prune_tmp_object(const char *path, const char *filename)
 {
@@ -124,9 +126,11 @@ static void remove_temporary_files(const char *path)
 int cmd_prune(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
+       struct progress *progress = NULL;
        const struct option options[] = {
                OPT__DRY_RUN(&show_only, "do not remove, show only"),
                OPT__VERBOSE(&verbose, "report pruned objects"),
+               OPT_BOOL(0, "progress", &show_progress, "show progress"),
                OPT_DATE(0, "expire", &expire,
                         "expire objects older than <time>"),
                OPT_END()
@@ -152,7 +156,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
                else
                        die("unrecognized argument: %s", name);
        }
-       mark_reachable_objects(&revs, 1);
+
+       if (show_progress == -1)
+               show_progress = isatty(2);
+       if (show_progress)
+               progress = start_progress_delay("Checking connectivity", 0, 0, 2);
+
+       mark_reachable_objects(&revs, 1, progress);
+       stop_progress(&progress);
        prune_object_dir(get_object_directory());
 
        prune_packed_objects(show_only);
index 35cce532f2bb632e01c0de0a8e6f9e1395eece88..6c373cf28bfc9d8409014d84eef5b886218b25ab 100644 (file)
@@ -19,7 +19,7 @@ static int thin;
 static int deleterefs;
 static const char *receivepack;
 static int verbosity;
-static int progress;
+static int progress = -1;
 
 static const char **refspec;
 static int refspec_nr;
@@ -260,7 +260,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
                OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
                        TRANSPORT_PUSH_SET_UPSTREAM),
-               OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
+               OPT_BOOL(0, "progress", &progress, "force progress reporting"),
                OPT_END()
        };
 
index af429e101777f1c5df09734433278cfb075e978c..0afb8b289621c419bd7472097335e4235da37d61 100644 (file)
@@ -11,6 +11,7 @@
 #include "transport.h"
 #include "string-list.h"
 #include "sha1-array.h"
+#include "connected.h"
 
 static const char receive_pack_usage[] = "git receive-pack <git-dir>";
 
@@ -25,16 +26,19 @@ static int deny_deletes;
 static int deny_non_fast_forwards;
 static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
 static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
-static int receive_fsck_objects;
+static int receive_fsck_objects = -1;
+static int transfer_fsck_objects = -1;
 static int receive_unpack_limit = -1;
 static int transfer_unpack_limit = -1;
 static int unpack_limit = 100;
 static int report_status;
 static int use_sideband;
+static int quiet;
 static int prefer_ofs_delta = 1;
 static int auto_update_server_info;
 static int auto_gc = 1;
 static const char *head_name;
+static void *head_name_to_free;
 static int sent_capabilities;
 
 static enum deny_action parse_deny_action(const char *var, const char *value)
@@ -79,6 +83,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (strcmp(var, "transfer.fsckobjects") == 0) {
+               transfer_fsck_objects = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "receive.denycurrentbranch")) {
                deny_current_branch = parse_deny_action(var, value);
                return 0;
@@ -107,20 +116,19 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
        return git_default_config(var, value, cb);
 }
 
-static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static void show_ref(const char *path, const unsigned char *sha1)
 {
        if (sent_capabilities)
                packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
        else
                packet_write(1, "%s %s%c%s%s\n",
                             sha1_to_hex(sha1), path, 0,
-                            " report-status delete-refs side-band-64k",
+                            " report-status delete-refs side-band-64k quiet",
                             prefer_ofs_delta ? " ofs-delta" : "");
        sent_capabilities = 1;
-       return 0;
 }
 
-static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused)
 {
        path = strip_namespace(path);
        /*
@@ -133,21 +141,40 @@ static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, vo
         */
        if (!path)
                path = ".have";
-       return show_ref(path, sha1, flag, cb_data);
+       show_ref(path, sha1);
+       return 0;
+}
+
+static void show_one_alternate_sha1(const unsigned char sha1[20], void *unused)
+{
+       show_ref(".have", sha1);
+}
+
+static void collect_one_alternate_ref(const struct ref *ref, void *data)
+{
+       struct sha1_array *sa = data;
+       sha1_array_append(sa, ref->old_sha1);
 }
 
 static void write_head_info(void)
 {
+       struct sha1_array sa = SHA1_ARRAY_INIT;
+       for_each_alternate_ref(collect_one_alternate_ref, &sa);
+       sha1_array_for_each_unique(&sa, show_one_alternate_sha1, NULL);
+       sha1_array_clear(&sa);
        for_each_ref(show_ref_cb, NULL);
        if (!sent_capabilities)
-               show_ref("capabilities^{}", null_sha1, 0, NULL);
+               show_ref("capabilities^{}", null_sha1);
 
+       /* EOF */
+       packet_flush(1);
 }
 
 struct command {
        struct command *next;
        const char *error_string;
-       unsigned int skip_update;
+       unsigned int skip_update:1,
+                    did_not_exist:1;
        unsigned char old_sha1[20];
        unsigned char new_sha1[20];
        char ref_name[FLEX_ARRAY]; /* more */
@@ -257,6 +284,7 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
 
 struct receive_hook_feed_state {
        struct command *cmd;
+       int skip_broken;
        struct strbuf buf;
 };
 
@@ -265,7 +293,8 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
        struct receive_hook_feed_state *state = state_;
        struct command *cmd = state->cmd;
 
-       while (cmd && cmd->error_string)
+       while (cmd &&
+              state->skip_broken && (cmd->error_string || cmd->did_not_exist))
                cmd = cmd->next;
        if (!cmd)
                return -1; /* EOF */
@@ -281,13 +310,15 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
        return 0;
 }
 
-static int run_receive_hook(struct command *commands, const char *hook_name)
+static int run_receive_hook(struct command *commands, const char *hook_name,
+                           int skip_broken)
 {
        struct receive_hook_feed_state state;
        int status;
 
        strbuf_init(&state.buf, 0);
        state.cmd = commands;
+       state.skip_broken = skip_broken;
        if (feed_receive_hook(&state, NULL, NULL))
                return 0;
        state.cmd = commands;
@@ -389,7 +420,7 @@ static const char *update(struct command *cmd)
        struct ref_lock *lock;
 
        /* only refs/... are allowed */
-       if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
+       if (prefixcmp(name, "refs/") || check_refname_format(name + 5, 0)) {
                rp_error("refusing to create funny ref '%s' remotely", name);
                return "funny refname";
        }
@@ -478,8 +509,13 @@ static const char *update(struct command *cmd)
 
        if (is_null_sha1(new_sha1)) {
                if (!parse_object(old_sha1)) {
-                       rp_warning("Allowing deletion of corrupt ref.");
                        old_sha1 = NULL;
+                       if (ref_exists(name)) {
+                               rp_warning("Allowing deletion of corrupt ref.");
+                       } else {
+                               rp_warning("Deleting a non-existent ref.");
+                               cmd->did_not_exist = 1;
+                       }
                }
                if (delete_ref(namespaced_name, old_sha1, 0)) {
                        rp_error("failed to delete %s", name);
@@ -510,7 +546,7 @@ static void run_update_post_hook(struct command *commands)
        struct child_process proc;
 
        for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
-               if (cmd->error_string)
+               if (cmd->error_string || cmd->did_not_exist)
                        continue;
                argc++;
        }
@@ -521,7 +557,7 @@ static void run_update_post_hook(struct command *commands)
 
        for (argc = 1, cmd = commands; cmd; cmd = cmd->next) {
                char *p;
-               if (cmd->error_string)
+               if (cmd->error_string || cmd->did_not_exist)
                        continue;
                p = xmalloc(strlen(cmd->ref_name) + 1);
                strcpy(p, cmd->ref_name);
@@ -554,7 +590,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
        int flag;
 
        strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
-       dst_name = resolve_ref(buf.buf, sha1, 0, &flag);
+       dst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag);
        strbuf_release(&buf);
 
        if (!(flag & REF_ISSYMREF))
@@ -606,12 +642,56 @@ static void check_aliased_updates(struct command *commands)
        }
        sort_string_list(&ref_list);
 
-       for (cmd = commands; cmd; cmd = cmd->next)
-               check_aliased_update(cmd, &ref_list);
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (!cmd->error_string)
+                       check_aliased_update(cmd, &ref_list);
+       }
 
        string_list_clear(&ref_list, 0);
 }
 
+static int command_singleton_iterator(void *cb_data, unsigned char sha1[20])
+{
+       struct command **cmd_list = cb_data;
+       struct command *cmd = *cmd_list;
+
+       if (!cmd || is_null_sha1(cmd->new_sha1))
+               return -1; /* end of list */
+       *cmd_list = NULL; /* this returns only one */
+       hashcpy(sha1, cmd->new_sha1);
+       return 0;
+}
+
+static void set_connectivity_errors(struct command *commands)
+{
+       struct command *cmd;
+
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               struct command *singleton = cmd;
+               if (!check_everything_connected(command_singleton_iterator,
+                                               0, &singleton))
+                       continue;
+               cmd->error_string = "missing necessary objects";
+       }
+}
+
+static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
+{
+       struct command **cmd_list = cb_data;
+       struct command *cmd = *cmd_list;
+
+       while (cmd) {
+               if (!is_null_sha1(cmd->new_sha1)) {
+                       hashcpy(sha1, cmd->new_sha1);
+                       *cmd_list = cmd->next;
+                       return 0;
+               }
+               cmd = cmd->next;
+       }
+       *cmd_list = NULL;
+       return -1; /* end of list */
+}
+
 static void execute_commands(struct command *commands, const char *unpacker_error)
 {
        struct command *cmd;
@@ -623,19 +703,33 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
                return;
        }
 
-       if (run_receive_hook(commands, pre_receive_hook)) {
-               for (cmd = commands; cmd; cmd = cmd->next)
-                       cmd->error_string = "pre-receive hook declined";
+       cmd = commands;
+       if (check_everything_connected(iterate_receive_command_list,
+                                      0, &cmd))
+               set_connectivity_errors(commands);
+
+       if (run_receive_hook(commands, pre_receive_hook, 0)) {
+               for (cmd = commands; cmd; cmd = cmd->next) {
+                       if (!cmd->error_string)
+                               cmd->error_string = "pre-receive hook declined";
+               }
                return;
        }
 
        check_aliased_updates(commands);
 
-       head_name = resolve_ref("HEAD", sha1, 0, NULL);
+       free(head_name_to_free);
+       head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL);
 
-       for (cmd = commands; cmd; cmd = cmd->next)
-               if (!cmd->skip_update)
-                       cmd->error_string = update(cmd);
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (cmd->error_string)
+                       continue;
+
+               if (cmd->skip_update)
+                       continue;
+
+               cmd->error_string = update(cmd);
+       }
 }
 
 static struct command *read_head_info(void)
@@ -665,10 +759,13 @@ static struct command *read_head_info(void)
                refname = line + 82;
                reflen = strlen(refname);
                if (reflen + 82 < len) {
-                       if (strstr(refname + reflen + 1, "report-status"))
+                       const char *feature_list = refname + reflen + 1;
+                       if (parse_feature_request(feature_list, "report-status"))
                                report_status = 1;
-                       if (strstr(refname + reflen + 1, "side-band-64k"))
+                       if (parse_feature_request(feature_list, "side-band-64k"))
                                use_sideband = LARGE_PACKET_MAX;
+                       if (parse_feature_request(feature_list, "quiet"))
+                               quiet = 1;
                }
                cmd = xcalloc(1, sizeof(struct command) + len - 80);
                hashcpy(cmd->old_sha1, old_sha1);
@@ -707,6 +804,11 @@ static const char *unpack(void)
        struct pack_header hdr;
        const char *hdr_err;
        char hdr_arg[38];
+       int fsck_objects = (receive_fsck_objects >= 0
+                           ? receive_fsck_objects
+                           : transfer_fsck_objects >= 0
+                           ? transfer_fsck_objects
+                           : 0);
 
        hdr_err = parse_pack_header(&hdr);
        if (hdr_err)
@@ -717,9 +819,11 @@ static const char *unpack(void)
 
        if (ntohl(hdr.hdr_entries) < unpack_limit) {
                int code, i = 0;
-               const char *unpacker[4];
+               const char *unpacker[5];
                unpacker[i++] = "unpack-objects";
-               if (receive_fsck_objects)
+               if (quiet)
+                       unpacker[i++] = "-q";
+               if (fsck_objects)
                        unpacker[i++] = "--strict";
                unpacker[i++] = hdr_arg;
                unpacker[i++] = NULL;
@@ -739,7 +843,7 @@ static const char *unpack(void)
 
                keeper[i++] = "index-pack";
                keeper[i++] = "--stdin";
-               if (receive_fsck_objects)
+               if (fsck_objects)
                        keeper[i++] = "--strict";
                keeper[i++] = "--fix-thin";
                keeper[i++] = hdr_arg;
@@ -798,25 +902,6 @@ static int delete_only(struct command *commands)
        return 1;
 }
 
-static void add_one_alternate_sha1(const unsigned char sha1[20], void *unused)
-{
-       add_extra_ref(".have", sha1, 0);
-}
-
-static void collect_one_alternate_ref(const struct ref *ref, void *data)
-{
-       struct sha1_array *sa = data;
-       sha1_array_append(sa, ref->old_sha1);
-}
-
-static void add_alternate_refs(void)
-{
-       struct sha1_array sa = SHA1_ARRAY_INIT;
-       for_each_alternate_ref(collect_one_alternate_ref, &sa);
-       sha1_array_for_each_unique(&sa, add_one_alternate_sha1, NULL);
-       sha1_array_clear(&sa);
-}
-
 int cmd_receive_pack(int argc, const char **argv, const char *prefix)
 {
        int advertise_refs = 0;
@@ -832,6 +917,11 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                const char *arg = *argv++;
 
                if (*arg == '-') {
+                       if (!strcmp(arg, "--quiet")) {
+                               quiet = 1;
+                               continue;
+                       }
+
                        if (!strcmp(arg, "--advertise-refs")) {
                                advertise_refs = 1;
                                continue;
@@ -866,12 +956,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                unpack_limit = receive_unpack_limit;
 
        if (advertise_refs || !stateless_rpc) {
-               add_alternate_refs();
                write_head_info();
-               clear_extra_refs();
-
-               /* EOF */
-               packet_flush(1);
        }
        if (advertise_refs)
                return 0;
@@ -886,7 +971,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                        unlink_or_warn(pack_lockfile);
                if (report_status)
                        report(commands, unpack_status);
-               run_receive_hook(commands, post_receive_hook);
+               run_receive_hook(commands, post_receive_hook, 1);
                run_update_post_hook(commands);
                if (auto_gc) {
                        const char *argv_gc_auto[] = {
index 3a9c80f3dbfe26d5623c118fc0fcaa257e01b973..062d7dad1b5af720e70adcaa05b60bf68977b05c 100644 (file)
@@ -647,7 +647,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
                init_revisions(&cb.revs, prefix);
                if (cb.verbose)
                        printf("Marking reachable objects...");
-               mark_reachable_objects(&cb.revs, 0);
+               mark_reachable_objects(&cb.revs, 0, NULL);
                if (cb.verbose)
                        putchar('\n');
        }
index e1285becffd768c88b4802425f0f5901cda83f06..adc456ebefce89095a3c5229d1a790719782d436 100644 (file)
@@ -16,7 +16,7 @@ static const char * const builtin_remote_usage[] = {
        "git remote [-v | --verbose] show [-n] <name>",
        "git remote prune [-n | --dry-run] <name>",
        "git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]",
-       "git remote set-branches <name> [--add] <branch>...",
+       "git remote set-branches [--add] <name> <branch>...",
        "git remote set-url <name> <newurl> [<oldurl>]",
        "git remote set-url --add <name> <newurl>",
        "git remote set-url --delete <name> <url>",
@@ -343,8 +343,7 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
        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))
+               if (!ref->peer_ref || !ref_exists(ref->peer_ref->name))
                        string_list_append(&states->new, abbrev_branch(ref->name));
                else
                        string_list_append(&states->tracked, abbrev_branch(ref->name));
@@ -390,8 +389,8 @@ static int get_push_ref_states(const struct ref *remote_refs,
        local_refs = get_local_heads();
        push_map = copy_ref_list(remote_refs);
 
-       match_refs(local_refs, &push_map, remote->push_refspec_nr,
-                  remote->push_refspec, MATCH_REFS_NONE);
+       match_push_refs(local_refs, &push_map, remote->push_refspec_nr,
+                       remote->push_refspec, MATCH_REFS_NONE);
 
        states->push.strdup_strings = 1;
        for (ref = push_map; ref; ref = ref->next) {
@@ -574,7 +573,7 @@ static int read_remote_branches(const char *refname,
        strbuf_addf(&buf, "refs/remotes/%s/", rename->old);
        if (!prefixcmp(refname, buf.buf)) {
                item = string_list_append(rename->remote_branches, xstrdup(refname));
-               symref = resolve_ref(refname, orig_sha1, 1, &flag);
+               symref = resolve_ref_unsafe(refname, orig_sha1, 1, &flag);
                if (flag & REF_ISSYMREF)
                        item->util = xstrdup(symref);
                else
@@ -710,7 +709,7 @@ static int mv(int argc, const char **argv)
                int flag = 0;
                unsigned char sha1[20];
 
-               resolve_ref(item->string, sha1, 1, &flag);
+               read_ref_full(item->string, sha1, 1, &flag);
                if (!(flag & REF_ISSYMREF))
                        continue;
                if (delete_ref(item->string, NULL, REF_NODEREF))
@@ -1220,10 +1219,9 @@ static int set_head(int argc, const char **argv)
                usage_with_options(builtin_remote_sethead_usage, options);
 
        if (head_name) {
-               unsigned char sha1[20];
                strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
                /* make sure it's valid */
-               if (!resolve_ref(buf2.buf, sha1, 1, NULL))
+               if (!ref_exists(buf2.buf))
                        result |= error("Not a valid ref: %s", buf2.buf);
                else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
                        result |= error("Could not setup %s", buf.buf);
@@ -1399,7 +1397,7 @@ static int set_branches(int argc, const char **argv)
                             builtin_remote_setbranches_usage, 0);
        if (argc == 0) {
                error("no remote specified");
-               usage_with_options(builtin_remote_seturl_usage, options);
+               usage_with_options(builtin_remote_setbranches_usage, options);
        }
        argv[argc] = NULL;
 
@@ -1427,7 +1425,7 @@ static int set_url(int argc, const char **argv)
                            "delete URLs"),
                OPT_END()
        };
-       argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_seturl_usage,
                             PARSE_OPT_KEEP_ARGV0);
 
        if (add_mode && delete_mode)
index fe3a647a36c9a064d32c8bf9d9868da041bd82a2..4a8970e9c95c1e07c1ed919d40f33238dcd05588 100644 (file)
@@ -58,7 +58,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
                        had_error = 1;
                        continue;
                }
-               if (!resolve_ref(ref, sha1, 1, NULL)) {
+               if (read_ref(ref, sha1)) {
                        error("replace ref '%s' not found.", *p);
                        had_error = 1;
                        continue;
@@ -94,10 +94,10 @@ static int replace_object(const char *object_ref, const char *replace_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))
+       if (check_refname_format(ref, 0))
                die("'%s' is not a valid ref name.", ref);
 
-       if (!resolve_ref(ref, prev, 1, NULL))
+       if (read_ref(ref, prev))
                hashclr(prev);
        else if (!force)
                die("replace ref '%s' already exists", ref);
index 811e8e252c1c6a54e65179557203daf2bc42bdb9..8c2c1d52a227334a3d6456bf0989cd561628ffa0 100644 (file)
@@ -43,6 +43,7 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
        int nr = 1;
        int newfd;
        struct tree_desc desc[2];
+       struct tree *tree;
        struct unpack_trees_options opts;
        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 
@@ -84,6 +85,12 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
                return error(_("Failed to find tree of %s."), sha1_to_hex(sha1));
        if (unpack_trees(nr, desc, &opts))
                return -1;
+
+       if (reset_type == MIXED || reset_type == HARD) {
+               tree = parse_tree_indirect(sha1);
+               prime_cache_tree(&active_cache_tree, tree);
+       }
+
        if (write_cache(newfd, active_cache, active_nr) ||
            commit_locked_index(lock))
                return error(_("Could not write new index file."));
index 56727e8c1d9d87fe3e3bf3919ba86d27d0735758..4c4d404afc7321a222d3dc136b570ada3d3c2317 100644 (file)
@@ -52,6 +52,11 @@ static void show_commit(struct commit *commit, void *data)
        struct rev_list_info *info = data;
        struct rev_info *revs = info->revs;
 
+       if (info->flags & REV_LIST_QUIET) {
+               finish_commit(commit, data);
+               return;
+       }
+
        graph_show_commit(revs->graph);
 
        if (revs->count) {
@@ -168,29 +173,26 @@ static void finish_commit(struct commit *commit, void *data)
        commit->buffer = NULL;
 }
 
-static void finish_object(struct object *obj, const struct name_path *path, const char *name)
+static void finish_object(struct object *obj,
+                         const struct name_path *path, const char *name,
+                         void *cb_data)
 {
+       struct rev_list_info *info = cb_data;
        if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1))
                die("missing blob object '%s'", sha1_to_hex(obj->sha1));
+       if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT)
+               parse_object(obj->sha1);
 }
 
-static void show_object(struct object *obj, const struct name_path *path, const char *component)
+static void show_object(struct object *obj,
+                       const struct name_path *path, const char *component,
+                       void *cb_data)
 {
-       char *name = path_name(path, component);
-       /* An object with name "foo\n0000000..." can be used to
-        * confuse downstream "git pack-objects" very badly.
-        */
-       const char *ep = strchr(name, '\n');
-
-       finish_object(obj, path, name);
-       if (ep) {
-               printf("%s %.*s\n", sha1_to_hex(obj->sha1),
-                      (int) (ep - name),
-                      name);
-       }
-       else
-               printf("%s %s\n", sha1_to_hex(obj->sha1), name);
-       free(name);
+       struct rev_list_info *info = cb_data;
+       finish_object(obj, path, component, cb_data);
+       if (info->flags & REV_LIST_QUIET)
+               return;
+       show_object_with_name(stdout, obj, path, component);
 }
 
 static void show_edge(struct commit *commit)
@@ -247,13 +249,6 @@ void print_commit_list(struct commit_list *list,
        }
 }
 
-static void show_tried_revs(struct commit_list *tried)
-{
-       printf("bisect_tried='");
-       print_commit_list(tried, "%s|", "%s");
-       printf("'\n");
-}
-
 static void print_var_str(const char *var, const char *val)
 {
        printf("%s='%s'\n", var, val);
@@ -266,12 +261,12 @@ static void print_var_int(const char *var, int val)
 
 static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
 {
-       int cnt, flags = info->bisect_show_flags;
+       int cnt, flags = info->flags;
        char hex[41] = "";
        struct commit_list *tried;
        struct rev_info *revs = info->revs;
 
-       if (!revs->commits && !(flags & BISECT_SHOW_TRIED))
+       if (!revs->commits)
                return 1;
 
        revs->commits = filter_skipped(revs->commits, &tried,
@@ -299,9 +294,6 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
                printf("------\n");
        }
 
-       if (flags & BISECT_SHOW_TRIED)
-               show_tried_revs(tried);
-
        print_var_str("bisect_rev", hex);
        print_var_int("bisect_nr", cnt - 1);
        print_var_int("bisect_good", all - reaches - 1);
@@ -320,7 +312,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        int bisect_list = 0;
        int bisect_show_vars = 0;
        int bisect_find_all = 0;
-       int quiet = 0;
 
        git_config(git_default_config, NULL);
        init_revisions(&revs, prefix);
@@ -333,7 +324,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        if (revs.bisect)
                bisect_list = 1;
 
-       quiet = DIFF_OPT_TST(&revs.diffopt, QUICK);
+       if (DIFF_OPT_TST(&revs.diffopt, QUICK))
+               info.flags |= REV_LIST_QUIET;
        for (i = 1 ; i < argc; i++) {
                const char *arg = argv[i];
 
@@ -352,7 +344,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                if (!strcmp(arg, "--bisect-all")) {
                        bisect_list = 1;
                        bisect_find_all = 1;
-                       info.bisect_show_flags = BISECT_SHOW_ALL;
+                       info.flags |= BISECT_SHOW_ALL;
                        revs.show_decorations = 1;
                        continue;
                }
@@ -403,10 +395,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                        return show_bisect_vars(&info, reaches, all);
        }
 
-       traverse_commit_list(&revs,
-                            quiet ? finish_commit : show_commit,
-                            quiet ? finish_object : show_object,
-                            &info);
+       traverse_commit_list(&revs, show_commit, show_object, &info);
 
        if (revs.count) {
                if (revs.left_right && revs.cherry_mark)
index 4c19f844a977b6413afcd5df6b4f8bc0875775a8..98d1cbeccacc4b22dee88dab3d0154a3f70afe81 100644 (file)
@@ -468,6 +468,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                return 0;
        }
 
+       if (argc > 2 && !strcmp(argv[1], "--resolve-git-dir")) {
+               const char *gitdir = resolve_gitdir(argv[2]);
+               if (!gitdir)
+                       die("not a gitdir '%s'", argv[2]);
+               puts(gitdir);
+               return 0;
+       }
+
        if (argc > 1 && !strcmp("-h", argv[1]))
                usage(builtin_rev_parse_usage);
 
index 3117776c2c030bec03563f043c4dc8bb34eb17cd..df63794e050b0a27082952f458d93e0b254ce310 100644 (file)
@@ -13,6 +13,8 @@
 #include "rerere.h"
 #include "merge-recursive.h"
 #include "refs.h"
+#include "dir.h"
+#include "sequencer.h"
 
 /*
  * This implements the builtins revert and cherry-pick.
 
 static const char * const revert_usage[] = {
        "git revert [options] <commit-ish>",
+       "git revert <subcommand>",
        NULL
 };
 
 static const char * const cherry_pick_usage[] = {
        "git cherry-pick [options] <commit-ish>",
+       "git cherry-pick <subcommand>",
        NULL
 };
 
-static int edit, no_replay, no_commit, mainline, signoff, allow_ff;
-static enum { REVERT, CHERRY_PICK } action;
-static struct commit *commit;
-static int commit_argc;
-static const char **commit_argv;
-static int allow_rerere_auto;
+enum replay_action { REVERT, CHERRY_PICK };
+enum replay_subcommand {
+       REPLAY_NONE,
+       REPLAY_REMOVE_STATE,
+       REPLAY_CONTINUE,
+       REPLAY_ROLLBACK
+};
+
+struct replay_opts {
+       enum replay_action action;
+       enum replay_subcommand subcommand;
 
-static const char *me;
+       /* Boolean options */
+       int edit;
+       int record_origin;
+       int no_commit;
+       int signoff;
+       int allow_ff;
+       int allow_rerere_auto;
 
-/* Merge strategy. */
-static const char *strategy;
-static const char **xopts;
-static size_t xopts_nr, xopts_alloc;
+       int mainline;
+
+       /* Merge strategy */
+       const char *strategy;
+       const char **xopts;
+       size_t xopts_nr, xopts_alloc;
+
+       /* Only used by REPLAY_NONE */
+       struct rev_info *revs;
+};
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
+static const char *action_name(const struct replay_opts *opts)
+{
+       return opts->action == REVERT ? "revert" : "cherry-pick";
+}
+
 static char *get_encoding(const char *message);
 
-static const char * const *revert_or_cherry_pick_usage(void)
+static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts)
 {
-       return action == REVERT ? revert_usage : cherry_pick_usage;
+       return opts->action == REVERT ? revert_usage : cherry_pick_usage;
 }
 
 static int option_parse_x(const struct option *opt,
                          const char *arg, int unset)
 {
+       struct replay_opts **opts_ptr = opt->value;
+       struct replay_opts *opts = *opts_ptr;
+
        if (unset)
                return 0;
 
-       ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
-       xopts[xopts_nr++] = xstrdup(arg);
+       ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
+       opts->xopts[opts->xopts_nr++] = xstrdup(arg);
        return 0;
 }
 
-static void parse_args(int argc, const char **argv)
+static void verify_opt_compatible(const char *me, const char *base_opt, ...)
+{
+       const char *this_opt;
+       va_list ap;
+
+       va_start(ap, base_opt);
+       while ((this_opt = va_arg(ap, const char *))) {
+               if (va_arg(ap, int))
+                       break;
+       }
+       va_end(ap);
+
+       if (this_opt)
+               die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt);
+}
+
+static void verify_opt_mutually_compatible(const char *me, ...)
 {
-       const char * const * usage_str = revert_or_cherry_pick_usage();
-       int noop;
+       const char *opt1, *opt2 = NULL;
+       va_list ap;
+
+       va_start(ap, me);
+       while ((opt1 = va_arg(ap, const char *))) {
+               if (va_arg(ap, int))
+                       break;
+       }
+       if (opt1) {
+               while ((opt2 = va_arg(ap, const char *))) {
+                       if (va_arg(ap, int))
+                               break;
+               }
+       }
+
+       if (opt1 && opt2)
+               die(_("%s: %s cannot be used with %s"), me, opt1, opt2);
+}
+
+static void parse_args(int argc, const char **argv, struct replay_opts *opts)
+{
+       const char * const * usage_str = revert_or_cherry_pick_usage(opts);
+       const char *me = action_name(opts);
+       int remove_state = 0;
+       int contin = 0;
+       int rollback = 0;
        struct option options[] = {
-               OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
-               OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
-               { OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)",
-                 PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 },
-               OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
-               OPT_INTEGER('m', "mainline", &mainline, "parent number"),
-               OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
-               OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"),
-               OPT_CALLBACK('X', "strategy-option", &xopts, "option",
+               OPT_BOOLEAN(0, "quit", &remove_state, "end revert or cherry-pick sequence"),
+               OPT_BOOLEAN(0, "continue", &contin, "resume revert or cherry-pick sequence"),
+               OPT_BOOLEAN(0, "abort", &rollback, "cancel revert or cherry-pick sequence"),
+               OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"),
+               OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"),
+               OPT_NOOP_NOARG('r', NULL),
+               OPT_BOOLEAN('s', "signoff", &opts->signoff, "add Signed-off-by:"),
+               OPT_INTEGER('m', "mainline", &opts->mainline, "parent number"),
+               OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto),
+               OPT_STRING(0, "strategy", &opts->strategy, "strategy", "merge strategy"),
+               OPT_CALLBACK('X', "strategy-option", &opts, "option",
                        "option for merge strategy", option_parse_x),
                OPT_END(),
                OPT_END(),
                OPT_END(),
        };
 
-       if (action == CHERRY_PICK) {
+       if (opts->action == CHERRY_PICK) {
                struct option cp_extra[] = {
-                       OPT_BOOLEAN('x', NULL, &no_replay, "append commit name"),
-                       OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"),
+                       OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"),
+                       OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"),
                        OPT_END(),
                };
                if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
                        die(_("program error"));
        }
 
-       commit_argc = parse_options(argc, argv, NULL, options, usage_str,
-                                   PARSE_OPT_KEEP_ARGV0 |
-                                   PARSE_OPT_KEEP_UNKNOWN);
-       if (commit_argc < 2)
-               usage_with_options(usage_str, options);
+       argc = parse_options(argc, argv, NULL, options, usage_str,
+                       PARSE_OPT_KEEP_ARGV0 |
+                       PARSE_OPT_KEEP_UNKNOWN);
+
+       /* Check for incompatible subcommands */
+       verify_opt_mutually_compatible(me,
+                               "--quit", remove_state,
+                               "--continue", contin,
+                               "--abort", rollback,
+                               NULL);
+
+       /* Set the subcommand */
+       if (remove_state)
+               opts->subcommand = REPLAY_REMOVE_STATE;
+       else if (contin)
+               opts->subcommand = REPLAY_CONTINUE;
+       else if (rollback)
+               opts->subcommand = REPLAY_ROLLBACK;
+       else
+               opts->subcommand = REPLAY_NONE;
+
+       /* Check for incompatible command line arguments */
+       if (opts->subcommand != REPLAY_NONE) {
+               char *this_operation;
+               if (opts->subcommand == REPLAY_REMOVE_STATE)
+                       this_operation = "--quit";
+               else if (opts->subcommand == REPLAY_CONTINUE)
+                       this_operation = "--continue";
+               else {
+                       assert(opts->subcommand == REPLAY_ROLLBACK);
+                       this_operation = "--abort";
+               }
 
-       commit_argv = argv;
+               verify_opt_compatible(me, this_operation,
+                               "--no-commit", opts->no_commit,
+                               "--signoff", opts->signoff,
+                               "--mainline", opts->mainline,
+                               "--strategy", opts->strategy ? 1 : 0,
+                               "--strategy-option", opts->xopts ? 1 : 0,
+                               "-x", opts->record_origin,
+                               "--ff", opts->allow_ff,
+                               NULL);
+       }
+
+       if (opts->allow_ff)
+               verify_opt_compatible(me, "--ff",
+                               "--signoff", opts->signoff,
+                               "--no-commit", opts->no_commit,
+                               "-x", opts->record_origin,
+                               "--edit", opts->edit,
+                               NULL);
+
+       if (opts->subcommand != REPLAY_NONE) {
+               opts->revs = NULL;
+       } else {
+               opts->revs = xmalloc(sizeof(*opts->revs));
+               init_revisions(opts->revs, NULL);
+               opts->revs->no_walk = 1;
+               if (argc < 2)
+                       usage_with_options(usage_str, options);
+               argc = setup_revisions(argc, argv, opts->revs, NULL);
+       }
+
+       if (argc > 1)
+               usage_with_options(usage_str, options);
 }
 
 struct commit_message {
@@ -116,25 +245,25 @@ struct commit_message {
        const char *message;
 };
 
-static int get_message(const char *raw_message, struct commit_message *out)
+static int get_message(struct commit *commit, struct commit_message *out)
 {
        const char *encoding;
        const char *abbrev, *subject;
        int abbrev_len, subject_len;
        char *q;
 
-       if (!raw_message)
+       if (!commit->buffer)
                return -1;
-       encoding = get_encoding(raw_message);
+       encoding = get_encoding(commit->buffer);
        if (!encoding)
                encoding = "UTF-8";
        if (!git_commit_encoding)
                git_commit_encoding = "UTF-8";
 
        out->reencoded_message = NULL;
-       out->message = raw_message;
+       out->message = commit->buffer;
        if (strcmp(encoding, git_commit_encoding))
-               out->reencoded_message = reencode_string(raw_message,
+               out->reencoded_message = reencode_string(commit->buffer,
                                        git_commit_encoding, encoding);
        if (out->reencoded_message)
                out->message = out->reencoded_message;
@@ -167,9 +296,6 @@ static char *get_encoding(const char *message)
 {
        const char *p = message, *eol;
 
-       if (!p)
-               die (_("Could not read commit message of %s"),
-                               sha1_to_hex(commit->object.sha1));
        while (*p && *p != '\n') {
                for (eol = p + 1; *eol && *eol != '\n'; eol++)
                        ; /* do nothing */
@@ -185,45 +311,24 @@ static char *get_encoding(const char *message)
        return NULL;
 }
 
-static void add_message_to_msg(struct strbuf *msgbuf, const char *message)
-{
-       const char *p = message;
-       while (*p && (*p != '\n' || p[1] != '\n'))
-               p++;
-
-       if (!*p)
-               strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1));
-
-       p += 2;
-       strbuf_addstr(msgbuf, p);
-}
-
-static void write_cherry_pick_head(void)
+static void write_cherry_pick_head(struct commit *commit, const char *pseudoref)
 {
+       const char *filename;
        int fd;
        struct strbuf buf = STRBUF_INIT;
 
        strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
 
-       fd = open(git_path("CHERRY_PICK_HEAD"), O_WRONLY | O_CREAT, 0666);
+       filename = git_path("%s", pseudoref);
+       fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
-               die_errno(_("Could not open '%s' for writing"),
-                         git_path("CHERRY_PICK_HEAD"));
+               die_errno(_("Could not open '%s' for writing"), filename);
        if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
-               die_errno(_("Could not write to '%s'"), git_path("CHERRY_PICK_HEAD"));
+               die_errno(_("Could not write to '%s'"), filename);
        strbuf_release(&buf);
 }
 
-static void advise(const char *advice, ...)
-{
-       va_list params;
-
-       va_start(params, advice);
-       vreportf("hint: ", advice, params);
-       va_end(params);
-}
-
-static void print_advice(void)
+static void print_advice(int show_hint)
 {
        char *msg = getenv("GIT_CHERRY_PICK_HELP");
 
@@ -238,9 +343,10 @@ static void print_advice(void)
                return;
        }
 
-       advise("after resolving the conflicts, mark the corrected paths");
-       advise("with 'git add <paths>' or 'git rm <paths>'");
-       advise("and commit the result with 'git commit'");
+       if (show_hint)
+               advise(_("after resolving the conflicts, mark the corrected paths\n"
+                        "with 'git add <paths>' or 'git rm <paths>'\n"
+                        "and commit the result with 'git commit'"));
 }
 
 static void write_message(struct strbuf *msgbuf, const char *filename)
@@ -250,7 +356,7 @@ static void write_message(struct strbuf *msgbuf, const char *filename)
        int msg_fd = hold_lock_file_for_update(&msg_file, filename,
                                               LOCK_DIE_ON_ERROR);
        if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
-               die_errno(_("Could not write to %s."), filename);
+               die_errno(_("Could not write to %s"), filename);
        strbuf_release(msgbuf);
        if (commit_lock_file(&msg_file) < 0)
                die(_("Error wrapping up %s"), filename);
@@ -261,25 +367,20 @@ static struct tree *empty_tree(void)
        return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
 }
 
-static NORETURN void die_dirty_index(const char *me)
+static int error_dirty_index(struct replay_opts *opts)
 {
-       if (read_cache_unmerged()) {
-               die_resolve_conflict(me);
-       } else {
-               if (advice_commit_before_merge) {
-                       if (action == REVERT)
-                               die(_("Your local changes would be overwritten by revert.\n"
-                                         "Please, commit your changes or stash them to proceed."));
-                       else
-                               die(_("Your local changes would be overwritten by cherry-pick.\n"
-                                         "Please, commit your changes or stash them to proceed."));
-               } else {
-                       if (action == REVERT)
-                               die(_("Your local changes would be overwritten by revert.\n"));
-                       else
-                               die(_("Your local changes would be overwritten by cherry-pick.\n"));
-               }
-       }
+       if (read_cache_unmerged())
+               return error_resolve_conflict(action_name(opts));
+
+       /* Different translation strings for cherry-pick and revert */
+       if (opts->action == CHERRY_PICK)
+               error(_("Your local changes would be overwritten by cherry-pick."));
+       else
+               error(_("Your local changes would be overwritten by revert."));
+
+       if (advice_commit_before_merge)
+               advise(_("Commit your changes or stash them to proceed."));
+       return -1;
 }
 
 static int fast_forward_to(const unsigned char *to, const unsigned char *from)
@@ -295,7 +396,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from)
 
 static int do_recursive_merge(struct commit *base, struct commit *next,
                              const char *base_label, const char *next_label,
-                             unsigned char *head, struct strbuf *msgbuf)
+                             unsigned char *head, struct strbuf *msgbuf,
+                             struct replay_opts *opts)
 {
        struct merge_options o;
        struct tree *result, *next_tree, *base_tree, *head_tree;
@@ -316,7 +418,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        next_tree = next ? next->tree : empty_tree();
        base_tree = base ? base->tree : empty_tree();
 
-       for (xopt = xopts; xopt != xopts + xopts_nr; xopt++)
+       for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
                parse_merge_opt(&o, *xopt);
 
        clean = merge_trees(&o,
@@ -327,7 +429,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
            (write_cache(index_fd, active_cache, active_nr) ||
             commit_locked_index(&index_lock)))
                /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
-               die(_("%s: Unable to write new index file"), me);
+               die(_("%s: Unable to write new index file"), action_name(opts));
        rollback_lock_file(&index_lock);
 
        if (!clean) {
@@ -356,7 +458,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  * If we are revert, or if our cherry-pick results in a hand merge,
  * we had better say that the current user is responsible for that.
  */
-static int run_git_commit(const char *defmsg)
+static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 {
        /* 6 is max possible length of our args array including NULL */
        const char *args[6];
@@ -364,9 +466,9 @@ static int run_git_commit(const char *defmsg)
 
        args[i++] = "commit";
        args[i++] = "-n";
-       if (signoff)
+       if (opts->signoff)
                args[i++] = "-s";
-       if (!edit) {
+       if (!opts->edit) {
                args[i++] = "-F";
                args[i++] = defmsg;
        }
@@ -375,7 +477,7 @@ static int run_git_commit(const char *defmsg)
        return run_command_v_opt(args, RUN_GIT_CMD);
 }
 
-static int do_pick_commit(void)
+static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 {
        unsigned char head[20];
        struct commit *base, *next, *parent;
@@ -385,7 +487,7 @@ static int do_pick_commit(void)
        struct strbuf msgbuf = STRBUF_INIT;
        int res;
 
-       if (no_commit) {
+       if (opts->no_commit) {
                /*
                 * We do not intend to commit immediately.  We just want to
                 * merge the differences in, so let's compute the tree
@@ -396,9 +498,9 @@ static int do_pick_commit(void)
                        die (_("Your index file is unmerged."));
        } else {
                if (get_sha1("HEAD", head))
-                       die (_("You do not have a valid HEAD"));
+                       return error(_("You do not have a valid HEAD"));
                if (index_differs_from("HEAD", 0))
-                       die_dirty_index(me);
+                       return error_dirty_index(opts);
        }
        discard_cache();
 
@@ -410,36 +512,36 @@ static int do_pick_commit(void)
                int cnt;
                struct commit_list *p;
 
-               if (!mainline)
-                       die(_("Commit %s is a merge but no -m option was given."),
-                           sha1_to_hex(commit->object.sha1));
+               if (!opts->mainline)
+                       return error(_("Commit %s is a merge but no -m option was given."),
+                               sha1_to_hex(commit->object.sha1));
 
                for (cnt = 1, p = commit->parents;
-                    cnt != mainline && p;
+                    cnt != opts->mainline && p;
                     cnt++)
                        p = p->next;
-               if (cnt != mainline || !p)
-                       die(_("Commit %s does not have parent %d"),
-                           sha1_to_hex(commit->object.sha1), mainline);
+               if (cnt != opts->mainline || !p)
+                       return error(_("Commit %s does not have parent %d"),
+                               sha1_to_hex(commit->object.sha1), opts->mainline);
                parent = p->item;
-       } else if (0 < mainline)
-               die(_("Mainline was specified but commit %s is not a merge."),
-                   sha1_to_hex(commit->object.sha1));
+       } else if (0 < opts->mainline)
+               return error(_("Mainline was specified but commit %s is not a merge."),
+                       sha1_to_hex(commit->object.sha1));
        else
                parent = commit->parents->item;
 
-       if (allow_ff && parent && !hashcmp(parent->object.sha1, head))
+       if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head))
                return fast_forward_to(commit->object.sha1, head);
 
        if (parent && parse_commit(parent) < 0)
                /* TRANSLATORS: The first %s will be "revert" or
                   "cherry-pick", the second %s a SHA1 */
-               die(_("%s: cannot parse parent commit %s"),
-                   me, sha1_to_hex(parent->object.sha1));
+               return error(_("%s: cannot parse parent commit %s"),
+                       action_name(opts), sha1_to_hex(parent->object.sha1));
 
-       if (get_message(commit->buffer, &msg) != 0)
-               die(_("Cannot get commit message for %s"),
-                               sha1_to_hex(commit->object.sha1));
+       if (get_message(commit, &msg) != 0)
+               return error(_("Cannot get commit message for %s"),
+                       sha1_to_hex(commit->object.sha1));
 
        /*
         * "commit" is an existing commit.  We would want to apply
@@ -450,7 +552,7 @@ static int do_pick_commit(void)
 
        defmsg = git_pathdup("MERGE_MSG");
 
-       if (action == REVERT) {
+       if (opts->action == REVERT) {
                base = commit;
                base_label = msg.label;
                next = parent;
@@ -466,23 +568,34 @@ static int do_pick_commit(void)
                }
                strbuf_addstr(&msgbuf, ".\n");
        } else {
+               const char *p;
+
                base = parent;
                base_label = msg.parent_label;
                next = commit;
                next_label = msg.label;
-               add_message_to_msg(&msgbuf, msg.message);
-               if (no_replay) {
+
+               /*
+                * Append the commit log message to msgbuf; it starts
+                * after the tree, parent, author, committer
+                * information followed by "\n\n".
+                */
+               p = strstr(msg.message, "\n\n");
+               if (p) {
+                       p += 2;
+                       strbuf_addstr(&msgbuf, p);
+               }
+
+               if (opts->record_origin) {
                        strbuf_addstr(&msgbuf, "(cherry picked from commit ");
                        strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
                        strbuf_addstr(&msgbuf, ")\n");
                }
-               if (!no_commit)
-                       write_cherry_pick_head();
        }
 
-       if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
+       if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REVERT) {
                res = do_recursive_merge(base, next, base_label, next_label,
-                                        head, &msgbuf);
+                                        head, &msgbuf, opts);
                write_message(&msgbuf, defmsg);
        } else {
                struct commit_list *common = NULL;
@@ -492,23 +605,34 @@ static int do_pick_commit(void)
 
                commit_list_insert(base, &common);
                commit_list_insert(next, &remotes);
-               res = try_merge_command(strategy, xopts_nr, xopts, common,
-                                       sha1_to_hex(head), remotes);
+               res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts,
+                                       common, sha1_to_hex(head), remotes);
                free_commit_list(common);
                free_commit_list(remotes);
        }
 
+       /*
+        * If the merge was clean or if it failed due to conflict, we write
+        * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
+        * However, if the merge did not even start, then we don't want to
+        * write it at all.
+        */
+       if (opts->action == CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1))
+               write_cherry_pick_head(commit, "CHERRY_PICK_HEAD");
+       if (opts->action == REVERT && ((opts->no_commit && res == 0) || res == 1))
+               write_cherry_pick_head(commit, "REVERT_HEAD");
+
        if (res) {
-               error(action == REVERT
+               error(opts->action == REVERT
                      ? _("could not revert %s... %s")
                      : _("could not apply %s... %s"),
                      find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
                      msg.subject);
-               print_advice();
-               rerere(allow_rerere_auto);
+               print_advice(res == 1);
+               rerere(opts->allow_rerere_auto);
        } else {
-               if (!no_commit)
-                       res = run_git_commit(defmsg);
+               if (!opts->no_commit)
+                       res = run_git_commit(defmsg, opts);
        }
 
        free_message(&msg);
@@ -517,84 +641,525 @@ static int do_pick_commit(void)
        return res;
 }
 
-static void prepare_revs(struct rev_info *revs)
+static void prepare_revs(struct replay_opts *opts)
 {
-       int argc;
-
-       init_revisions(revs, NULL);
-       revs->no_walk = 1;
-       if (action != REVERT)
-               revs->reverse = 1;
-
-       argc = setup_revisions(commit_argc, commit_argv, revs, NULL);
-       if (argc > 1)
-               usage(*revert_or_cherry_pick_usage());
+       if (opts->action != REVERT)
+               opts->revs->reverse ^= 1;
 
-       if (prepare_revision_walk(revs))
+       if (prepare_revision_walk(opts->revs))
                die(_("revision walk setup failed"));
 
-       if (!revs->commits)
+       if (!opts->revs->commits)
                die(_("empty commit set passed"));
 }
 
-static void read_and_refresh_cache(const char *me)
+static void read_and_refresh_cache(struct replay_opts *opts)
 {
        static struct lock_file index_lock;
        int index_fd = hold_locked_index(&index_lock, 0);
        if (read_index_preload(&the_index, NULL) < 0)
-               die(_("git %s: failed to read the index"), me);
+               die(_("git %s: failed to read the index"), action_name(opts));
        refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
        if (the_index.cache_changed) {
                if (write_index(&the_index, index_fd) ||
                    commit_locked_index(&index_lock))
-                       die(_("git %s: failed to refresh the index"), me);
+                       die(_("git %s: failed to refresh the index"), action_name(opts));
        }
        rollback_lock_file(&index_lock);
 }
 
-static int revert_or_cherry_pick(int argc, const char **argv)
+/*
+ * Append a commit to the end of the commit_list.
+ *
+ * next starts by pointing to the variable that holds the head of an
+ * empty commit_list, and is updated to point to the "next" field of
+ * the last item on the list as new commits are appended.
+ *
+ * Usage example:
+ *
+ *     struct commit_list *list;
+ *     struct commit_list **next = &list;
+ *
+ *     next = commit_list_append(c1, next);
+ *     next = commit_list_append(c2, next);
+ *     assert(commit_list_count(list) == 2);
+ *     return list;
+ */
+static struct commit_list **commit_list_append(struct commit *commit,
+                                              struct commit_list **next)
 {
-       struct rev_info revs;
+       struct commit_list *new = xmalloc(sizeof(struct commit_list));
+       new->item = commit;
+       *next = new;
+       new->next = NULL;
+       return &new->next;
+}
 
-       git_config(git_default_config, NULL);
-       me = action == REVERT ? "revert" : "cherry-pick";
-       setenv(GIT_REFLOG_ACTION, me, 0);
-       parse_args(argc, argv);
+static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
+               struct replay_opts *opts)
+{
+       struct commit_list *cur = NULL;
+       const char *sha1_abbrev = NULL;
+       const char *action_str = opts->action == REVERT ? "revert" : "pick";
+       const char *subject;
+       int subject_len;
+
+       for (cur = todo_list; cur; cur = cur->next) {
+               sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
+               subject_len = find_commit_subject(cur->item->buffer, &subject);
+               strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev,
+                       subject_len, subject);
+       }
+       return 0;
+}
+
+static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts)
+{
+       unsigned char commit_sha1[20];
+       enum replay_action action;
+       char *end_of_object_name;
+       int saved, status, padding;
+
+       if (!prefixcmp(bol, "pick")) {
+               action = CHERRY_PICK;
+               bol += strlen("pick");
+       } else if (!prefixcmp(bol, "revert")) {
+               action = REVERT;
+               bol += strlen("revert");
+       } else
+               return NULL;
+
+       /* Eat up extra spaces/ tabs before object name */
+       padding = strspn(bol, " \t");
+       if (!padding)
+               return NULL;
+       bol += padding;
+
+       end_of_object_name = bol + strcspn(bol, " \t\n");
+       saved = *end_of_object_name;
+       *end_of_object_name = '\0';
+       status = get_sha1(bol, commit_sha1);
+       *end_of_object_name = saved;
+
+       /*
+        * Verify that the action matches up with the one in
+        * opts; we don't support arbitrary instructions
+        */
+       if (action != opts->action) {
+               const char *action_str;
+               action_str = action == REVERT ? "revert" : "cherry-pick";
+               error(_("Cannot %s during a %s"), action_str, action_name(opts));
+               return NULL;
+       }
+
+       if (status < 0)
+               return NULL;
+
+       return lookup_commit_reference(commit_sha1);
+}
+
+static int parse_insn_buffer(char *buf, struct commit_list **todo_list,
+                       struct replay_opts *opts)
+{
+       struct commit_list **next = todo_list;
+       struct commit *commit;
+       char *p = buf;
+       int i;
+
+       for (i = 1; *p; i++) {
+               char *eol = strchrnul(p, '\n');
+               commit = parse_insn_line(p, eol, opts);
+               if (!commit)
+                       return error(_("Could not parse line %d."), i);
+               next = commit_list_append(commit, next);
+               p = *eol ? eol + 1 : eol;
+       }
+       if (!*todo_list)
+               return error(_("No commits parsed."));
+       return 0;
+}
+
+static void read_populate_todo(struct commit_list **todo_list,
+                       struct replay_opts *opts)
+{
+       const char *todo_file = git_path(SEQ_TODO_FILE);
+       struct strbuf buf = STRBUF_INIT;
+       int fd, res;
+
+       fd = open(todo_file, O_RDONLY);
+       if (fd < 0)
+               die_errno(_("Could not open %s"), todo_file);
+       if (strbuf_read(&buf, fd, 0) < 0) {
+               close(fd);
+               strbuf_release(&buf);
+               die(_("Could not read %s."), todo_file);
+       }
+       close(fd);
+
+       res = parse_insn_buffer(buf.buf, todo_list, opts);
+       strbuf_release(&buf);
+       if (res)
+               die(_("Unusable instruction sheet: %s"), todo_file);
+}
+
+static int populate_opts_cb(const char *key, const char *value, void *data)
+{
+       struct replay_opts *opts = data;
+       int error_flag = 1;
+
+       if (!value)
+               error_flag = 0;
+       else if (!strcmp(key, "options.no-commit"))
+               opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
+       else if (!strcmp(key, "options.edit"))
+               opts->edit = git_config_bool_or_int(key, value, &error_flag);
+       else if (!strcmp(key, "options.signoff"))
+               opts->signoff = git_config_bool_or_int(key, value, &error_flag);
+       else if (!strcmp(key, "options.record-origin"))
+               opts->record_origin = git_config_bool_or_int(key, value, &error_flag);
+       else if (!strcmp(key, "options.allow-ff"))
+               opts->allow_ff = git_config_bool_or_int(key, value, &error_flag);
+       else if (!strcmp(key, "options.mainline"))
+               opts->mainline = git_config_int(key, value);
+       else if (!strcmp(key, "options.strategy"))
+               git_config_string(&opts->strategy, key, value);
+       else if (!strcmp(key, "options.strategy-option")) {
+               ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
+               opts->xopts[opts->xopts_nr++] = xstrdup(value);
+       } else
+               return error(_("Invalid key: %s"), key);
+
+       if (!error_flag)
+               return error(_("Invalid value for %s: %s"), key, value);
+
+       return 0;
+}
+
+static void read_populate_opts(struct replay_opts **opts_ptr)
+{
+       const char *opts_file = git_path(SEQ_OPTS_FILE);
+
+       if (!file_exists(opts_file))
+               return;
+       if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0)
+               die(_("Malformed options sheet: %s"), opts_file);
+}
+
+static void walk_revs_populate_todo(struct commit_list **todo_list,
+                               struct replay_opts *opts)
+{
+       struct commit *commit;
+       struct commit_list **next;
+
+       prepare_revs(opts);
+
+       next = todo_list;
+       while ((commit = get_revision(opts->revs)))
+               next = commit_list_append(commit, next);
+}
+
+static int create_seq_dir(void)
+{
+       const char *seq_dir = git_path(SEQ_DIR);
+
+       if (file_exists(seq_dir)) {
+               error(_("a cherry-pick or revert is already in progress"));
+               advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
+               return -1;
+       }
+       else if (mkdir(seq_dir, 0777) < 0)
+               die_errno(_("Could not create sequencer directory %s"), seq_dir);
+       return 0;
+}
+
+static void save_head(const char *head)
+{
+       const char *head_file = git_path(SEQ_HEAD_FILE);
+       static struct lock_file head_lock;
+       struct strbuf buf = STRBUF_INIT;
+       int fd;
+
+       fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);
+       strbuf_addf(&buf, "%s\n", head);
+       if (write_in_full(fd, buf.buf, buf.len) < 0)
+               die_errno(_("Could not write to %s"), head_file);
+       if (commit_lock_file(&head_lock) < 0)
+               die(_("Error wrapping up %s."), head_file);
+}
+
+static int reset_for_rollback(const unsigned char *sha1)
+{
+       const char *argv[4];    /* reset --merge <arg> + NULL */
+       argv[0] = "reset";
+       argv[1] = "--merge";
+       argv[2] = sha1_to_hex(sha1);
+       argv[3] = NULL;
+       return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int rollback_single_pick(void)
+{
+       unsigned char head_sha1[20];
+
+       if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
+           !file_exists(git_path("REVERT_HEAD")))
+               return error(_("no cherry-pick or revert in progress"));
+       if (read_ref_full("HEAD", head_sha1, 0, NULL))
+               return error(_("cannot resolve HEAD"));
+       if (is_null_sha1(head_sha1))
+               return error(_("cannot abort from a branch yet to be born"));
+       return reset_for_rollback(head_sha1);
+}
+
+static int sequencer_rollback(struct replay_opts *opts)
+{
+       const char *filename;
+       FILE *f;
+       unsigned char sha1[20];
+       struct strbuf buf = STRBUF_INIT;
+
+       filename = git_path(SEQ_HEAD_FILE);
+       f = fopen(filename, "r");
+       if (!f && errno == ENOENT) {
+               /*
+                * There is no multiple-cherry-pick in progress.
+                * If CHERRY_PICK_HEAD or REVERT_HEAD indicates
+                * a single-cherry-pick in progress, abort that.
+                */
+               return rollback_single_pick();
+       }
+       if (!f)
+               return error(_("cannot open %s: %s"), filename,
+                                               strerror(errno));
+       if (strbuf_getline(&buf, f, '\n')) {
+               error(_("cannot read %s: %s"), filename, ferror(f) ?
+                       strerror(errno) : _("unexpected end of file"));
+               fclose(f);
+               goto fail;
+       }
+       fclose(f);
+       if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') {
+               error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"),
+                       filename);
+               goto fail;
+       }
+       if (reset_for_rollback(sha1))
+               goto fail;
+       remove_sequencer_state();
+       strbuf_release(&buf);
+       return 0;
+fail:
+       strbuf_release(&buf);
+       return -1;
+}
+
+static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
+{
+       const char *todo_file = git_path(SEQ_TODO_FILE);
+       static struct lock_file todo_lock;
+       struct strbuf buf = STRBUF_INIT;
+       int fd;
+
+       fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR);
+       if (format_todo(&buf, todo_list, opts) < 0)
+               die(_("Could not format %s."), todo_file);
+       if (write_in_full(fd, buf.buf, buf.len) < 0) {
+               strbuf_release(&buf);
+               die_errno(_("Could not write to %s"), todo_file);
+       }
+       if (commit_lock_file(&todo_lock) < 0) {
+               strbuf_release(&buf);
+               die(_("Error wrapping up %s."), todo_file);
+       }
+       strbuf_release(&buf);
+}
 
-       if (allow_ff) {
-               if (signoff)
-                       die(_("cherry-pick --ff cannot be used with --signoff"));
-               if (no_commit)
-                       die(_("cherry-pick --ff cannot be used with --no-commit"));
-               if (no_replay)
-                       die(_("cherry-pick --ff cannot be used with -x"));
-               if (edit)
-                       die(_("cherry-pick --ff cannot be used with --edit"));
+static void save_opts(struct replay_opts *opts)
+{
+       const char *opts_file = git_path(SEQ_OPTS_FILE);
+
+       if (opts->no_commit)
+               git_config_set_in_file(opts_file, "options.no-commit", "true");
+       if (opts->edit)
+               git_config_set_in_file(opts_file, "options.edit", "true");
+       if (opts->signoff)
+               git_config_set_in_file(opts_file, "options.signoff", "true");
+       if (opts->record_origin)
+               git_config_set_in_file(opts_file, "options.record-origin", "true");
+       if (opts->allow_ff)
+               git_config_set_in_file(opts_file, "options.allow-ff", "true");
+       if (opts->mainline) {
+               struct strbuf buf = STRBUF_INIT;
+               strbuf_addf(&buf, "%d", opts->mainline);
+               git_config_set_in_file(opts_file, "options.mainline", buf.buf);
+               strbuf_release(&buf);
+       }
+       if (opts->strategy)
+               git_config_set_in_file(opts_file, "options.strategy", opts->strategy);
+       if (opts->xopts) {
+               int i;
+               for (i = 0; i < opts->xopts_nr; i++)
+                       git_config_set_multivar_in_file(opts_file,
+                                                       "options.strategy-option",
+                                                       opts->xopts[i], "^$", 0);
        }
+}
 
-       read_and_refresh_cache(me);
+static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
+{
+       struct commit_list *cur;
+       int res;
 
-       prepare_revs(&revs);
+       setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+       if (opts->allow_ff)
+               assert(!(opts->signoff || opts->no_commit ||
+                               opts->record_origin || opts->edit));
+       read_and_refresh_cache(opts);
 
-       while ((commit = get_revision(&revs))) {
-               int res = do_pick_commit();
+       for (cur = todo_list; cur; cur = cur->next) {
+               save_todo(cur, opts);
+               res = do_pick_commit(cur->item, opts);
                if (res)
                        return res;
        }
 
+       /*
+        * Sequence of picks finished successfully; cleanup by
+        * removing the .git/sequencer directory
+        */
+       remove_sequencer_state();
        return 0;
 }
 
+static int continue_single_pick(void)
+{
+       const char *argv[] = { "commit", NULL };
+
+       if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
+           !file_exists(git_path("REVERT_HEAD")))
+               return error(_("no cherry-pick or revert in progress"));
+       return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int sequencer_continue(struct replay_opts *opts)
+{
+       struct commit_list *todo_list = NULL;
+
+       if (!file_exists(git_path(SEQ_TODO_FILE)))
+               return continue_single_pick();
+       read_populate_opts(&opts);
+       read_populate_todo(&todo_list, opts);
+
+       /* Verify that the conflict has been resolved */
+       if (file_exists(git_path("CHERRY_PICK_HEAD")) ||
+           file_exists(git_path("REVERT_HEAD"))) {
+               int ret = continue_single_pick();
+               if (ret)
+                       return ret;
+       }
+       if (index_differs_from("HEAD", 0))
+               return error_dirty_index(opts);
+       todo_list = todo_list->next;
+       return pick_commits(todo_list, opts);
+}
+
+static int single_pick(struct commit *cmit, struct replay_opts *opts)
+{
+       setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+       return do_pick_commit(cmit, opts);
+}
+
+static int pick_revisions(struct replay_opts *opts)
+{
+       struct commit_list *todo_list = NULL;
+       unsigned char sha1[20];
+
+       if (opts->subcommand == REPLAY_NONE)
+               assert(opts->revs);
+
+       read_and_refresh_cache(opts);
+
+       /*
+        * Decide what to do depending on the arguments; a fresh
+        * cherry-pick should be handled differently from an existing
+        * one that is being continued
+        */
+       if (opts->subcommand == REPLAY_REMOVE_STATE) {
+               remove_sequencer_state();
+               return 0;
+       }
+       if (opts->subcommand == REPLAY_ROLLBACK)
+               return sequencer_rollback(opts);
+       if (opts->subcommand == REPLAY_CONTINUE)
+               return sequencer_continue(opts);
+
+       /*
+        * If we were called as "git cherry-pick <commit>", just
+        * cherry-pick/revert it, set CHERRY_PICK_HEAD /
+        * REVERT_HEAD, and don't touch the sequencer state.
+        * This means it is possible to cherry-pick in the middle
+        * of a cherry-pick sequence.
+        */
+       if (opts->revs->cmdline.nr == 1 &&
+           opts->revs->cmdline.rev->whence == REV_CMD_REV &&
+           opts->revs->no_walk &&
+           !opts->revs->cmdline.rev->flags) {
+               struct commit *cmit;
+               if (prepare_revision_walk(opts->revs))
+                       die(_("revision walk setup failed"));
+               cmit = get_revision(opts->revs);
+               if (!cmit || get_revision(opts->revs))
+                       die("BUG: expected exactly one commit from walk");
+               return single_pick(cmit, opts);
+       }
+
+       /*
+        * Start a new cherry-pick/ revert sequence; but
+        * first, make sure that an existing one isn't in
+        * progress
+        */
+
+       walk_revs_populate_todo(&todo_list, opts);
+       if (create_seq_dir() < 0)
+               return -1;
+       if (get_sha1("HEAD", sha1)) {
+               if (opts->action == REVERT)
+                       return error(_("Can't revert as initial commit"));
+               return error(_("Can't cherry-pick into empty head"));
+       }
+       save_head(sha1_to_hex(sha1));
+       save_opts(opts);
+       return pick_commits(todo_list, opts);
+}
+
 int cmd_revert(int argc, const char **argv, const char *prefix)
 {
+       struct replay_opts opts;
+       int res;
+
+       memset(&opts, 0, sizeof(opts));
        if (isatty(0))
-               edit = 1;
-       action = REVERT;
-       return revert_or_cherry_pick(argc, argv);
+               opts.edit = 1;
+       opts.action = REVERT;
+       git_config(git_default_config, NULL);
+       parse_args(argc, argv, &opts);
+       res = pick_revisions(&opts);
+       if (res < 0)
+               die(_("revert failed"));
+       return res;
 }
 
 int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
 {
-       action = CHERRY_PICK;
-       return revert_or_cherry_pick(argc, argv);
+       struct replay_opts opts;
+       int res;
+
+       memset(&opts, 0, sizeof(opts));
+       opts.action = CHERRY_PICK;
+       git_config(git_default_config, NULL);
+       parse_args(argc, argv, &opts);
+       res = pick_revisions(&opts);
+       if (res < 0)
+               die(_("cherry-pick failed"));
+       return res;
 }
index c1f6ddd927d61fbc2558ee3624224af405d918c3..9df341c793d58eff215805bf5ca5da383f4a99d9 100644 (file)
@@ -58,7 +58,7 @@ 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)
+       if (args->quiet || !args->progress)
                argv[i++] = "-q";
        if (args->progress)
                argv[i++] = "--progress";
@@ -250,6 +250,7 @@ int send_pack(struct send_pack_args *args,
        int allow_deleting_refs = 0;
        int status_report = 0;
        int use_sideband = 0;
+       int quiet_supported = 0;
        unsigned cmds_sent = 0;
        int ret;
        struct async demux;
@@ -263,6 +264,8 @@ int send_pack(struct send_pack_args *args,
                args->use_ofs_delta = 1;
        if (server_supports("side-band-64k"))
                use_sideband = 1;
+       if (server_supports("quiet"))
+               quiet_supported = 1;
 
        if (!remote_refs) {
                fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
@@ -300,16 +303,18 @@ int send_pack(struct send_pack_args *args,
                } else {
                        char *old_hex = sha1_to_hex(ref->old_sha1);
                        char *new_hex = sha1_to_hex(ref->new_sha1);
-
-                       if (!cmds_sent && (status_report || use_sideband)) {
-                               packet_buf_write(&req_buf, "%s %s %s%c%s%s",
-                                       old_hex, new_hex, ref->name, 0,
-                                       status_report ? " report-status" : "",
-                                       use_sideband ? " side-band-64k" : "");
+                       int quiet = quiet_supported && (args->quiet || !args->progress);
+
+                       if (!cmds_sent && (status_report || use_sideband || args->quiet)) {
+                               packet_buf_write(&req_buf, "%s %s %s%c%s%s%s",
+                                                old_hex, new_hex, ref->name, 0,
+                                                status_report ? " report-status" : "",
+                                                use_sideband ? " side-band-64k" : "",
+                                                quiet ? " quiet" : "");
                        }
                        else
                                packet_buf_write(&req_buf, "%s %s %s",
-                                       old_hex, new_hex, ref->name);
+                                                old_hex, new_hex, ref->name);
                        ref->status = status_report ?
                                REF_STATUS_EXPECTING_REPORT :
                                REF_STATUS_OK;
@@ -334,7 +339,7 @@ int send_pack(struct send_pack_args *args,
                demux.data = fd;
                demux.out = -1;
                if (start_async(&demux))
-                       die("receive-pack: unable to fork off sideband demultiplexer");
+                       die("send-pack: unable to fork off sideband demultiplexer");
                in = demux.out;
        }
 
@@ -439,6 +444,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                                args.force_update = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--quiet")) {
+                               args.quiet = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--verbose")) {
                                args.verbose = 1;
                                continue;
@@ -494,8 +503,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
 
        memset(&extra_have, 0, sizeof(extra_have));
 
-       get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL,
-                        &extra_have);
+       get_remote_heads(fd[0], &remote_refs, REF_NORMAL, &extra_have);
 
        transport_verify_remote_names(nr_refspecs, refspecs);
 
@@ -509,7 +517,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                flags |= MATCH_REFS_MIRROR;
 
        /* match them up */
-       if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
+       if (match_push_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
                return -1;
 
        set_ref_status_for_push(remote_refs, args.send_mirror,
index 4b480d7c7ca6c6258a5cd82cfc88df62cd0d218f..a59e088cf59215d2d196dda530ad43ef0596b6be 100644 (file)
@@ -726,10 +726,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 
                if (ac == 0) {
                        static const char *fake_av[2];
-                       const char *refname;
 
-                       refname = resolve_ref("HEAD", sha1, 1, NULL);
-                       fake_av[0] = xstrdup(refname);
+                       fake_av[0] = resolve_refdup("HEAD", sha1, 1, NULL);
                        fake_av[1] = NULL;
                        av = fake_av;
                        ac = 1;
@@ -791,7 +789,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                }
        }
 
-       head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
+       head_p = resolve_ref_unsafe("HEAD", head_sha1, 1, NULL);
        if (head_p) {
                head_len = strlen(head_p);
                memcpy(head, head_p, head_len + 1);
index 45f0340c3ea004822477ed70fdd14e05dfcd9bb5..3911661900f79562133b4ebd6cbb513a06232b5b 100644 (file)
@@ -145,7 +145,7 @@ static int exclude_existing(const char *match)
                        if (strncmp(ref, match, matchlen))
                                continue;
                }
-               if (check_ref_format(ref)) {
+               if (check_refname_format(ref, 0)) {
                        warning("ref '%s' ignored", ref);
                        continue;
                }
@@ -225,7 +225,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
                        unsigned char sha1[20];
 
                        if (!prefixcmp(*pattern, "refs/") &&
-                           resolve_ref(*pattern, sha1, 1, NULL)) {
+                           !read_ref(*pattern, sha1)) {
                                if (!quiet)
                                        show_one(*pattern, sha1);
                        }
index 1288ffcc52530f8ef9561acd2eb2ec5322c9e230..f16986c0ae811f6b998d20be39da1af6095fcfe8 100644 (file)
@@ -75,7 +75,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix)
                                !strcmp(argv[1], "--strip-comments")))
                strip_comments = 1;
        else if (argc > 1)
-               usage("git stripspace [-s | --strip-comments] < <stream>");
+               usage("git stripspace [-s | --strip-comments] < input");
 
        if (strbuf_read(&buf, 0, 1024) < 0)
                die_errno("could not read the input");
index dea849c3c5ec0c0a7ee0ac98e1ae62a6dacf5806..2ef5962386dcc21af94c680be5fd75fc96d4962f 100644 (file)
@@ -12,7 +12,7 @@ static void check_symref(const char *HEAD, int quiet)
 {
        unsigned char sha1[20];
        int flag;
-       const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag);
+       const char *refs_heads_master = resolve_ref_unsafe(HEAD, sha1, 0, &flag);
 
        if (!refs_heads_master)
                die("No such ref: %s", HEAD);
index 667515e5278d22548fb83507347ab652533a67b2..03df16ac6e0e492483bf3695e0f6b2aa0a95978b 100644 (file)
@@ -14,6 +14,7 @@
 #include "parse-options.h"
 #include "diff.h"
 #include "revision.h"
+#include "gpg-interface.h"
 
 static const char * const git_tag_usage[] = {
        "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
@@ -23,8 +24,6 @@ static const char * const git_tag_usage[] = {
        NULL
 };
 
-static char signingkey[1000];
-
 struct tag_filter {
        const char **patterns;
        int lines;
@@ -84,18 +83,51 @@ static int contains(struct commit *candidate, const struct commit_list *want)
        return contains_recurse(candidate, want);
 }
 
+static void show_tag_lines(const unsigned char *sha1, int lines)
+{
+       int i;
+       unsigned long size;
+       enum object_type type;
+       char *buf, *sp, *eol;
+       size_t len;
+
+       buf = read_sha1_file(sha1, &type, &size);
+       if (!buf)
+               die_errno("unable to read object %s", sha1_to_hex(sha1));
+       if (type != OBJ_COMMIT && type != OBJ_TAG)
+               goto free_return;
+       if (!size)
+               die("an empty %s object %s?",
+                   typename(type), sha1_to_hex(sha1));
+
+       /* skip header */
+       sp = strstr(buf, "\n\n");
+       if (!sp)
+               goto free_return;
+
+       /* only take up to "lines" lines, and strip the signature from a tag */
+       if (type == OBJ_TAG)
+               size = parse_signature(buf, size);
+       for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
+               if (i)
+                       printf("\n    ");
+               eol = memchr(sp, '\n', size - (sp - buf));
+               len = eol ? eol - sp : size - (sp - buf);
+               fwrite(sp, len, 1, stdout);
+               if (!eol)
+                       break;
+               sp = eol + 1;
+       }
+free_return:
+       free(buf);
+}
+
 static int show_reference(const char *refname, const unsigned char *sha1,
                          int flag, void *cb_data)
 {
        struct tag_filter *filter = cb_data;
 
        if (match_pattern(filter->patterns, refname)) {
-               int i;
-               unsigned long size;
-               enum object_type type;
-               char *buf, *sp, *eol;
-               size_t len;
-
                if (filter->with_commit) {
                        struct commit *commit;
 
@@ -111,33 +143,8 @@ static int show_reference(const char *refname, const unsigned char *sha1,
                        return 0;
                }
                printf("%-15s ", refname);
-
-               buf = read_sha1_file(sha1, &type, &size);
-               if (!buf || !size)
-                       return 0;
-
-               /* skip header */
-               sp = strstr(buf, "\n\n");
-               if (!sp) {
-                       free(buf);
-                       return 0;
-               }
-               /* only take up to "lines" lines, and strip the signature */
-               size = parse_signature(buf, size);
-               for (i = 0, sp += 2;
-                               i < filter->lines && sp < buf + size;
-                               i++) {
-                       if (i)
-                               printf("\n    ");
-                       eol = memchr(sp, '\n', size - (sp - buf));
-                       len = eol ? eol - sp : size - (sp - buf);
-                       fwrite(sp, len, 1, stdout);
-                       if (!eol)
-                               break;
-                       sp = eol + 1;
-               }
+               show_tag_lines(sha1, filter->lines);
                putchar('\n');
-               free(buf);
        }
 
        return 0;
@@ -174,7 +181,7 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
                        had_error = 1;
                        continue;
                }
-               if (!resolve_ref(ref, sha1, 1, NULL)) {
+               if (read_ref(ref, sha1)) {
                        error(_("tag '%s' not found."), *p);
                        had_error = 1;
                        continue;
@@ -208,83 +215,29 @@ static int verify_tag(const char *name, const char *ref,
 
 static int do_sign(struct strbuf *buffer)
 {
-       struct child_process gpg;
-       const char *args[4];
-       char *bracket;
-       int len;
-       int i, j;
-
-       if (!*signingkey) {
-               if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
-                               sizeof(signingkey)) > sizeof(signingkey) - 1)
-                       return error(_("committer info too long."));
-               bracket = strchr(signingkey, '>');
-               if (bracket)
-                       bracket[1] = '\0';
-       }
-
-       /* When the username signingkey is bad, program could be terminated
-        * because gpg exits without reading and then write gets SIGPIPE. */
-       signal(SIGPIPE, SIG_IGN);
-
-       memset(&gpg, 0, sizeof(gpg));
-       gpg.argv = args;
-       gpg.in = -1;
-       gpg.out = -1;
-       args[0] = "gpg";
-       args[1] = "-bsau";
-       args[2] = signingkey;
-       args[3] = NULL;
-
-       if (start_command(&gpg))
-               return error(_("could not run gpg."));
-
-       if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
-               close(gpg.in);
-               close(gpg.out);
-               finish_command(&gpg);
-               return error(_("gpg did not accept the tag data"));
-       }
-       close(gpg.in);
-       len = strbuf_read(buffer, gpg.out, 1024);
-       close(gpg.out);
-
-       if (finish_command(&gpg) || !len || len < 0)
-               return error(_("gpg failed to sign the tag"));
-
-       /* Strip CR from the line endings, in case we are on Windows. */
-       for (i = j = 0; i < buffer->len; i++)
-               if (buffer->buf[i] != '\r') {
-                       if (i != j)
-                               buffer->buf[j] = buffer->buf[i];
-                       j++;
-               }
-       strbuf_setlen(buffer, j);
-
-       return 0;
+       return sign_buffer(buffer, buffer, get_signing_key());
 }
 
 static const char tag_template[] =
        N_("\n"
        "#\n"
        "# Write a tag message\n"
+       "# Lines starting with '#' will be ignored.\n"
        "#\n");
 
-static void set_signingkey(const char *value)
-{
-       if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey))
-               die(_("signing key value too long (%.10s...)"), value);
-}
+static const char tag_template_nocleanup[] =
+       N_("\n"
+       "#\n"
+       "# Write a tag message\n"
+       "# Lines starting with '#' will be kept; you may remove them"
+       " yourself if you want to.\n"
+       "#\n");
 
 static int git_tag_config(const char *var, const char *value, void *cb)
 {
-       if (!strcmp(var, "user.signingkey")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               set_signingkey(value);
-               return 0;
-       }
-
+       int status = git_gpg_config(var, value, cb);
+       if (status)
+               return status;
        return git_default_config(var, value, cb);
 }
 
@@ -319,8 +272,18 @@ static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)
        return 0;
 }
 
+struct create_tag_options {
+       unsigned int message_given:1;
+       unsigned int sign;
+       enum {
+               CLEANUP_NONE,
+               CLEANUP_SPACE,
+               CLEANUP_ALL
+       } cleanup_mode;
+};
+
 static void create_tag(const unsigned char *object, const char *tag,
-                      struct strbuf *buf, int message, int sign,
+                      struct strbuf *buf, struct create_tag_options *opt,
                       unsigned char *prev, unsigned char *result)
 {
        enum object_type type;
@@ -345,7 +308,7 @@ static void create_tag(const unsigned char *object, const char *tag,
        if (header_len > sizeof(header_buf) - 1)
                die(_("tag header too big."));
 
-       if (!message) {
+       if (!opt->message_given) {
                int fd;
 
                /* write the template message before editing: */
@@ -356,8 +319,12 @@ static void create_tag(const unsigned char *object, const char *tag,
 
                if (!is_null_sha1(prev))
                        write_tag_body(fd, prev);
+               else if (opt->cleanup_mode == CLEANUP_ALL)
+                       write_or_die(fd, _(tag_template),
+                                       strlen(_(tag_template)));
                else
-                       write_or_die(fd, _(tag_template), strlen(_(tag_template)));
+                       write_or_die(fd, _(tag_template_nocleanup),
+                                       strlen(_(tag_template_nocleanup)));
                close(fd);
 
                if (launch_editor(path, buf, NULL)) {
@@ -367,14 +334,15 @@ static void create_tag(const unsigned char *object, const char *tag,
                }
        }
 
-       stripspace(buf, 1);
+       if (opt->cleanup_mode != CLEANUP_NONE)
+               stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
 
-       if (!message && !buf->len)
+       if (!opt->message_given && !buf->len)
                die(_("no tag message?"));
 
        strbuf_insert(buf, 0, header_buf, header_len);
 
-       if (build_tag_object(buf, sign, result) < 0) {
+       if (build_tag_object(buf, opt->sign, result) < 0) {
                if (path)
                        fprintf(stderr, _("The tag message has been left in %s\n"),
                                path);
@@ -407,12 +375,12 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
 {
        if (name[0] == '-')
-               return CHECK_REF_FORMAT_ERROR;
+               return -1;
 
        strbuf_reset(sb);
        strbuf_addf(sb, "refs/tags/%s", name);
 
-       return check_ref_format(sb->buf);
+       return check_refname_format(sb->buf, 0);
 }
 
 int cmd_tag(int argc, const char **argv, const char *prefix)
@@ -422,28 +390,31 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        unsigned char object[20], prev[20];
        const char *object_ref, *tag;
        struct ref_lock *lock;
-
-       int annotate = 0, sign = 0, force = 0, lines = -1,
-               list = 0, delete = 0, verify = 0;
+       struct create_tag_options opt;
+       char *cleanup_arg = NULL;
+       int annotate = 0, force = 0, lines = -1, list = 0,
+               delete = 0, verify = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
        struct commit_list *with_commit = NULL;
        struct option options[] = {
-               OPT_BOOLEAN('l', NULL, &list, "list tag names"),
+               OPT_BOOLEAN('l', "list", &list, "list tag names"),
                { OPTION_INTEGER, 'n', NULL, &lines, "n",
                                "print <n> lines of each tag message",
                                PARSE_OPT_OPTARG, NULL, 1 },
-               OPT_BOOLEAN('d', NULL, &delete, "delete tags"),
-               OPT_BOOLEAN('v', NULL, &verify, "verify tags"),
+               OPT_BOOLEAN('d', "delete", &delete, "delete tags"),
+               OPT_BOOLEAN('v', "verify", &verify, "verify tags"),
 
                OPT_GROUP("Tag creation options"),
-               OPT_BOOLEAN('a', NULL, &annotate,
+               OPT_BOOLEAN('a', "annotate", &annotate,
                                        "annotated tag, needs a message"),
-               OPT_CALLBACK('m', NULL, &msg, "message",
+               OPT_CALLBACK('m', "message", &msg, "message",
                             "tag message", parse_msg_arg),
-               OPT_FILENAME('F', NULL, &msgfile, "read message from file"),
-               OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
-               OPT_STRING('u', NULL, &keyid, "key-id",
+               OPT_FILENAME('F', "file", &msgfile, "read message from file"),
+               OPT_BOOLEAN('s', "sign", &opt.sign, "annotated and GPG-signed tag"),
+               OPT_STRING(0, "cleanup", &cleanup_arg, "mode",
+                       "how to strip spaces and #comments from message"),
+               OPT_STRING('u', "local-user", &keyid, "key-id",
                                        "use another key to sign the tag"),
                OPT__FORCE(&force, "replace the tag if exists"),
 
@@ -459,13 +430,15 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
        git_config(git_tag_config, NULL);
 
+       memset(&opt, 0, sizeof(opt));
+
        argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
 
        if (keyid) {
-               sign = 1;
-               set_signingkey(keyid);
+               opt.sign = 1;
+               set_signing_key(keyid);
        }
-       if (sign)
+       if (opt.sign)
                annotate = 1;
        if (argc == 0 && !(delete || verify))
                list = 1;
@@ -518,14 +491,24 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        if (strbuf_check_tag_ref(&ref, tag))
                die(_("'%s' is not a valid tag name."), tag);
 
-       if (!resolve_ref(ref.buf, prev, 1, NULL))
+       if (read_ref(ref.buf, prev))
                hashclr(prev);
        else if (!force)
                die(_("tag '%s' already exists"), tag);
 
+       opt.message_given = msg.given || msgfile;
+
+       if (!cleanup_arg || !strcmp(cleanup_arg, "strip"))
+               opt.cleanup_mode = CLEANUP_ALL;
+       else if (!strcmp(cleanup_arg, "verbatim"))
+               opt.cleanup_mode = CLEANUP_NONE;
+       else if (!strcmp(cleanup_arg, "whitespace"))
+               opt.cleanup_mode = CLEANUP_SPACE;
+       else
+               die(_("Invalid cleanup mode %s"), cleanup_arg);
+
        if (annotate)
-               create_tag(object, tag, &buf, msg.given || msgfile,
-                          sign, prev, object);
+               create_tag(object, tag, &buf, &opt, prev, object);
 
        lock = lock_any_ref_for_update(ref.buf, prev, 0);
        if (!lock)
index 2d0b38333eadef0a5c6501e5a3046ebc2afa048e..b928beb8ed51c9af9aa4f640e80443a5e059d505 100644 (file)
@@ -6,6 +6,7 @@
 #include "archive.h"
 #include "pkt-line.h"
 #include "sideband.h"
+#include "run-command.h"
 
 static const char upload_archive_usage[] =
        "git upload-archive <repo>";
@@ -13,12 +14,9 @@ static const char upload_archive_usage[] =
 static const char deadchild[] =
 "git upload-archive: archiver died with error";
 
-static const char lostchild[] =
-"git upload-archive: archiver process was lost";
-
 #define MAX_ARGS (64)
 
-static int run_upload_archive(int argc, const char **argv, const char *prefix)
+int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix)
 {
        const char *sent_argv[MAX_ARGS];
        const char *arg_cmd = "argument ";
@@ -96,8 +94,8 @@ static ssize_t process_input(int child_fd, int band)
 
 int cmd_upload_archive(int argc, const char **argv, const char *prefix)
 {
-       pid_t writer;
-       int fd1[2], fd2[2];
+       struct child_process writer = { argv };
+
        /*
         * Set up sideband subprocess.
         *
@@ -105,39 +103,24 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
         * multiplexed out to our fd#1.  If the child dies, we tell the other
         * end over channel #3.
         */
-       if (pipe(fd1) < 0 || pipe(fd2) < 0) {
-               int err = errno;
-               packet_write(1, "NACK pipe failed on the remote side\n");
-               die("upload-archive: %s", strerror(err));
-       }
-       writer = fork();
-       if (writer < 0) {
+       argv[0] = "upload-archive--writer";
+       writer.out = writer.err = -1;
+       writer.git_cmd = 1;
+       if (start_command(&writer)) {
                int err = errno;
-               packet_write(1, "NACK fork failed on the remote side\n");
+               packet_write(1, "NACK unable to spawn subprocess\n");
                die("upload-archive: %s", strerror(err));
        }
-       if (!writer) {
-               /* child - connect fd#1 and fd#2 to the pipe */
-               dup2(fd1[1], 1);
-               dup2(fd2[1], 2);
-               close(fd1[1]); close(fd2[1]);
-               close(fd1[0]); close(fd2[0]); /* we do not read from pipe */
-
-               exit(run_upload_archive(argc, argv, prefix));
-       }
 
-       /* parent - read from child, multiplex and send out to fd#1 */
-       close(fd1[1]); close(fd2[1]); /* we do not write to pipe */
        packet_write(1, "ACK\n");
        packet_flush(1);
 
        while (1) {
                struct pollfd pfd[2];
-               int status;
 
-               pfd[0].fd = fd1[0];
+               pfd[0].fd = writer.out;
                pfd[0].events = POLLIN;
-               pfd[1].fd = fd2[0];
+               pfd[1].fd = writer.err;
                pfd[1].events = POLLIN;
                if (poll(pfd, 2, -1) < 0) {
                        if (errno != EINTR) {
@@ -156,9 +139,7 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
                        if (process_input(pfd[0].fd, 1))
                                continue;
 
-               if (waitpid(writer, &status, 0) < 0)
-                       error_clnt("%s", lostchild);
-               else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
+               if (finish_command(&writer))
                        error_clnt("%s", deadchild);
                packet_flush(1);
                break;
index 313476604967bb2962350c82d45ffad04eab09e1..28c21743381db95f6ddb4b185ec34cdbff686866 100644 (file)
@@ -11,6 +11,7 @@
 #include "run-command.h"
 #include <signal.h>
 #include "parse-options.h"
+#include "gpg-interface.h"
 
 static const char * const verify_tag_usage[] = {
                "git verify-tag [-v|--verbose] <tag>...",
@@ -19,42 +20,16 @@ static const char * const verify_tag_usage[] = {
 
 static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
 {
-       struct child_process gpg;
-       const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
-       char path[PATH_MAX];
-       size_t len;
-       int fd, ret;
+       int len;
 
-       fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
-       if (fd < 0)
-               return error("could not create temporary file '%s': %s",
-                                               path, strerror(errno));
-       if (write_in_full(fd, buf, size) < 0)
-               return error("failed writing temporary file '%s': %s",
-                                               path, strerror(errno));
-       close(fd);
-
-       /* find the length without signature */
        len = parse_signature(buf, size);
        if (verbose)
                write_in_full(1, buf, len);
 
-       memset(&gpg, 0, sizeof(gpg));
-       gpg.argv = args_gpg;
-       gpg.in = -1;
-       args_gpg[2] = path;
-       if (start_command(&gpg)) {
-               unlink(path);
-               return error("could not run gpg.");
-       }
-
-       write_in_full(gpg.in, buf, len);
-       close(gpg.in);
-       ret = finish_command(&gpg);
+       if (size == len)
+               return error("no signature found");
 
-       unlink_or_warn(path);
-
-       return ret;
+       return verify_signed_buffer(buf, len, buf + len, size - len, NULL);
 }
 
 static int verify_tag(const char *name, int verbose)
diff --git a/bulk-checkin.c b/bulk-checkin.c
new file mode 100644 (file)
index 0000000..6b0b6d4
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#include "bulk-checkin.h"
+#include "csum-file.h"
+#include "pack.h"
+
+static int pack_compression_level = Z_DEFAULT_COMPRESSION;
+
+static struct bulk_checkin_state {
+       unsigned plugged:1;
+
+       char *pack_tmp_name;
+       struct sha1file *f;
+       off_t offset;
+       struct pack_idx_option pack_idx_opts;
+
+       struct pack_idx_entry **written;
+       uint32_t alloc_written;
+       uint32_t nr_written;
+} state;
+
+static void finish_bulk_checkin(struct bulk_checkin_state *state)
+{
+       unsigned char sha1[20];
+       char packname[PATH_MAX];
+       int i;
+
+       if (!state->f)
+               return;
+
+       if (state->nr_written == 0) {
+               close(state->f->fd);
+               unlink(state->pack_tmp_name);
+               goto clear_exit;
+       } else if (state->nr_written == 1) {
+               sha1close(state->f, sha1, CSUM_FSYNC);
+       } else {
+               int fd = sha1close(state->f, sha1, 0);
+               fixup_pack_header_footer(fd, sha1, state->pack_tmp_name,
+                                        state->nr_written, sha1,
+                                        state->offset);
+               close(fd);
+       }
+
+       sprintf(packname, "%s/pack/pack-", get_object_directory());
+       finish_tmp_packfile(packname, state->pack_tmp_name,
+                           state->written, state->nr_written,
+                           &state->pack_idx_opts, sha1);
+       for (i = 0; i < state->nr_written; i++)
+               free(state->written[i]);
+
+clear_exit:
+       free(state->written);
+       memset(state, 0, sizeof(*state));
+
+       /* Make objects we just wrote available to ourselves */
+       reprepare_packed_git();
+}
+
+static int already_written(struct bulk_checkin_state *state, unsigned char sha1[])
+{
+       int i;
+
+       /* The object may already exist in the repository */
+       if (has_sha1_file(sha1))
+               return 1;
+
+       /* Might want to keep the list sorted */
+       for (i = 0; i < state->nr_written; i++)
+               if (!hashcmp(state->written[i]->sha1, sha1))
+                       return 1;
+
+       /* This is a new object we need to keep */
+       return 0;
+}
+
+/*
+ * Read the contents from fd for size bytes, streaming it to the
+ * packfile in state while updating the hash in ctx. Signal a failure
+ * by returning a negative value when the resulting pack would exceed
+ * the pack size limit and this is not the first object in the pack,
+ * so that the caller can discard what we wrote from the current pack
+ * by truncating it and opening a new one. The caller will then call
+ * us again after rewinding the input fd.
+ *
+ * The already_hashed_to pointer is kept untouched by the caller to
+ * make sure we do not hash the same byte when we are called
+ * again. This way, the caller does not have to checkpoint its hash
+ * status before calling us just in case we ask it to call us again
+ * with a new pack.
+ */
+static int stream_to_pack(struct bulk_checkin_state *state,
+                         git_SHA_CTX *ctx, off_t *already_hashed_to,
+                         int fd, size_t size, enum object_type type,
+                         const char *path, unsigned flags)
+{
+       git_zstream s;
+       unsigned char obuf[16384];
+       unsigned hdrlen;
+       int status = Z_OK;
+       int write_object = (flags & HASH_WRITE_OBJECT);
+       off_t offset = 0;
+
+       memset(&s, 0, sizeof(s));
+       git_deflate_init(&s, pack_compression_level);
+
+       hdrlen = encode_in_pack_object_header(type, size, obuf);
+       s.next_out = obuf + hdrlen;
+       s.avail_out = sizeof(obuf) - hdrlen;
+
+       while (status != Z_STREAM_END) {
+               unsigned char ibuf[16384];
+
+               if (size && !s.avail_in) {
+                       ssize_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf);
+                       if (xread(fd, ibuf, rsize) != rsize)
+                               die("failed to read %d bytes from '%s'",
+                                   (int)rsize, path);
+                       offset += rsize;
+                       if (*already_hashed_to < offset) {
+                               size_t hsize = offset - *already_hashed_to;
+                               if (rsize < hsize)
+                                       hsize = rsize;
+                               if (hsize)
+                                       git_SHA1_Update(ctx, ibuf, hsize);
+                               *already_hashed_to = offset;
+                       }
+                       s.next_in = ibuf;
+                       s.avail_in = rsize;
+                       size -= rsize;
+               }
+
+               status = git_deflate(&s, size ? 0 : Z_FINISH);
+
+               if (!s.avail_out || status == Z_STREAM_END) {
+                       if (write_object) {
+                               size_t written = s.next_out - obuf;
+
+                               /* would we bust the size limit? */
+                               if (state->nr_written &&
+                                   pack_size_limit_cfg &&
+                                   pack_size_limit_cfg < state->offset + written) {
+                                       git_deflate_abort(&s);
+                                       return -1;
+                               }
+
+                               sha1write(state->f, obuf, written);
+                               state->offset += written;
+                       }
+                       s.next_out = obuf;
+                       s.avail_out = sizeof(obuf);
+               }
+
+               switch (status) {
+               case Z_OK:
+               case Z_BUF_ERROR:
+               case Z_STREAM_END:
+                       continue;
+               default:
+                       die("unexpected deflate failure: %d", status);
+               }
+       }
+       git_deflate_end(&s);
+       return 0;
+}
+
+/* Lazily create backing packfile for the state */
+static void prepare_to_stream(struct bulk_checkin_state *state,
+                             unsigned flags)
+{
+       if (!(flags & HASH_WRITE_OBJECT) || state->f)
+               return;
+
+       state->f = create_tmp_packfile(&state->pack_tmp_name);
+       reset_pack_idx_option(&state->pack_idx_opts);
+
+       /* Pretend we are going to write only one object */
+       state->offset = write_pack_header(state->f, 1);
+       if (!state->offset)
+               die_errno("unable to write pack header");
+}
+
+static int deflate_to_pack(struct bulk_checkin_state *state,
+                          unsigned char result_sha1[],
+                          int fd, size_t size,
+                          enum object_type type, const char *path,
+                          unsigned flags)
+{
+       off_t seekback, already_hashed_to;
+       git_SHA_CTX ctx;
+       unsigned char obuf[16384];
+       unsigned header_len;
+       struct sha1file_checkpoint checkpoint;
+       struct pack_idx_entry *idx = NULL;
+
+       seekback = lseek(fd, 0, SEEK_CUR);
+       if (seekback == (off_t) -1)
+               return error("cannot find the current offset");
+
+       header_len = sprintf((char *)obuf, "%s %" PRIuMAX,
+                            typename(type), (uintmax_t)size) + 1;
+       git_SHA1_Init(&ctx);
+       git_SHA1_Update(&ctx, obuf, header_len);
+
+       /* Note: idx is non-NULL when we are writing */
+       if ((flags & HASH_WRITE_OBJECT) != 0)
+               idx = xcalloc(1, sizeof(*idx));
+
+       already_hashed_to = 0;
+
+       while (1) {
+               prepare_to_stream(state, flags);
+               if (idx) {
+                       sha1file_checkpoint(state->f, &checkpoint);
+                       idx->offset = state->offset;
+                       crc32_begin(state->f);
+               }
+               if (!stream_to_pack(state, &ctx, &already_hashed_to,
+                                   fd, size, type, path, flags))
+                       break;
+               /*
+                * Writing this object to the current pack will make
+                * it too big; we need to truncate it, start a new
+                * pack, and write into it.
+                */
+               if (!idx)
+                       die("BUG: should not happen");
+               sha1file_truncate(state->f, &checkpoint);
+               state->offset = checkpoint.offset;
+               finish_bulk_checkin(state);
+               if (lseek(fd, seekback, SEEK_SET) == (off_t) -1)
+                       return error("cannot seek back");
+       }
+       git_SHA1_Final(result_sha1, &ctx);
+       if (!idx)
+               return 0;
+
+       idx->crc32 = crc32_end(state->f);
+       if (already_written(state, result_sha1)) {
+               sha1file_truncate(state->f, &checkpoint);
+               state->offset = checkpoint.offset;
+               free(idx);
+       } else {
+               hashcpy(idx->sha1, result_sha1);
+               ALLOC_GROW(state->written,
+                          state->nr_written + 1,
+                          state->alloc_written);
+               state->written[state->nr_written++] = idx;
+       }
+       return 0;
+}
+
+int index_bulk_checkin(unsigned char *sha1,
+                      int fd, size_t size, enum object_type type,
+                      const char *path, unsigned flags)
+{
+       int status = deflate_to_pack(&state, sha1, fd, size, type,
+                                    path, flags);
+       if (!state.plugged)
+               finish_bulk_checkin(&state);
+       return status;
+}
+
+void plug_bulk_checkin(void)
+{
+       state.plugged = 1;
+}
+
+void unplug_bulk_checkin(void)
+{
+       state.plugged = 0;
+       if (state.f)
+               finish_bulk_checkin(&state);
+}
diff --git a/bulk-checkin.h b/bulk-checkin.h
new file mode 100644 (file)
index 0000000..4f599f8
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#ifndef BULK_CHECKIN_H
+#define BULK_CHECKIN_H
+
+#include "cache.h"
+
+extern int index_bulk_checkin(unsigned char sha1[],
+                             int fd, size_t size, enum object_type type,
+                             const char *path, unsigned flags);
+
+extern void plug_bulk_checkin(void);
+extern void unplug_bulk_checkin(void);
+
+#endif
index 6bf849740c6de45fe8ca11eca2a06a52c7ebc0bb..d9cfd90534b6b6b0659576d5a71d1fa216bfa6b5 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -23,50 +23,85 @@ static void add_to_ref_list(const unsigned char *sha1, const char *name,
        list->nr++;
 }
 
-/* returns an fd */
-int read_bundle_header(const char *path, struct bundle_header *header)
+static int parse_bundle_header(int fd, struct bundle_header *header,
+                              const char *report_path)
 {
-       char buffer[1024];
-       int fd;
-       long fpos;
-       FILE *ffd = fopen(path, "rb");
-
-       if (!ffd)
-               return error("could not open '%s'", path);
-       if (!fgets(buffer, sizeof(buffer), ffd) ||
-                       strcmp(buffer, bundle_signature)) {
-               fclose(ffd);
-               return error("'%s' does not look like a v2 bundle file", path);
+       struct strbuf buf = STRBUF_INIT;
+       int status = 0;
+
+       /* The bundle header begins with the signature */
+       if (strbuf_getwholeline_fd(&buf, fd, '\n') ||
+           strcmp(buf.buf, bundle_signature)) {
+               if (report_path)
+                       error("'%s' does not look like a v2 bundle file",
+                             report_path);
+               status = -1;
+               goto abort;
        }
-       while (fgets(buffer, sizeof(buffer), ffd)
-                       && buffer[0] != '\n') {
-               int is_prereq = buffer[0] == '-';
-               int offset = is_prereq ? 1 : 0;
-               int len = strlen(buffer);
+
+       /* The bundle header ends with an empty line */
+       while (!strbuf_getwholeline_fd(&buf, fd, '\n') &&
+              buf.len && buf.buf[0] != '\n') {
                unsigned char sha1[20];
-               struct ref_list *list = is_prereq ? &header->prerequisites
-                       : &header->references;
-               char delim;
-
-               if (len && buffer[len - 1] == '\n')
-                       buffer[len - 1] = '\0';
-               if (get_sha1_hex(buffer + offset, sha1)) {
-                       warning("unrecognized header: %s", buffer);
-                       continue;
+               int is_prereq = 0;
+
+               if (*buf.buf == '-') {
+                       is_prereq = 1;
+                       strbuf_remove(&buf, 0, 1);
+               }
+               strbuf_rtrim(&buf);
+
+               /*
+                * Tip lines have object name, SP, and refname.
+                * Prerequisites have object name that is optionally
+                * followed by SP and subject line.
+                */
+               if (get_sha1_hex(buf.buf, sha1) ||
+                   (40 <= buf.len && !isspace(buf.buf[40])) ||
+                   (!is_prereq && buf.len <= 40)) {
+                       if (report_path)
+                               error("unrecognized header: %s%s (%d)",
+                                     (is_prereq ? "-" : ""), buf.buf, (int)buf.len);
+                       status = -1;
+                       break;
+               } else {
+                       if (is_prereq)
+                               add_to_ref_list(sha1, "", &header->prerequisites);
+                       else
+                               add_to_ref_list(sha1, buf.buf + 41, &header->references);
                }
-               delim = buffer[40 + offset];
-               if (!isspace(delim) && (delim != '\0' || !is_prereq))
-                       die ("invalid header: %s", buffer);
-               add_to_ref_list(sha1, isspace(delim) ?
-                               buffer + 41 + offset : "", list);
        }
-       fpos = ftell(ffd);
-       fclose(ffd);
-       fd = open(path, O_RDONLY);
+
+ abort:
+       if (status) {
+               close(fd);
+               fd = -1;
+       }
+       strbuf_release(&buf);
+       return fd;
+}
+
+int read_bundle_header(const char *path, struct bundle_header *header)
+{
+       int fd = open(path, O_RDONLY);
+
        if (fd < 0)
                return error("could not open '%s'", path);
-       lseek(fd, fpos, SEEK_SET);
-       return fd;
+       return parse_bundle_header(fd, header, path);
+}
+
+int is_bundle(const char *path, int quiet)
+{
+       struct bundle_header header;
+       int fd = open(path, O_RDONLY);
+
+       if (fd < 0)
+               return 0;
+       memset(&header, 0, sizeof(header));
+       fd = parse_bundle_header(fd, &header, quiet ? NULL : path);
+       if (fd >= 0)
+               close(fd);
+       return (fd >= 0);
 }
 
 static int list_refs(struct ref_list *r, int argc, const char **argv)
@@ -122,11 +157,8 @@ int verify_bundle(struct bundle_header *header, int verbose)
        req_nr = revs.pending.nr;
        setup_revisions(2, argv, &revs, NULL);
 
-       memset(&refs, 0, sizeof(struct object_array));
-       for (i = 0; i < revs.pending.nr; i++) {
-               struct object_array_entry *e = revs.pending.objects + i;
-               add_object_array(e->item, e->name, &refs);
-       }
+       refs = revs.pending;
+       revs.leak_pending = 1;
 
        if (prepare_revision_walk(&revs))
                die("revision walk setup failed");
@@ -144,8 +176,8 @@ int verify_bundle(struct bundle_header *header, int verbose)
                                refs.objects[i].name);
                }
 
-       for (i = 0; i < refs.nr; i++)
-               clear_commit_marks((struct commit *)refs.objects[i].item, -1);
+       clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);
+       free(refs.objects);
 
        if (verbose) {
                struct ref_list *r;
@@ -202,7 +234,7 @@ int create_bundle(struct bundle_header *header, const char *path,
        const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *));
        const char **argv_pack = xmalloc(6 * sizeof(const char *));
        int i, ref_count = 0;
-       char buffer[1024];
+       struct strbuf buf = STRBUF_INIT;
        struct rev_info revs;
        struct child_process rls;
        FILE *rls_fout;
@@ -234,20 +266,21 @@ int create_bundle(struct bundle_header *header, const char *path,
        if (start_command(&rls))
                return -1;
        rls_fout = xfdopen(rls.out, "r");
-       while (fgets(buffer, sizeof(buffer), rls_fout)) {
+       while (strbuf_getwholeline(&buf, rls_fout, '\n') != EOF) {
                unsigned char sha1[20];
-               if (buffer[0] == '-') {
-                       write_or_die(bundle_fd, buffer, strlen(buffer));
-                       if (!get_sha1_hex(buffer + 1, sha1)) {
+               if (buf.len > 0 && buf.buf[0] == '-') {
+                       write_or_die(bundle_fd, buf.buf, buf.len);
+                       if (!get_sha1_hex(buf.buf + 1, sha1)) {
                                struct object *object = parse_object(sha1);
                                object->flags |= UNINTERESTING;
-                               add_pending_object(&revs, object, buffer);
+                               add_pending_object(&revs, object, xstrdup(buf.buf));
                        }
-               } else if (!get_sha1_hex(buffer, sha1)) {
+               } else if (!get_sha1_hex(buf.buf, sha1)) {
                        struct object *object = parse_object(sha1);
                        object->flags |= SHOWN;
                }
        }
+       strbuf_release(&buf);
        fclose(rls_fout);
        if (finish_command(&rls))
                return error("rev-list died");
@@ -271,7 +304,7 @@ int create_bundle(struct bundle_header *header, const char *path,
                        continue;
                if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)
                        continue;
-               if (!resolve_ref(e->name, sha1, 1, &flag))
+               if (read_ref_full(e->name, sha1, 1, &flag))
                        flag = 0;
                display_ref = (flag & REF_ISSYMREF) ? e->name : ref;
 
index c5a22c8d10c7bf5e27536a3b84033b77f3542445..1584e4d821440d525f10515d6a07c6506b732fec 100644 (file)
--- a/bundle.h
+++ b/bundle.h
@@ -14,6 +14,7 @@ struct bundle_header {
        struct ref_list references;
 };
 
+int is_bundle(const char *path, int quiet);
 int read_bundle_header(const char *path, struct bundle_header *header);
 int create_bundle(struct bundle_header *header, const char *path,
                int argc, const char **argv);
index f755590da827234830d8b4359720cfbfd87a3dea..bf03cb7fcefdc7e119203f096b367592d5b7cf8c 100644 (file)
@@ -150,7 +150,7 @@ void cache_tree_invalidate_path(struct cache_tree *it, const char *path)
 }
 
 static int verify_cache(struct cache_entry **cache,
-                       int entries)
+                       int entries, int silent)
 {
        int i, funny;
 
@@ -158,7 +158,9 @@ static int verify_cache(struct cache_entry **cache,
        funny = 0;
        for (i = 0; i < entries; i++) {
                struct cache_entry *ce = cache[i];
-               if (ce_stage(ce) || (ce->ce_flags & CE_INTENT_TO_ADD)) {
+               if (ce_stage(ce)) {
+                       if (silent)
+                               return -1;
                        if (10 < ++funny) {
                                fprintf(stderr, "...\n");
                                break;
@@ -336,8 +338,8 @@ static int update_one(struct cache_tree *it,
                                mode, sha1_to_hex(sha1), entlen+baselen, path);
                }
 
-               if (ce->ce_flags & CE_REMOVE)
-                       continue; /* entry being removed */
+               if (ce->ce_flags & (CE_REMOVE | CE_INTENT_TO_ADD))
+                       continue; /* entry being removed or placeholder */
 
                strbuf_grow(&buffer, entlen + 100);
                strbuf_addf(&buffer, "%o %.*s%c", mode, entlen, path + baselen, '\0');
@@ -370,10 +372,11 @@ int cache_tree_update(struct cache_tree *it,
                      struct cache_entry **cache,
                      int entries,
                      int missing_ok,
-                     int dryrun)
+                     int dryrun,
+                     int silent)
 {
        int i;
-       i = verify_cache(cache, entries);
+       i = verify_cache(cache, entries, silent);
        if (i)
                return i;
        i = update_one(it, cache, entries, "", 0, missing_ok, dryrun);
@@ -573,7 +576,7 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
 
                if (cache_tree_update(active_cache_tree,
                                      active_cache, active_nr,
-                                     missing_ok, 0) < 0)
+                                     missing_ok, 0, 0) < 0)
                        return WRITE_TREE_UNMERGED_INDEX;
                if (0 <= newfd) {
                        if (!write_cache(newfd, active_cache, active_nr) &&
@@ -668,3 +671,11 @@ int cache_tree_matches_traversal(struct cache_tree *root,
                return it->entry_count;
        return 0;
 }
+
+int update_main_cache_tree (int silent)
+{
+       if (!the_index.cache_tree)
+               the_index.cache_tree = cache_tree();
+       return cache_tree_update(the_index.cache_tree,
+                                the_index.cache, the_index.cache_nr, 0, 0, silent);
+}
index 3df641f59311f43aa951a2cdfa9f110b97b13a45..0ec0b2a159dfd352ca621322a9ce3715328ab2d0 100644 (file)
@@ -29,7 +29,9 @@ void cache_tree_write(struct strbuf *, struct cache_tree *root);
 struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
 
 int cache_tree_fully_valid(struct cache_tree *);
-int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int);
+int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int, int);
+
+int update_main_cache_tree(int);
 
 /* bitmasks to write_cache_as_tree flags */
 #define WRITE_TREE_MISSING_OK 1
diff --git a/cache.h b/cache.h
index 8d95fb25a2505a408aea4beba43409facb578213..3a8e1258e7a15e5ffba52ad400d9ae23cedb0982 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -35,6 +35,7 @@ int git_inflate(git_zstream *, int flush);
 void git_deflate_init(git_zstream *, int level);
 void git_deflate_init_gzip(git_zstream *, int level);
 void git_deflate_end(git_zstream *);
+int git_deflate_abort(git_zstream *);
 int git_deflate_end_gently(git_zstream *);
 int git_deflate(git_zstream *, int flush);
 unsigned long git_deflate_bound(git_zstream *, unsigned long);
@@ -168,6 +169,7 @@ struct cache_entry {
        unsigned int ce_flags;
        unsigned char sha1[20];
        struct cache_entry *next;
+       struct cache_entry *dir_next;
        char name[FLEX_ARRAY]; /* more */
 };
 
@@ -305,7 +307,7 @@ static inline unsigned int canon_mode(unsigned int mode)
 }
 
 #define flexible_size(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
-#define cache_entry_size(len) flexible_size(cache_entry,len)
+#define cache_entry_size(len) (offsetof(struct cache_entry,name) + (len) + 1)
 #define ondisk_cache_entry_size(len) flexible_size(ondisk_cache_entry,len)
 #define ondisk_cache_entry_extended_size(len) flexible_size(ondisk_cache_entry_extended,len)
 
@@ -315,7 +317,6 @@ struct index_state {
        struct string_list *resolve_undo;
        struct cache_tree *cache_tree;
        struct cache_time timestamp;
-       void *alloc;
        unsigned name_hash_initialized : 1,
                 initialized : 1;
        struct hash_table name_hash;
@@ -431,6 +432,7 @@ extern char *git_work_tree_cfg;
 extern int is_inside_work_tree(void);
 extern int have_git_dir(void);
 extern const char *get_git_dir(void);
+extern int is_git_directory(const char *path);
 extern char *get_object_directory(void);
 extern char *get_index_file(void);
 extern char *get_graft_file(void);
@@ -439,12 +441,12 @@ extern const char *get_git_namespace(void);
 extern const char *strip_namespace(const char *namespaced_ref);
 extern const char *get_git_work_tree(void);
 extern const char *read_gitfile(const char *path);
+extern const char *resolve_gitdir(const char *suspect);
 extern void set_git_work_tree(const char *tree);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
 extern const char **get_pathspec(const char *prefix, const char **pathspec);
-extern const char *pathspec_prefix(const char *prefix, const char **pathspec);
 extern void setup_work_tree(void);
 extern const char *setup_git_directory_gently(int *);
 extern const char *setup_git_directory(void);
@@ -597,6 +599,7 @@ extern size_t packed_git_window_size;
 extern size_t packed_git_limit;
 extern size_t delta_base_cache_limit;
 extern unsigned long big_file_threshold;
+extern unsigned long pack_size_limit_cfg;
 extern int read_replace_refs;
 extern int fsync_object_files;
 extern int core_preload_index;
@@ -735,7 +738,7 @@ int safe_create_leading_directories(char *path);
 int safe_create_leading_directories_const(const char *path);
 int mkdir_in_gitdir(const char *path);
 extern char *expand_user_path(const char *path);
-char *enter_repo(char *path, int strict);
+const char *enter_repo(const char *path, int strict);
 static inline int is_absolute_path(const char *path)
 {
        return is_dir_sep(path[0]) || has_dos_drive_prefix(path);
@@ -820,10 +823,54 @@ static inline int get_sha1_with_context(const char *str, unsigned char *sha1, st
 {
        return get_sha1_with_context_1(str, sha1, orc, 0, NULL);
 }
+
+/*
+ * Try to read a SHA1 in hexadecimal format from the 40 characters
+ * starting at hex.  Write the 20-byte result to sha1 in binary form.
+ * Return 0 on success.  Reading stops if a NUL is encountered in the
+ * input, so it is safe to pass this function an arbitrary
+ * null-terminated string.
+ */
 extern int get_sha1_hex(const char *hex, unsigned char *sha1);
+
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer result! */
-extern int read_ref(const char *filename, unsigned char *sha1);
-extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
+extern int read_ref_full(const char *refname, unsigned char *sha1,
+                        int reading, int *flags);
+extern int read_ref(const char *refname, unsigned char *sha1);
+
+/*
+ * Resolve a reference, recursively following symbolic refererences.
+ *
+ * Store the referred-to object's name in sha1 and return the name of
+ * the non-symbolic reference that ultimately pointed at it.  The
+ * return value, if not NULL, is a pointer into either a static buffer
+ * or the input ref.
+ *
+ * If the reference cannot be resolved to an object, the behavior
+ * depends on the "reading" argument:
+ *
+ * - If reading is set, return NULL.
+ *
+ * - If reading is not set, clear sha1 and return the name of the last
+ *   reference name in the chain, which will either be a non-symbolic
+ *   reference or an undefined reference.  If this is a prelude to
+ *   "writing" to the ref, the return value is the name of the ref
+ *   that will actually be created or changed.
+ *
+ * If flag is non-NULL, set the value that it points to the
+ * combination of REF_ISPACKED (if the reference was found among the
+ * packed references) and REF_ISSYMREF (if the initial reference was a
+ * symbolic reference).
+ *
+ * If ref is not a properly-formatted, normalized reference, return
+ * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,
+ * give up and return NULL.
+ *
+ * errno is sometimes set on errors, but not always.
+ */
+extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag);
+extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag);
+
 extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
 extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
 extern int interpret_branch_name(const char *str, struct strbuf *);
@@ -831,7 +878,7 @@ extern int get_sha1_mb(const char *str, unsigned char *sha1);
 
 extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
 extern const char *ref_rev_parse_rules[];
-extern const char *ref_fetch_rules[];
+#define ref_fetch_rules ref_rev_parse_rules
 
 extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
 extern int validate_headref(const char *ref);
@@ -982,17 +1029,16 @@ struct ref {
 extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
 
 #define CONNECT_VERBOSE       (1u << 0)
-extern char *git_getpass(const char *prompt);
 extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
 extern int finish_connect(struct child_process *conn);
 extern int git_connection_is_socket(struct child_process *conn);
-extern int path_match(const char *path, int nr, char **match);
 struct extra_have_objects {
        int nr, alloc;
        unsigned char (*array)[20];
 };
-extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags, struct extra_have_objects *);
+extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *);
 extern int server_supports(const char *feature);
+extern const char *parse_feature_request(const char *features, const char *feature);
 
 extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
 
@@ -1015,6 +1061,7 @@ extern struct packed_git *add_packed_git(const char *, int, int);
 extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t);
 extern off_t nth_packed_object_offset(const struct packed_git *, uint32_t);
 extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *);
+extern int is_pack_valid(struct packed_git *);
 extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
 extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
 extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
@@ -1077,9 +1124,11 @@ extern int git_config_bool(const char *, const char *);
 extern int git_config_maybe_bool(const char *, const char *);
 extern int git_config_string(const char **, const char *, const char *);
 extern int git_config_pathname(const char **, const char *, const char *);
+extern int git_config_set_in_file(const char *, const char *, const char *);
 extern int git_config_set(const char *, const char *);
 extern int git_config_parse_key(const char *, char **, int *);
 extern int git_config_set_multivar(const char *, const char *, const char *, int);
+extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
 extern int git_config_rename_section(const char *, const char *);
 extern const char *git_etc_gitconfig(void);
 extern int check_repository_format_version(const char *var, const char *value, void *cb);
@@ -1128,6 +1177,7 @@ extern void setup_pager(void);
 extern const char *pager_program;
 extern int pager_in_use(void);
 extern int pager_use_color;
+extern int term_columns(void);
 
 extern const char *editor_program;
 extern const char *askpass_program;
index 214014dc645e43ae257e46f1046c8ca0d21d3472..a2e8dcf8553ff15d7cfed8f8ec4735185ec162bb 100644 (file)
@@ -8,6 +8,7 @@
 #include "log-tree.h"
 #include "refs.h"
 #include "userdiff.h"
+#include "sha1-array.h"
 
 static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
 {
@@ -1116,15 +1117,14 @@ static void handle_combined_callback(struct diff_options *opt,
 }
 
 void diff_tree_combined(const unsigned char *sha1,
-                       const unsigned char parent[][20],
-                       int num_parent,
+                       const struct sha1_array *parents,
                        int dense,
                        struct rev_info *rev)
 {
        struct diff_options *opt = &rev->diffopt;
        struct diff_options diffopts;
        struct combine_diff_path *p, *paths = NULL;
-       int i, num_paths, needsep, show_log_first;
+       int i, num_paths, needsep, show_log_first, num_parent = parents->nr;
 
        diffopts = *opt;
        diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
@@ -1144,7 +1144,7 @@ void diff_tree_combined(const unsigned char *sha1,
                        diffopts.output_format = stat_opt;
                else
                        diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
-               diff_tree_sha1(parent[i], sha1, "", &diffopts);
+               diff_tree_sha1(parents->sha1[i], sha1, "", &diffopts);
                diffcore_std(&diffopts);
                paths = intersect_paths(paths, i, num_parent);
 
@@ -1196,25 +1196,16 @@ void diff_tree_combined(const unsigned char *sha1,
        }
 }
 
-void diff_tree_combined_merge(const unsigned char *sha1,
-                            int dense, struct rev_info *rev)
+void diff_tree_combined_merge(const struct commit *commit, int dense,
+                             struct rev_info *rev)
 {
-       int num_parent;
-       const unsigned char (*parent)[20];
-       struct commit *commit = lookup_commit(sha1);
-       struct commit_list *parents;
-
-       /* count parents */
-       for (parents = commit->parents, num_parent = 0;
-            parents;
-            parents = parents->next, num_parent++)
-               ; /* nothing */
-
-       parent = xmalloc(num_parent * sizeof(*parent));
-       for (parents = commit->parents, num_parent = 0;
-            parents;
-            parents = parents->next, num_parent++)
-               hashcpy((unsigned char *)(parent + num_parent),
-                       parents->item->object.sha1);
-       diff_tree_combined(sha1, parent, num_parent, dense, rev);
+       struct commit_list *parent = commit->parents;
+       struct sha1_array parents = SHA1_ARRAY_INIT;
+
+       while (parent) {
+               sha1_array_append(&parents, parent->item->object.sha1);
+               parent = parent->next;
+       }
+       diff_tree_combined(commit->object.sha1, &parents, dense, rev);
+       sha1_array_clear(&parents);
 }
index 95bf18cf06b7c77789e68bd8ab0a312395fb5d86..a36ee9b0150278e8fc4b61e7226df18409b334be 100644 (file)
@@ -130,5 +130,6 @@ git-upload-pack                         synchelpers
 git-var                                 plumbinginterrogators
 git-verify-pack                         plumbinginterrogators
 git-verify-tag                          ancillaryinterrogators
+gitweb                                  ancillaryinterrogators
 git-whatchanged                         ancillaryinterrogators
 git-write-tree                          plumbingmanipulators
index 9f4cc636dd74e99a1311500e15501bdb4875cb9d..35af4988f0ff83c6a3379ea9f6de4e4e1568c39f 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -6,6 +6,7 @@
 #include "diff.h"
 #include "revision.h"
 #include "notes.h"
+#include "gpg-interface.h"
 
 int save_commit_buffer = 1;
 
@@ -442,6 +443,20 @@ void clear_commit_marks(struct commit *commit, unsigned int mark)
        }
 }
 
+void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
+{
+       struct object *object;
+       struct commit *commit;
+       unsigned int i;
+
+       for (i = 0; i < a->nr; i++) {
+               object = a->objects[i].item;
+               commit = lookup_commit_reference_gently(object->sha1, 1);
+               if (commit)
+                       clear_commit_marks(commit, mark);
+       }
+}
+
 struct commit *pop_commit(struct commit_list **stack)
 {
        struct commit_list *top = *stack;
@@ -826,14 +841,260 @@ struct commit_list *reduce_heads(struct commit_list *heads)
        return result;
 }
 
+static const char gpg_sig_header[] = "gpgsig";
+static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
+
+static int do_sign_commit(struct strbuf *buf, const char *keyid)
+{
+       struct strbuf sig = STRBUF_INIT;
+       int inspos, copypos;
+
+       /* find the end of the header */
+       inspos = strstr(buf->buf, "\n\n") - buf->buf + 1;
+
+       if (!keyid || !*keyid)
+               keyid = get_signing_key();
+       if (sign_buffer(buf, &sig, keyid)) {
+               strbuf_release(&sig);
+               return -1;
+       }
+
+       for (copypos = 0; sig.buf[copypos]; ) {
+               const char *bol = sig.buf + copypos;
+               const char *eol = strchrnul(bol, '\n');
+               int len = (eol - bol) + !!*eol;
+
+               if (!copypos) {
+                       strbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);
+                       inspos += gpg_sig_header_len;
+               }
+               strbuf_insert(buf, inspos++, " ", 1);
+               strbuf_insert(buf, inspos, bol, len);
+               inspos += len;
+               copypos += len;
+       }
+       strbuf_release(&sig);
+       return 0;
+}
+
+int parse_signed_commit(const unsigned char *sha1,
+                       struct strbuf *payload, struct strbuf *signature)
+{
+       unsigned long size;
+       enum object_type type;
+       char *buffer = read_sha1_file(sha1, &type, &size);
+       int in_signature, saw_signature = -1;
+       char *line, *tail;
+
+       if (!buffer || type != OBJ_COMMIT)
+               goto cleanup;
+
+       line = buffer;
+       tail = buffer + size;
+       in_signature = 0;
+       saw_signature = 0;
+       while (line < tail) {
+               const char *sig = NULL;
+               char *next = memchr(line, '\n', tail - line);
+
+               next = next ? next + 1 : tail;
+               if (in_signature && line[0] == ' ')
+                       sig = line + 1;
+               else if (!prefixcmp(line, gpg_sig_header) &&
+                        line[gpg_sig_header_len] == ' ')
+                       sig = line + gpg_sig_header_len + 1;
+               if (sig) {
+                       strbuf_add(signature, sig, next - sig);
+                       saw_signature = 1;
+                       in_signature = 1;
+               } else {
+                       if (*line == '\n')
+                               /* dump the whole remainder of the buffer */
+                               next = tail;
+                       strbuf_add(payload, line, next - line);
+                       in_signature = 0;
+               }
+               line = next;
+       }
+ cleanup:
+       free(buffer);
+       return saw_signature;
+}
+
+static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)
+{
+       struct merge_remote_desc *desc;
+       struct commit_extra_header *mergetag;
+       char *buf;
+       unsigned long size, len;
+       enum object_type type;
+
+       desc = merge_remote_util(parent);
+       if (!desc || !desc->obj)
+               return;
+       buf = read_sha1_file(desc->obj->sha1, &type, &size);
+       if (!buf || type != OBJ_TAG)
+               goto free_return;
+       len = parse_signature(buf, size);
+       if (size == len)
+               goto free_return;
+       /*
+        * We could verify this signature and either omit the tag when
+        * it does not validate, but the integrator may not have the
+        * public key of the signer of the tag he is merging, while a
+        * later auditor may have it while auditing, so let's not run
+        * verify-signed-buffer here for now...
+        *
+        * if (verify_signed_buffer(buf, len, buf + len, size - len, ...))
+        *      warn("warning: signed tag unverified.");
+        */
+       mergetag = xcalloc(1, sizeof(*mergetag));
+       mergetag->key = xstrdup("mergetag");
+       mergetag->value = buf;
+       mergetag->len = size;
+
+       **tail = mergetag;
+       *tail = &mergetag->next;
+       return;
+
+free_return:
+       free(buf);
+}
+
+void append_merge_tag_headers(struct commit_list *parents,
+                             struct commit_extra_header ***tail)
+{
+       while (parents) {
+               struct commit *parent = parents->item;
+               handle_signed_tag(parent, tail);
+               parents = parents->next;
+       }
+}
+
+static void add_extra_header(struct strbuf *buffer,
+                            struct commit_extra_header *extra)
+{
+       strbuf_addstr(buffer, extra->key);
+       if (extra->len)
+               strbuf_add_lines(buffer, " ", extra->value, extra->len);
+       else
+               strbuf_addch(buffer, '\n');
+}
+
+struct commit_extra_header *read_commit_extra_headers(struct commit *commit,
+                                                     const char **exclude)
+{
+       struct commit_extra_header *extra = NULL;
+       unsigned long size;
+       enum object_type type;
+       char *buffer = read_sha1_file(commit->object.sha1, &type, &size);
+       if (buffer && type == OBJ_COMMIT)
+               extra = read_commit_extra_header_lines(buffer, size, exclude);
+       free(buffer);
+       return extra;
+}
+
+static inline int standard_header_field(const char *field, size_t len)
+{
+       return ((len == 4 && !memcmp(field, "tree ", 5)) ||
+               (len == 6 && !memcmp(field, "parent ", 7)) ||
+               (len == 6 && !memcmp(field, "author ", 7)) ||
+               (len == 9 && !memcmp(field, "committer ", 10)) ||
+               (len == 8 && !memcmp(field, "encoding ", 9)));
+}
+
+static int excluded_header_field(const char *field, size_t len, const char **exclude)
+{
+       if (!exclude)
+               return 0;
+
+       while (*exclude) {
+               size_t xlen = strlen(*exclude);
+               if (len == xlen &&
+                   !memcmp(field, *exclude, xlen) && field[xlen] == ' ')
+                       return 1;
+               exclude++;
+       }
+       return 0;
+}
+
+struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, size_t size,
+                                                          const char **exclude)
+{
+       struct commit_extra_header *extra = NULL, **tail = &extra, *it = NULL;
+       const char *line, *next, *eof, *eob;
+       struct strbuf buf = STRBUF_INIT;
+
+       for (line = buffer, eob = line + size;
+            line < eob && *line != '\n';
+            line = next) {
+               next = memchr(line, '\n', eob - line);
+               next = next ? next + 1 : eob;
+               if (*line == ' ') {
+                       /* continuation */
+                       if (it)
+                               strbuf_add(&buf, line + 1, next - (line + 1));
+                       continue;
+               }
+               if (it)
+                       it->value = strbuf_detach(&buf, &it->len);
+               strbuf_reset(&buf);
+               it = NULL;
+
+               eof = strchr(line, ' ');
+               if (next <= eof)
+                       eof = next;
+
+               if (standard_header_field(line, eof - line) ||
+                   excluded_header_field(line, eof - line, exclude))
+                       continue;
+
+               it = xcalloc(1, sizeof(*it));
+               it->key = xmemdupz(line, eof-line);
+               *tail = it;
+               tail = &it->next;
+               if (eof + 1 < next)
+                       strbuf_add(&buf, eof + 1, next - (eof + 1));
+       }
+       if (it)
+               it->value = strbuf_detach(&buf, &it->len);
+       return extra;
+}
+
+void free_commit_extra_headers(struct commit_extra_header *extra)
+{
+       while (extra) {
+               struct commit_extra_header *next = extra->next;
+               free(extra->key);
+               free(extra->value);
+               free(extra);
+               extra = next;
+       }
+}
+
+int commit_tree(const struct strbuf *msg, unsigned char *tree,
+               struct commit_list *parents, unsigned char *ret,
+               const char *author, const char *sign_commit)
+{
+       struct commit_extra_header *extra = NULL, **tail = &extra;
+       int result;
+
+       append_merge_tag_headers(parents, &tail);
+       result = commit_tree_extended(msg, tree, parents, ret,
+                                     author, sign_commit, extra);
+       free_commit_extra_headers(extra);
+       return result;
+}
+
 static const char commit_utf8_warn[] =
 "Warning: commit message does not conform to UTF-8.\n"
 "You may want to amend it after fixing the message, or set the config\n"
 "variable i18n.commitencoding to the encoding your project uses.\n";
 
-int commit_tree(const char *msg, unsigned char *tree,
-               struct commit_list *parents, unsigned char *ret,
-               const char *author)
+int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
+                        struct commit_list *parents, unsigned char *ret,
+                        const char *author, const char *sign_commit,
+                        struct commit_extra_header *extra)
 {
        int result;
        int encoding_is_utf8;
@@ -841,6 +1102,9 @@ int commit_tree(const char *msg, unsigned char *tree,
 
        assert_sha1_type(tree, OBJ_TREE);
 
+       if (memchr(msg->buf, '\0', msg->len))
+               return error("a NUL byte in commit log message not allowed.");
+
        /* Not having i18n.commitencoding is the same as having utf-8 */
        encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
 
@@ -854,8 +1118,10 @@ int commit_tree(const char *msg, unsigned char *tree,
         */
        while (parents) {
                struct commit_list *next = parents->next;
+               struct commit *parent = parents->item;
+
                strbuf_addf(&buffer, "parent %s\n",
-                       sha1_to_hex(parents->item->object.sha1));
+                           sha1_to_hex(parent->object.sha1));
                free(parents);
                parents = next;
        }
@@ -867,16 +1133,43 @@ int commit_tree(const char *msg, unsigned char *tree,
        strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
        if (!encoding_is_utf8)
                strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
+
+       while (extra) {
+               add_extra_header(&buffer, extra);
+               extra = extra->next;
+       }
        strbuf_addch(&buffer, '\n');
 
        /* And add the comment */
-       strbuf_addstr(&buffer, msg);
+       strbuf_addbuf(&buffer, msg);
 
        /* And check the encoding */
        if (encoding_is_utf8 && !is_utf8(buffer.buf))
                fprintf(stderr, commit_utf8_warn);
 
+       if (sign_commit && do_sign_commit(&buffer, sign_commit))
+               return -1;
+
        result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
        strbuf_release(&buffer);
        return result;
 }
+
+struct commit *get_merge_parent(const char *name)
+{
+       struct object *obj;
+       struct commit *commit;
+       unsigned char sha1[20];
+       if (get_sha1(name, sha1))
+               return NULL;
+       obj = parse_object(sha1);
+       commit = (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);
+       if (commit && !commit->util) {
+               struct merge_remote_desc *desc;
+               desc = xmalloc(sizeof(*desc));
+               desc->obj = obj;
+               desc->name = strdup(name);
+               commit->util = desc;
+       }
+       return commit;
+}
index 14f6a5a2ed91e2176cce4543798774f88a8628b1..154c0e34ff7d2dbaddcfb66b74d26697ffba6381 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -133,6 +133,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
 struct commit *pop_commit(struct commit_list **stack);
 
 void clear_commit_marks(struct commit *commit, unsigned int mark);
+void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
 
 /*
  * Performs an in-place topological sort of list supplied.
@@ -180,8 +181,43 @@ static inline int single_parent(struct commit *commit)
 
 struct commit_list *reduce_heads(struct commit_list *heads);
 
-extern int commit_tree(const char *msg, unsigned char *tree,
-               struct commit_list *parents, unsigned char *ret,
-               const char *author);
+struct commit_extra_header {
+       struct commit_extra_header *next;
+       char *key;
+       char *value;
+       size_t len;
+};
+
+extern void append_merge_tag_headers(struct commit_list *parents,
+                                    struct commit_extra_header ***tail);
+
+extern int commit_tree(const struct strbuf *msg, unsigned char *tree,
+                      struct commit_list *parents, unsigned char *ret,
+                      const char *author, const char *sign_commit);
+
+extern int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
+                               struct commit_list *parents, unsigned char *ret,
+                               const char *author, const char *sign_commit,
+                               struct commit_extra_header *);
+
+extern struct commit_extra_header *read_commit_extra_headers(struct commit *, const char **);
+extern struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
+
+extern void free_commit_extra_headers(struct commit_extra_header *extra);
+
+struct merge_remote_desc {
+       struct object *obj; /* the named object, could be a tag */
+       const char *name;
+};
+#define merge_remote_util(commit) ((struct merge_remote_desc *)((commit)->util))
+
+/*
+ * Given "name" from the command line to merge, find the commit object
+ * and return it, while storing merge_remote_desc in its ->util field,
+ * to allow callers to tell if we are told to merge a tag.
+ */
+struct commit *get_merge_parent(const char *name);
 
+extern int parse_signed_commit(const unsigned char *sha1,
+                              struct strbuf *message, struct strbuf *signature);
 #endif /* COMMIT_H */
index ea249c6ac6423fd4ef865c1a9d0149ac0ba0cc46..90b7cc45f33411bbc39cd731ca3e43288cb40b69 100644 (file)
  * SOFTWARE.
  */
 
-#include <errno.h>
-#include <sys/types.h>
-
 #include "../git-compat-util.h"
 
-#include <stdio.h>
-#include <string.h>
-
 #ifndef NS_INADDRSZ
 #define NS_INADDRSZ    4
 #endif
@@ -98,7 +92,9 @@ inet_ntop6(const u_char *src, char *dst, size_t size)
        for (i = 0; i < NS_IN6ADDRSZ; i++)
                words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3));
        best.base = -1;
+       best.len = 0;
        cur.base = -1;
+       cur.len = 0;
        for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
                if (words[i] == 0) {
                        if (cur.base == -1)
index 2ec995e63d0cdef6dcd8b2fd6ea7aa544f4d1b33..2b9a0a4e22fa5256575b6b184a16fc93ee47869e 100644 (file)
  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <errno.h>
-#include <sys/types.h>
-
 #include "../git-compat-util.h"
 
-#include <stdio.h>
-#include <string.h>
-
 #ifndef NS_INT16SZ
 #define NS_INT16SZ       2
 #endif
index f899bf08394a9b4b940a311572ab0dc31bdf17de..a0ac487c0c12ea0e7c81485b5784e28f2115a2ea 100644 (file)
@@ -1321,6 +1321,13 @@ static void ensure_socket_initialization(void)
        initialized = 1;
 }
 
+#undef gethostname
+int mingw_gethostname(char *name, int namelen)
+{
+    ensure_socket_initialization();
+    return gethostname(name, namelen);
+}
+
 #undef gethostbyname
 struct hostent *mingw_gethostbyname(const char *host)
 {
index 43e42f4481a371af5f886bdb0ce1cc789dfdb82e..0ff1e04812ef2491f15b1d40bb8e1c2977b26d98 100644 (file)
@@ -190,6 +190,9 @@ char *mingw_getcwd(char *pointer, int len);
 char *mingw_getenv(const char *name);
 #define getenv mingw_getenv
 
+int mingw_gethostname(char *host, int namelen);
+#define gethostname mingw_gethostname
+
 struct hostent *mingw_gethostbyname(const char *host);
 #define gethostbyname mingw_gethostbyname
 
index a33b01c032b1ab948d87b29447ad0787c86f14f1..aa4b56315ae2488ea656d695f0f660e4dbd745ef 100644 (file)
@@ -4,6 +4,7 @@
 #include <direct.h>
 #include <process.h>
 #include <malloc.h>
+#include <io.h>
 
 /* porting function */
 #define inline __inline
index a89ab5b8e88d5641f9e5cca5b3f9681d12069d39..e276ccd7b38faf3d96778c300ebca0d0a22fe410 100644 (file)
@@ -173,7 +173,7 @@ _obstack_begin (struct obstack *h,
                                               alignment - 1);
   h->chunk_limit = chunk->limit
     = (char *) chunk + h->chunk_size;
-  chunk->prev = 0;
+  chunk->prev = NULL;
   /* The initial chunk now contains no empty object.  */
   h->maybe_empty_object = 0;
   h->alloc_failed = 0;
@@ -221,7 +221,7 @@ _obstack_begin_1 (struct obstack *h, int size, int alignment,
                                               alignment - 1);
   h->chunk_limit = chunk->limit
     = (char *) chunk + h->chunk_size;
-  chunk->prev = 0;
+  chunk->prev = NULL;
   /* The initial chunk now contains no empty object.  */
   h->maybe_empty_object = 0;
   h->alloc_failed = 0;
@@ -321,12 +321,12 @@ _obstack_allocated_p (struct obstack *h, void *obj)
   /* We use >= rather than > since the object cannot be exactly at
      the beginning of the chunk but might be an empty object exactly
      at the end of an adjacent chunk.  */
-  while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj))
+  while (lp != NULL && ((void *) lp >= obj || (void *) (lp)->limit < obj))
     {
       plp = lp->prev;
       lp = plp;
     }
-  return lp != 0;
+  return lp != NULL;
 }
 \f
 /* Free objects in obstack H, including OBJ and everything allocate
@@ -344,7 +344,7 @@ obstack_free (struct obstack *h, void *obj)
   /* We use >= because there cannot be an object at the beginning of a chunk.
      But there can be an empty object at that address
      at the end of another chunk.  */
-  while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj))
+  while (lp != NULL && ((void *) lp >= obj || (void *) (lp)->limit < obj))
     {
       plp = lp->prev;
       CALL_FREEFUN (h, lp);
@@ -359,7 +359,7 @@ obstack_free (struct obstack *h, void *obj)
       h->chunk_limit = lp->limit;
       h->chunk = lp;
     }
-  else if (obj != 0)
+  else if (obj != NULL)
     /* obj is not in any of the chunks! */
     abort ();
 }
@@ -376,7 +376,7 @@ _obstack_memory_used (struct obstack *h)
   register struct _obstack_chunk* lp;
   register int nbytes = 0;
 
-  for (lp = h->chunk; lp != 0; lp = lp->prev)
+  for (lp = h->chunk; lp != NULL; lp = lp->prev)
     {
       nbytes += lp->limit - (char *) lp;
     }
@@ -395,7 +395,6 @@ _obstack_memory_used (struct obstack *h)
 # endif
 
 static void
-__attribute__ ((noreturn))
 print_and_abort (void)
 {
   /* Don't change any of these strings.  Yes, it would be possible to add
index 3a22ea7b751efb768d72afa2f97fd963e10eec7e..fc1439a6439393ff5224e98ab126607ceaa4994d 100644 (file)
@@ -6,7 +6,10 @@ int gitsetenv(const char *name, const char *value, int replace)
        size_t namelen, valuelen;
        char *envstr;
 
-       if (!name || !value) return -1;
+       if (!name || strchr(name, '=') || !value) {
+               errno = EINVAL;
+               return -1;
+       }
        if (!replace) {
                char *oldval = NULL;
                oldval = getenv(name);
@@ -16,7 +19,10 @@ int gitsetenv(const char *name, const char *value, int replace)
        namelen = strlen(name);
        valuelen = strlen(value);
        envstr = malloc((namelen + valuelen + 2));
-       if (!envstr) return -1;
+       if (!envstr) {
+               errno = ENOMEM;
+               return -1;
+       }
 
        memcpy(envstr, name, namelen);
        envstr[namelen] = '=';
index e1e0e7543d9414726122c121b7909bf73809a81a..42ea1ac110813bbd16e77cfbc36f16e6a5e9ddb2 100644 (file)
 #undef vsnprintf
 int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap)
 {
+       va_list cp;
        char *s;
        int ret = -1;
 
        if (maxsize > 0) {
-               ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, ap);
+               va_copy(cp, ap);
+               ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, cp);
+               va_end(cp);
                if (ret == maxsize-1)
                        ret = -1;
                /* Windows does not NUL-terminate if result fills buffer */
@@ -42,7 +45,9 @@ int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap)
                if (! str)
                        break;
                s = str;
-               ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, ap);
+               va_copy(cp, ap);
+               ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, cp);
+               va_end(cp);
                if (ret == maxsize-1)
                        ret = -1;
        }
diff --git a/compat/strtoimax.c b/compat/strtoimax.c
new file mode 100644 (file)
index 0000000..ac09ed8
--- /dev/null
@@ -0,0 +1,10 @@
+#include "../git-compat-util.h"
+
+intmax_t gitstrtoimax (const char *nptr, char **endptr, int base)
+{
+#if defined(NO_STRTOULL)
+       return strtol(nptr, endptr, base);
+#else
+       return strtoll(nptr, endptr, base);
+#endif
+}
diff --git a/compat/terminal.c b/compat/terminal.c
new file mode 100644 (file)
index 0000000..6d16c8f
--- /dev/null
@@ -0,0 +1,81 @@
+#include "git-compat-util.h"
+#include "compat/terminal.h"
+#include "sigchain.h"
+#include "strbuf.h"
+
+#ifdef HAVE_DEV_TTY
+
+static int term_fd = -1;
+static struct termios old_term;
+
+static void restore_term(void)
+{
+       if (term_fd < 0)
+               return;
+
+       tcsetattr(term_fd, TCSAFLUSH, &old_term);
+       term_fd = -1;
+}
+
+static void restore_term_on_signal(int sig)
+{
+       restore_term();
+       sigchain_pop(sig);
+       raise(sig);
+}
+
+char *git_terminal_prompt(const char *prompt, int echo)
+{
+       static struct strbuf buf = STRBUF_INIT;
+       int r;
+       FILE *fh;
+
+       fh = fopen("/dev/tty", "w+");
+       if (!fh)
+               return NULL;
+
+       if (!echo) {
+               struct termios t;
+
+               if (tcgetattr(fileno(fh), &t) < 0) {
+                       fclose(fh);
+                       return NULL;
+               }
+
+               old_term = t;
+               term_fd = fileno(fh);
+               sigchain_push_common(restore_term_on_signal);
+
+               t.c_lflag &= ~ECHO;
+               if (tcsetattr(fileno(fh), TCSAFLUSH, &t) < 0) {
+                       term_fd = -1;
+                       fclose(fh);
+                       return NULL;
+               }
+       }
+
+       fputs(prompt, fh);
+       fflush(fh);
+
+       r = strbuf_getline(&buf, fh, '\n');
+       if (!echo) {
+               putc('\n', fh);
+               fflush(fh);
+       }
+
+       restore_term();
+       fclose(fh);
+
+       if (r == EOF)
+               return NULL;
+       return buf.buf;
+}
+
+#else
+
+char *git_terminal_prompt(const char *prompt, int echo)
+{
+       return getpass(prompt);
+}
+
+#endif
diff --git a/compat/terminal.h b/compat/terminal.h
new file mode 100644 (file)
index 0000000..97db7cd
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef COMPAT_TERMINAL_H
+#define COMPAT_TERMINAL_H
+
+char *git_terminal_prompt(const char *prompt, int echo);
+
+#endif /* COMPAT_TERMINAL_H */
diff --git a/compat/vcbuild/include/arpa/inet.h b/compat/vcbuild/include/arpa/inet.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/grp.h b/compat/vcbuild/include/grp.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/inttypes.h b/compat/vcbuild/include/inttypes.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/netdb.h b/compat/vcbuild/include/netdb.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* 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
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* 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
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/pwd.h b/compat/vcbuild/include/pwd.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* 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
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* 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
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* 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
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/wait.h b/compat/vcbuild/include/sys/wait.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/termios.h b/compat/vcbuild/include/termios.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/win32/poll.c b/compat/win32/poll.c
new file mode 100644 (file)
index 0000000..403eaa7
--- /dev/null
@@ -0,0 +1,606 @@
+/* Emulation for poll(2)
+   Contributed by Paolo Bonzini.
+
+   Copyright 2001-2003, 2006-2011 Free Software Foundation, Inc.
+
+   This file is part of gnulib.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with this program; if not, write to the Free Software Foundation,
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+
+/* Tell gcc not to warn about the (nfd < 0) tests, below.  */
+#if (__GNUC__ == 4 && 3 <= __GNUC_MINOR__) || 4 < __GNUC__
+# pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+#include <malloc.h>
+
+#include <sys/types.h>
+
+/* Specification.  */
+#include <poll.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <assert.h>
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+# define WIN32_NATIVE
+# if defined (_MSC_VER)
+#  define _WIN32_WINNT 0x0502
+# endif
+# include <winsock2.h>
+# include <windows.h>
+# include <io.h>
+# include <stdio.h>
+# include <conio.h>
+#else
+# include <sys/time.h>
+# include <sys/socket.h>
+# include <sys/select.h>
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#include <time.h>
+
+#ifndef INFTIM
+# define INFTIM (-1)
+#endif
+
+/* BeOS does not have MSG_PEEK.  */
+#ifndef MSG_PEEK
+# define MSG_PEEK 0
+#endif
+
+#ifdef WIN32_NATIVE
+
+#define IsConsoleHandle(h) (((long) (h) & 3) == 3)
+
+static BOOL
+IsSocketHandle (HANDLE h)
+{
+  WSANETWORKEVENTS ev;
+
+  if (IsConsoleHandle (h))
+    return FALSE;
+
+  /* Under Wine, it seems that getsockopt returns 0 for pipes too.
+     WSAEnumNetworkEvents instead distinguishes the two correctly.  */
+  ev.lNetworkEvents = 0xDEADBEEF;
+  WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
+  return ev.lNetworkEvents != 0xDEADBEEF;
+}
+
+/* Declare data structures for ntdll functions.  */
+typedef struct _FILE_PIPE_LOCAL_INFORMATION {
+  ULONG NamedPipeType;
+  ULONG NamedPipeConfiguration;
+  ULONG MaximumInstances;
+  ULONG CurrentInstances;
+  ULONG InboundQuota;
+  ULONG ReadDataAvailable;
+  ULONG OutboundQuota;
+  ULONG WriteQuotaAvailable;
+  ULONG NamedPipeState;
+  ULONG NamedPipeEnd;
+} FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION;
+
+typedef struct _IO_STATUS_BLOCK
+{
+  union {
+    DWORD Status;
+    PVOID Pointer;
+  } u;
+  ULONG_PTR Information;
+} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+typedef enum _FILE_INFORMATION_CLASS {
+  FilePipeLocalInformation = 24
+} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
+
+typedef DWORD (WINAPI *PNtQueryInformationFile)
+        (HANDLE, IO_STATUS_BLOCK *, VOID *, ULONG, FILE_INFORMATION_CLASS);
+
+# ifndef PIPE_BUF
+#  define PIPE_BUF      512
+# endif
+
+/* Compute revents values for file handle H.  If some events cannot happen
+   for the handle, eliminate them from *P_SOUGHT.  */
+
+static int
+win32_compute_revents (HANDLE h, int *p_sought)
+{
+  int i, ret, happened;
+  INPUT_RECORD *irbuffer;
+  DWORD avail, nbuffer;
+  BOOL bRet;
+  IO_STATUS_BLOCK iosb;
+  FILE_PIPE_LOCAL_INFORMATION fpli;
+  static PNtQueryInformationFile NtQueryInformationFile;
+  static BOOL once_only;
+
+  switch (GetFileType (h))
+    {
+    case FILE_TYPE_PIPE:
+      if (!once_only)
+       {
+         NtQueryInformationFile = (PNtQueryInformationFile)
+           GetProcAddress (GetModuleHandle ("ntdll.dll"),
+                           "NtQueryInformationFile");
+         once_only = TRUE;
+       }
+
+      happened = 0;
+      if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0)
+       {
+         if (avail)
+           happened |= *p_sought & (POLLIN | POLLRDNORM);
+       }
+      else if (GetLastError () == ERROR_BROKEN_PIPE)
+       happened |= POLLHUP;
+
+      else
+       {
+         /* It was the write-end of the pipe.  Check if it is writable.
+            If NtQueryInformationFile fails, optimistically assume the pipe is
+            writable.  This could happen on Win9x, where NtQueryInformationFile
+            is not available, or if we inherit a pipe that doesn't permit
+            FILE_READ_ATTRIBUTES access on the write end (I think this should
+            not happen since WinXP SP2; WINE seems fine too).  Otherwise,
+            ensure that enough space is available for atomic writes.  */
+         memset (&iosb, 0, sizeof (iosb));
+         memset (&fpli, 0, sizeof (fpli));
+
+         if (!NtQueryInformationFile
+             || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli),
+                                        FilePipeLocalInformation)
+             || fpli.WriteQuotaAvailable >= PIPE_BUF
+             || (fpli.OutboundQuota < PIPE_BUF &&
+                 fpli.WriteQuotaAvailable == fpli.OutboundQuota))
+           happened |= *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
+       }
+      return happened;
+
+    case FILE_TYPE_CHAR:
+      ret = WaitForSingleObject (h, 0);
+      if (!IsConsoleHandle (h))
+       return ret == WAIT_OBJECT_0 ? *p_sought & ~(POLLPRI | POLLRDBAND) : 0;
+
+      nbuffer = avail = 0;
+      bRet = GetNumberOfConsoleInputEvents (h, &nbuffer);
+      if (bRet)
+       {
+         /* Input buffer.  */
+         *p_sought &= POLLIN | POLLRDNORM;
+         if (nbuffer == 0)
+           return POLLHUP;
+         if (!*p_sought)
+           return 0;
+
+         irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD));
+         bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail);
+         if (!bRet || avail == 0)
+           return POLLHUP;
+
+         for (i = 0; i < avail; i++)
+           if (irbuffer[i].EventType == KEY_EVENT)
+             return *p_sought;
+         return 0;
+       }
+      else
+       {
+         /* Screen buffer.  */
+         *p_sought &= POLLOUT | POLLWRNORM | POLLWRBAND;
+         return *p_sought;
+       }
+
+    default:
+      ret = WaitForSingleObject (h, 0);
+      if (ret == WAIT_OBJECT_0)
+       return *p_sought & ~(POLLPRI | POLLRDBAND);
+
+      return *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
+    }
+}
+
+/* Convert fd_sets returned by select into revents values.  */
+
+static int
+win32_compute_revents_socket (SOCKET h, int sought, long lNetworkEvents)
+{
+  int happened = 0;
+
+  if ((lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE)) == FD_ACCEPT)
+    happened |= (POLLIN | POLLRDNORM) & sought;
+
+  else if (lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE))
+    {
+      int r, error;
+
+      char data[64];
+      WSASetLastError (0);
+      r = recv (h, data, sizeof (data), MSG_PEEK);
+      error = WSAGetLastError ();
+      WSASetLastError (0);
+
+      if (r > 0 || error == WSAENOTCONN)
+       happened |= (POLLIN | POLLRDNORM) & sought;
+
+      /* Distinguish hung-up sockets from other errors.  */
+      else if (r == 0 || error == WSAESHUTDOWN || error == WSAECONNRESET
+              || error == WSAECONNABORTED || error == WSAENETRESET)
+       happened |= POLLHUP;
+
+      else
+       happened |= POLLERR;
+    }
+
+  if (lNetworkEvents & (FD_WRITE | FD_CONNECT))
+    happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
+
+  if (lNetworkEvents & FD_OOB)
+    happened |= (POLLPRI | POLLRDBAND) & sought;
+
+  return happened;
+}
+
+#else /* !MinGW */
+
+/* Convert select(2) returned fd_sets into poll(2) revents values.  */
+static int
+compute_revents (int fd, int sought, fd_set *rfds, fd_set *wfds, fd_set *efds)
+{
+  int happened = 0;
+  if (FD_ISSET (fd, rfds))
+    {
+      int r;
+      int socket_errno;
+
+# if defined __MACH__ && defined __APPLE__
+      /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK
+        for some kinds of descriptors.  Detect if this descriptor is a
+        connected socket, a server socket, or something else using a
+        0-byte recv, and use ioctl(2) to detect POLLHUP.  */
+      r = recv (fd, NULL, 0, MSG_PEEK);
+      socket_errno = (r < 0) ? errno : 0;
+      if (r == 0 || socket_errno == ENOTSOCK)
+       ioctl (fd, FIONREAD, &r);
+# else
+      char data[64];
+      r = recv (fd, data, sizeof (data), MSG_PEEK);
+      socket_errno = (r < 0) ? errno : 0;
+# endif
+      if (r == 0)
+       happened |= POLLHUP;
+
+      /* If the event happened on an unconnected server socket,
+        that's fine. */
+      else if (r > 0 || ( /* (r == -1) && */ socket_errno == ENOTCONN))
+       happened |= (POLLIN | POLLRDNORM) & sought;
+
+      /* Distinguish hung-up sockets from other errors.  */
+      else if (socket_errno == ESHUTDOWN || socket_errno == ECONNRESET
+              || socket_errno == ECONNABORTED || socket_errno == ENETRESET)
+       happened |= POLLHUP;
+
+      else
+       happened |= POLLERR;
+    }
+
+  if (FD_ISSET (fd, wfds))
+    happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
+
+  if (FD_ISSET (fd, efds))
+    happened |= (POLLPRI | POLLRDBAND) & sought;
+
+  return happened;
+}
+#endif /* !MinGW */
+
+int
+poll (struct pollfd *pfd, nfds_t nfd, int timeout)
+{
+#ifndef WIN32_NATIVE
+  fd_set rfds, wfds, efds;
+  struct timeval tv;
+  struct timeval *ptv;
+  int maxfd, rc;
+  nfds_t i;
+
+# ifdef _SC_OPEN_MAX
+  static int sc_open_max = -1;
+
+  if (nfd < 0
+      || (nfd > sc_open_max
+         && (sc_open_max != -1
+             || nfd > (sc_open_max = sysconf (_SC_OPEN_MAX)))))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+# else /* !_SC_OPEN_MAX */
+#  ifdef OPEN_MAX
+  if (nfd < 0 || nfd > OPEN_MAX)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+#  endif /* OPEN_MAX -- else, no check is needed */
+# endif /* !_SC_OPEN_MAX */
+
+  /* EFAULT is not necessary to implement, but let's do it in the
+     simplest case. */
+  if (!pfd)
+    {
+      errno = EFAULT;
+      return -1;
+    }
+
+  /* convert timeout number into a timeval structure */
+  if (timeout == 0)
+    {
+      ptv = &tv;
+      ptv->tv_sec = 0;
+      ptv->tv_usec = 0;
+    }
+  else if (timeout > 0)
+    {
+      ptv = &tv;
+      ptv->tv_sec = timeout / 1000;
+      ptv->tv_usec = (timeout % 1000) * 1000;
+    }
+  else if (timeout == INFTIM)
+    /* wait forever */
+    ptv = NULL;
+  else
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  /* create fd sets and determine max fd */
+  maxfd = -1;
+  FD_ZERO (&rfds);
+  FD_ZERO (&wfds);
+  FD_ZERO (&efds);
+  for (i = 0; i < nfd; i++)
+    {
+      if (pfd[i].fd < 0)
+       continue;
+
+      if (pfd[i].events & (POLLIN | POLLRDNORM))
+       FD_SET (pfd[i].fd, &rfds);
+
+      /* see select(2): "the only exceptional condition detectable
+        is out-of-band data received on a socket", hence we push
+        POLLWRBAND events onto wfds instead of efds. */
+      if (pfd[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND))
+       FD_SET (pfd[i].fd, &wfds);
+      if (pfd[i].events & (POLLPRI | POLLRDBAND))
+       FD_SET (pfd[i].fd, &efds);
+      if (pfd[i].fd >= maxfd
+         && (pfd[i].events & (POLLIN | POLLOUT | POLLPRI
+                              | POLLRDNORM | POLLRDBAND
+                              | POLLWRNORM | POLLWRBAND)))
+       {
+         maxfd = pfd[i].fd;
+         if (maxfd > FD_SETSIZE)
+           {
+             errno = EOVERFLOW;
+             return -1;
+           }
+       }
+    }
+
+  /* examine fd sets */
+  rc = select (maxfd + 1, &rfds, &wfds, &efds, ptv);
+  if (rc < 0)
+    return rc;
+
+  /* establish results */
+  rc = 0;
+  for (i = 0; i < nfd; i++)
+    if (pfd[i].fd < 0)
+      pfd[i].revents = 0;
+    else
+      {
+       int happened = compute_revents (pfd[i].fd, pfd[i].events,
+                                       &rfds, &wfds, &efds);
+       if (happened)
+         {
+           pfd[i].revents = happened;
+           rc++;
+         }
+      }
+
+  return rc;
+#else
+  static struct timeval tv0;
+  static HANDLE hEvent;
+  WSANETWORKEVENTS ev;
+  HANDLE h, handle_array[FD_SETSIZE + 2];
+  DWORD ret, wait_timeout, nhandles;
+  fd_set rfds, wfds, xfds;
+  BOOL poll_again;
+  MSG msg;
+  int rc = 0;
+  nfds_t i;
+
+  if (nfd < 0 || timeout < -1)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  if (!hEvent)
+    hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
+
+restart:
+  handle_array[0] = hEvent;
+  nhandles = 1;
+  FD_ZERO (&rfds);
+  FD_ZERO (&wfds);
+  FD_ZERO (&xfds);
+
+  /* Classify socket handles and create fd sets. */
+  for (i = 0; i < nfd; i++)
+    {
+      int sought = pfd[i].events;
+      pfd[i].revents = 0;
+      if (pfd[i].fd < 0)
+       continue;
+      if (!(sought & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM | POLLWRBAND
+                     | POLLPRI | POLLRDBAND)))
+       continue;
+
+      h = (HANDLE) _get_osfhandle (pfd[i].fd);
+      assert (h != NULL);
+      if (IsSocketHandle (h))
+       {
+         int requested = FD_CLOSE;
+
+         /* see above; socket handles are mapped onto select.  */
+         if (sought & (POLLIN | POLLRDNORM))
+           {
+             requested |= FD_READ | FD_ACCEPT;
+             FD_SET ((SOCKET) h, &rfds);
+           }
+         if (sought & (POLLOUT | POLLWRNORM | POLLWRBAND))
+           {
+             requested |= FD_WRITE | FD_CONNECT;
+             FD_SET ((SOCKET) h, &wfds);
+           }
+         if (sought & (POLLPRI | POLLRDBAND))
+           {
+             requested |= FD_OOB;
+             FD_SET ((SOCKET) h, &xfds);
+           }
+
+         if (requested)
+           WSAEventSelect ((SOCKET) h, hEvent, requested);
+       }
+      else
+       {
+         /* Poll now.  If we get an event, do not poll again.  Also,
+            screen buffer handles are waitable, and they'll block until
+            a character is available.  win32_compute_revents eliminates
+            bits for the "wrong" direction. */
+         pfd[i].revents = win32_compute_revents (h, &sought);
+         if (sought)
+           handle_array[nhandles++] = h;
+         if (pfd[i].revents)
+           timeout = 0;
+       }
+    }
+
+  if (select (0, &rfds, &wfds, &xfds, &tv0) > 0)
+    {
+      /* Do MsgWaitForMultipleObjects anyway to dispatch messages, but
+        no need to call select again.  */
+      poll_again = FALSE;
+      wait_timeout = 0;
+    }
+  else
+    {
+      poll_again = TRUE;
+      if (timeout == INFTIM)
+       wait_timeout = INFINITE;
+      else
+       wait_timeout = timeout;
+    }
+
+  for (;;)
+    {
+      ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE,
+                                      wait_timeout, QS_ALLINPUT);
+
+      if (ret == WAIT_OBJECT_0 + nhandles)
+       {
+         /* new input of some other kind */
+         BOOL bRet;
+         while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0)
+           {
+             TranslateMessage (&msg);
+             DispatchMessage (&msg);
+           }
+       }
+      else
+       break;
+    }
+
+  if (poll_again)
+    select (0, &rfds, &wfds, &xfds, &tv0);
+
+  /* Place a sentinel at the end of the array.  */
+  handle_array[nhandles] = NULL;
+  nhandles = 1;
+  for (i = 0; i < nfd; i++)
+    {
+      int happened;
+
+      if (pfd[i].fd < 0)
+       continue;
+      if (!(pfd[i].events & (POLLIN | POLLRDNORM |
+                            POLLOUT | POLLWRNORM | POLLWRBAND)))
+       continue;
+
+      h = (HANDLE) _get_osfhandle (pfd[i].fd);
+      if (h != handle_array[nhandles])
+       {
+         /* It's a socket.  */
+         WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
+         WSAEventSelect ((SOCKET) h, 0, 0);
+
+         /* If we're lucky, WSAEnumNetworkEvents already provided a way
+            to distinguish FD_READ and FD_ACCEPT; this saves a recv later.  */
+         if (FD_ISSET ((SOCKET) h, &rfds)
+             && !(ev.lNetworkEvents & (FD_READ | FD_ACCEPT)))
+           ev.lNetworkEvents |= FD_READ | FD_ACCEPT;
+         if (FD_ISSET ((SOCKET) h, &wfds))
+           ev.lNetworkEvents |= FD_WRITE | FD_CONNECT;
+         if (FD_ISSET ((SOCKET) h, &xfds))
+           ev.lNetworkEvents |= FD_OOB;
+
+         happened = win32_compute_revents_socket ((SOCKET) h, pfd[i].events,
+                                                  ev.lNetworkEvents);
+       }
+      else
+       {
+         /* Not a socket.  */
+         int sought = pfd[i].events;
+         happened = win32_compute_revents (h, &sought);
+         nhandles++;
+       }
+
+       if ((pfd[i].revents |= happened) != 0)
+       rc++;
+    }
+
+  if (!rc && timeout == INFTIM)
+    {
+      SwitchToThread();
+      goto restart;
+    }
+
+  return rc;
+#endif
+}
diff --git a/compat/win32/poll.h b/compat/win32/poll.h
new file mode 100644 (file)
index 0000000..b7aa59d
--- /dev/null
@@ -0,0 +1,53 @@
+/* Header for poll(2) emulation
+   Contributed by Paolo Bonzini.
+
+   Copyright 2001, 2002, 2003, 2007, 2009, 2010 Free Software Foundation, Inc.
+
+   This file is part of gnulib.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with this program; if not, write to the Free Software Foundation,
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+
+#ifndef _GL_POLL_H
+#define _GL_POLL_H
+
+/* fake a poll(2) environment */
+#define POLLIN      0x0001      /* any readable data available   */
+#define POLLPRI     0x0002      /* OOB/Urgent readable data      */
+#define POLLOUT     0x0004      /* file descriptor is writeable  */
+#define POLLERR     0x0008      /* some poll error occurred      */
+#define POLLHUP     0x0010      /* file descriptor was "hung up" */
+#define POLLNVAL    0x0020      /* requested events "invalid"    */
+#define POLLRDNORM  0x0040
+#define POLLRDBAND  0x0080
+#define POLLWRNORM  0x0100
+#define POLLWRBAND  0x0200
+
+struct pollfd
+{
+  int fd;                       /* which file descriptor to poll */
+  short events;                 /* events we are interested in   */
+  short revents;                /* events found on return        */
+};
+
+typedef unsigned long nfds_t;
+
+extern int poll (struct pollfd *pfd, nfds_t nfd, int timeout);
+
+/* Define INFTIM only if doing so conforms to POSIX.  */
+#if !defined (_POSIX_C_SOURCE) && !defined (_XOPEN_SOURCE)
+#define INFTIM (-1)
+#endif
+
+#endif /* _GL_POLL_H */
diff --git a/compat/win32/sys/poll.c b/compat/win32/sys/poll.c
deleted file mode 100644 (file)
index 708a6c9..0000000
+++ /dev/null
@@ -1,599 +0,0 @@
-/* Emulation for poll(2)
-   Contributed by Paolo Bonzini.
-
-   Copyright 2001-2003, 2006-2010 Free Software Foundation, Inc.
-
-   This file is part of gnulib.
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License along
-   with this program; if not, write to the Free Software Foundation,
-   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
-
-/* Tell gcc not to warn about the (nfd < 0) tests, below.  */
-#if (__GNUC__ == 4 && 3 <= __GNUC_MINOR__) || 4 < __GNUC__
-# pragma GCC diagnostic ignored "-Wtype-limits"
-#endif
-
-#include <malloc.h>
-
-#include <sys/types.h>
-#include "poll.h"
-#include <errno.h>
-#include <limits.h>
-#include <assert.h>
-
-#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
-# define WIN32_NATIVE
-# if defined (_MSC_VER)
-#  define _WIN32_WINNT 0x0502
-# endif
-# include <winsock2.h>
-# include <windows.h>
-# include <io.h>
-# include <stdio.h>
-# include <conio.h>
-#else
-# include <sys/time.h>
-# include <sys/socket.h>
-# include <sys/select.h>
-# include <unistd.h>
-#endif
-
-#ifdef HAVE_SYS_IOCTL_H
-# include <sys/ioctl.h>
-#endif
-#ifdef HAVE_SYS_FILIO_H
-# include <sys/filio.h>
-#endif
-
-#include <time.h>
-
-#ifndef INFTIM
-# define INFTIM (-1)
-#endif
-
-/* BeOS does not have MSG_PEEK.  */
-#ifndef MSG_PEEK
-# define MSG_PEEK 0
-#endif
-
-#ifdef WIN32_NATIVE
-
-#define IsConsoleHandle(h) (((long) (h) & 3) == 3)
-
-static BOOL
-IsSocketHandle (HANDLE h)
-{
-  WSANETWORKEVENTS ev;
-
-  if (IsConsoleHandle (h))
-    return FALSE;
-
-  /* Under Wine, it seems that getsockopt returns 0 for pipes too.
-     WSAEnumNetworkEvents instead distinguishes the two correctly.  */
-  ev.lNetworkEvents = 0xDEADBEEF;
-  WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
-  return ev.lNetworkEvents != 0xDEADBEEF;
-}
-
-/* Declare data structures for ntdll functions.  */
-typedef struct _FILE_PIPE_LOCAL_INFORMATION {
-  ULONG NamedPipeType;
-  ULONG NamedPipeConfiguration;
-  ULONG MaximumInstances;
-  ULONG CurrentInstances;
-  ULONG InboundQuota;
-  ULONG ReadDataAvailable;
-  ULONG OutboundQuota;
-  ULONG WriteQuotaAvailable;
-  ULONG NamedPipeState;
-  ULONG NamedPipeEnd;
-} FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION;
-
-typedef struct _IO_STATUS_BLOCK
-{
-  union {
-    DWORD Status;
-    PVOID Pointer;
-  } u;
-  ULONG_PTR Information;
-} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
-
-typedef enum _FILE_INFORMATION_CLASS {
-  FilePipeLocalInformation = 24
-} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
-
-typedef DWORD (WINAPI *PNtQueryInformationFile)
-        (HANDLE, IO_STATUS_BLOCK *, VOID *, ULONG, FILE_INFORMATION_CLASS);
-
-# ifndef PIPE_BUF
-#  define PIPE_BUF      512
-# endif
-
-/* Compute revents values for file handle H.  If some events cannot happen
-   for the handle, eliminate them from *P_SOUGHT.  */
-
-static int
-win32_compute_revents (HANDLE h, int *p_sought)
-{
-  int i, ret, happened;
-  INPUT_RECORD *irbuffer;
-  DWORD avail, nbuffer;
-  BOOL bRet;
-  IO_STATUS_BLOCK iosb;
-  FILE_PIPE_LOCAL_INFORMATION fpli;
-  static PNtQueryInformationFile NtQueryInformationFile;
-  static BOOL once_only;
-
-  switch (GetFileType (h))
-    {
-    case FILE_TYPE_PIPE:
-      if (!once_only)
-       {
-         NtQueryInformationFile = (PNtQueryInformationFile)
-           GetProcAddress (GetModuleHandle ("ntdll.dll"),
-                           "NtQueryInformationFile");
-         once_only = TRUE;
-       }
-
-      happened = 0;
-      if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0)
-       {
-         if (avail)
-           happened |= *p_sought & (POLLIN | POLLRDNORM);
-       }
-      else if (GetLastError () == ERROR_BROKEN_PIPE)
-       happened |= POLLHUP;
-
-      else
-       {
-         /* It was the write-end of the pipe.  Check if it is writable.
-            If NtQueryInformationFile fails, optimistically assume the pipe is
-            writable.  This could happen on Win9x, where NtQueryInformationFile
-            is not available, or if we inherit a pipe that doesn't permit
-            FILE_READ_ATTRIBUTES access on the write end (I think this should
-            not happen since WinXP SP2; WINE seems fine too).  Otherwise,
-            ensure that enough space is available for atomic writes.  */
-         memset (&iosb, 0, sizeof (iosb));
-         memset (&fpli, 0, sizeof (fpli));
-
-         if (!NtQueryInformationFile
-             || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli),
-                                        FilePipeLocalInformation)
-             || fpli.WriteQuotaAvailable >= PIPE_BUF
-             || (fpli.OutboundQuota < PIPE_BUF &&
-                 fpli.WriteQuotaAvailable == fpli.OutboundQuota))
-           happened |= *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
-       }
-      return happened;
-
-    case FILE_TYPE_CHAR:
-      ret = WaitForSingleObject (h, 0);
-      if (!IsConsoleHandle (h))
-       return ret == WAIT_OBJECT_0 ? *p_sought & ~(POLLPRI | POLLRDBAND) : 0;
-
-      nbuffer = avail = 0;
-      bRet = GetNumberOfConsoleInputEvents (h, &nbuffer);
-      if (bRet)
-       {
-         /* Input buffer.  */
-         *p_sought &= POLLIN | POLLRDNORM;
-         if (nbuffer == 0)
-           return POLLHUP;
-         if (!*p_sought)
-           return 0;
-
-         irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD));
-         bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail);
-         if (!bRet || avail == 0)
-           return POLLHUP;
-
-         for (i = 0; i < avail; i++)
-           if (irbuffer[i].EventType == KEY_EVENT)
-             return *p_sought;
-         return 0;
-       }
-      else
-       {
-         /* Screen buffer.  */
-         *p_sought &= POLLOUT | POLLWRNORM | POLLWRBAND;
-         return *p_sought;
-       }
-
-    default:
-      ret = WaitForSingleObject (h, 0);
-      if (ret == WAIT_OBJECT_0)
-       return *p_sought & ~(POLLPRI | POLLRDBAND);
-
-      return *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
-    }
-}
-
-/* Convert fd_sets returned by select into revents values.  */
-
-static int
-win32_compute_revents_socket (SOCKET h, int sought, long lNetworkEvents)
-{
-  int happened = 0;
-
-  if ((lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE)) == FD_ACCEPT)
-    happened |= (POLLIN | POLLRDNORM) & sought;
-
-  else if (lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE))
-    {
-      int r, error;
-
-      char data[64];
-      WSASetLastError (0);
-      r = recv (h, data, sizeof (data), MSG_PEEK);
-      error = WSAGetLastError ();
-      WSASetLastError (0);
-
-      if (r > 0 || error == WSAENOTCONN)
-       happened |= (POLLIN | POLLRDNORM) & sought;
-
-      /* Distinguish hung-up sockets from other errors.  */
-      else if (r == 0 || error == WSAESHUTDOWN || error == WSAECONNRESET
-              || error == WSAECONNABORTED || error == WSAENETRESET)
-       happened |= POLLHUP;
-
-      else
-       happened |= POLLERR;
-    }
-
-  if (lNetworkEvents & (FD_WRITE | FD_CONNECT))
-    happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
-
-  if (lNetworkEvents & FD_OOB)
-    happened |= (POLLPRI | POLLRDBAND) & sought;
-
-  return happened;
-}
-
-#else /* !MinGW */
-
-/* Convert select(2) returned fd_sets into poll(2) revents values.  */
-static int
-compute_revents (int fd, int sought, fd_set *rfds, fd_set *wfds, fd_set *efds)
-{
-  int happened = 0;
-  if (FD_ISSET (fd, rfds))
-    {
-      int r;
-      int socket_errno;
-
-# if defined __MACH__ && defined __APPLE__
-      /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK
-        for some kinds of descriptors.  Detect if this descriptor is a
-        connected socket, a server socket, or something else using a
-        0-byte recv, and use ioctl(2) to detect POLLHUP.  */
-      r = recv (fd, NULL, 0, MSG_PEEK);
-      socket_errno = (r < 0) ? errno : 0;
-      if (r == 0 || socket_errno == ENOTSOCK)
-       ioctl (fd, FIONREAD, &r);
-# else
-      char data[64];
-      r = recv (fd, data, sizeof (data), MSG_PEEK);
-      socket_errno = (r < 0) ? errno : 0;
-# endif
-      if (r == 0)
-       happened |= POLLHUP;
-
-      /* If the event happened on an unconnected server socket,
-        that's fine. */
-      else if (r > 0 || ( /* (r == -1) && */ socket_errno == ENOTCONN))
-       happened |= (POLLIN | POLLRDNORM) & sought;
-
-      /* Distinguish hung-up sockets from other errors.  */
-      else if (socket_errno == ESHUTDOWN || socket_errno == ECONNRESET
-              || socket_errno == ECONNABORTED || socket_errno == ENETRESET)
-       happened |= POLLHUP;
-
-      else
-       happened |= POLLERR;
-    }
-
-  if (FD_ISSET (fd, wfds))
-    happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
-
-  if (FD_ISSET (fd, efds))
-    happened |= (POLLPRI | POLLRDBAND) & sought;
-
-  return happened;
-}
-#endif /* !MinGW */
-
-int
-poll (pfd, nfd, timeout)
-     struct pollfd *pfd;
-     nfds_t nfd;
-     int timeout;
-{
-#ifndef WIN32_NATIVE
-  fd_set rfds, wfds, efds;
-  struct timeval tv;
-  struct timeval *ptv;
-  int maxfd, rc;
-  nfds_t i;
-
-# ifdef _SC_OPEN_MAX
-  static int sc_open_max = -1;
-
-  if (nfd < 0
-      || (nfd > sc_open_max
-         && (sc_open_max != -1
-             || nfd > (sc_open_max = sysconf (_SC_OPEN_MAX)))))
-    {
-      errno = EINVAL;
-      return -1;
-    }
-# else /* !_SC_OPEN_MAX */
-#  ifdef OPEN_MAX
-  if (nfd < 0 || nfd > OPEN_MAX)
-    {
-      errno = EINVAL;
-      return -1;
-    }
-#  endif /* OPEN_MAX -- else, no check is needed */
-# endif /* !_SC_OPEN_MAX */
-
-  /* EFAULT is not necessary to implement, but let's do it in the
-     simplest case. */
-  if (!pfd)
-    {
-      errno = EFAULT;
-      return -1;
-    }
-
-  /* convert timeout number into a timeval structure */
-  if (timeout == 0)
-    {
-      ptv = &tv;
-      ptv->tv_sec = 0;
-      ptv->tv_usec = 0;
-    }
-  else if (timeout > 0)
-    {
-      ptv = &tv;
-      ptv->tv_sec = timeout / 1000;
-      ptv->tv_usec = (timeout % 1000) * 1000;
-    }
-  else if (timeout == INFTIM)
-    /* wait forever */
-    ptv = NULL;
-  else
-    {
-      errno = EINVAL;
-      return -1;
-    }
-
-  /* create fd sets and determine max fd */
-  maxfd = -1;
-  FD_ZERO (&rfds);
-  FD_ZERO (&wfds);
-  FD_ZERO (&efds);
-  for (i = 0; i < nfd; i++)
-    {
-      if (pfd[i].fd < 0)
-       continue;
-
-      if (pfd[i].events & (POLLIN | POLLRDNORM))
-       FD_SET (pfd[i].fd, &rfds);
-
-      /* see select(2): "the only exceptional condition detectable
-        is out-of-band data received on a socket", hence we push
-        POLLWRBAND events onto wfds instead of efds. */
-      if (pfd[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND))
-       FD_SET (pfd[i].fd, &wfds);
-      if (pfd[i].events & (POLLPRI | POLLRDBAND))
-       FD_SET (pfd[i].fd, &efds);
-      if (pfd[i].fd >= maxfd
-         && (pfd[i].events & (POLLIN | POLLOUT | POLLPRI
-                              | POLLRDNORM | POLLRDBAND
-                              | POLLWRNORM | POLLWRBAND)))
-       {
-         maxfd = pfd[i].fd;
-         if (maxfd > FD_SETSIZE)
-           {
-             errno = EOVERFLOW;
-             return -1;
-           }
-       }
-    }
-
-  /* examine fd sets */
-  rc = select (maxfd + 1, &rfds, &wfds, &efds, ptv);
-  if (rc < 0)
-    return rc;
-
-  /* establish results */
-  rc = 0;
-  for (i = 0; i < nfd; i++)
-    if (pfd[i].fd < 0)
-      pfd[i].revents = 0;
-    else
-      {
-       int happened = compute_revents (pfd[i].fd, pfd[i].events,
-                                       &rfds, &wfds, &efds);
-       if (happened)
-         {
-           pfd[i].revents = happened;
-           rc++;
-         }
-      }
-
-  return rc;
-#else
-  static struct timeval tv0;
-  static HANDLE hEvent;
-  WSANETWORKEVENTS ev;
-  HANDLE h, handle_array[FD_SETSIZE + 2];
-  DWORD ret, wait_timeout, nhandles;
-  fd_set rfds, wfds, xfds;
-  BOOL poll_again;
-  MSG msg;
-  int rc = 0;
-  nfds_t i;
-
-  if (nfd < 0 || timeout < -1)
-    {
-      errno = EINVAL;
-      return -1;
-    }
-
-  if (!hEvent)
-    hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
-
-  handle_array[0] = hEvent;
-  nhandles = 1;
-  FD_ZERO (&rfds);
-  FD_ZERO (&wfds);
-  FD_ZERO (&xfds);
-
-  /* Classify socket handles and create fd sets. */
-  for (i = 0; i < nfd; i++)
-    {
-      int sought = pfd[i].events;
-      pfd[i].revents = 0;
-      if (pfd[i].fd < 0)
-       continue;
-      if (!(sought & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM | POLLWRBAND
-                     | POLLPRI | POLLRDBAND)))
-       continue;
-
-      h = (HANDLE) _get_osfhandle (pfd[i].fd);
-      assert (h != NULL);
-      if (IsSocketHandle (h))
-       {
-         int requested = FD_CLOSE;
-
-         /* see above; socket handles are mapped onto select.  */
-         if (sought & (POLLIN | POLLRDNORM))
-           {
-             requested |= FD_READ | FD_ACCEPT;
-             FD_SET ((SOCKET) h, &rfds);
-           }
-         if (sought & (POLLOUT | POLLWRNORM | POLLWRBAND))
-           {
-             requested |= FD_WRITE | FD_CONNECT;
-             FD_SET ((SOCKET) h, &wfds);
-           }
-         if (sought & (POLLPRI | POLLRDBAND))
-           {
-             requested |= FD_OOB;
-             FD_SET ((SOCKET) h, &xfds);
-           }
-
-         if (requested)
-           WSAEventSelect ((SOCKET) h, hEvent, requested);
-       }
-      else
-       {
-         /* Poll now.  If we get an event, do not poll again.  Also,
-            screen buffer handles are waitable, and they'll block until
-            a character is available.  win32_compute_revents eliminates
-            bits for the "wrong" direction. */
-         pfd[i].revents = win32_compute_revents (h, &sought);
-         if (sought)
-           handle_array[nhandles++] = h;
-         if (pfd[i].revents)
-           timeout = 0;
-       }
-    }
-
-  if (select (0, &rfds, &wfds, &xfds, &tv0) > 0)
-    {
-      /* Do MsgWaitForMultipleObjects anyway to dispatch messages, but
-        no need to call select again.  */
-      poll_again = FALSE;
-      wait_timeout = 0;
-    }
-  else
-    {
-      poll_again = TRUE;
-      if (timeout == INFTIM)
-       wait_timeout = INFINITE;
-      else
-       wait_timeout = timeout;
-    }
-
-  for (;;)
-    {
-      ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE,
-                                      wait_timeout, QS_ALLINPUT);
-
-      if (ret == WAIT_OBJECT_0 + nhandles)
-       {
-         /* new input of some other kind */
-         BOOL bRet;
-         while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0)
-           {
-             TranslateMessage (&msg);
-             DispatchMessage (&msg);
-           }
-       }
-      else
-       break;
-    }
-
-  if (poll_again)
-    select (0, &rfds, &wfds, &xfds, &tv0);
-
-  /* Place a sentinel at the end of the array.  */
-  handle_array[nhandles] = NULL;
-  nhandles = 1;
-  for (i = 0; i < nfd; i++)
-    {
-      int happened;
-
-      if (pfd[i].fd < 0)
-       continue;
-      if (!(pfd[i].events & (POLLIN | POLLRDNORM |
-                            POLLOUT | POLLWRNORM | POLLWRBAND)))
-       continue;
-
-      h = (HANDLE) _get_osfhandle (pfd[i].fd);
-      if (h != handle_array[nhandles])
-       {
-         /* It's a socket.  */
-         WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
-         WSAEventSelect ((SOCKET) h, 0, 0);
-
-         /* If we're lucky, WSAEnumNetworkEvents already provided a way
-            to distinguish FD_READ and FD_ACCEPT; this saves a recv later.  */
-         if (FD_ISSET ((SOCKET) h, &rfds)
-             && !(ev.lNetworkEvents & (FD_READ | FD_ACCEPT)))
-           ev.lNetworkEvents |= FD_READ | FD_ACCEPT;
-         if (FD_ISSET ((SOCKET) h, &wfds))
-           ev.lNetworkEvents |= FD_WRITE | FD_CONNECT;
-         if (FD_ISSET ((SOCKET) h, &xfds))
-           ev.lNetworkEvents |= FD_OOB;
-
-         happened = win32_compute_revents_socket ((SOCKET) h, pfd[i].events,
-                                                  ev.lNetworkEvents);
-       }
-      else
-       {
-         /* Not a socket.  */
-         int sought = pfd[i].events;
-         happened = win32_compute_revents (h, &sought);
-         nhandles++;
-       }
-
-       if ((pfd[i].revents |= happened) != 0)
-       rc++;
-    }
-
-  return rc;
-#endif
-}
diff --git a/compat/win32/sys/poll.h b/compat/win32/sys/poll.h
deleted file mode 100644 (file)
index b7aa59d..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/* Header for poll(2) emulation
-   Contributed by Paolo Bonzini.
-
-   Copyright 2001, 2002, 2003, 2007, 2009, 2010 Free Software Foundation, Inc.
-
-   This file is part of gnulib.
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License along
-   with this program; if not, write to the Free Software Foundation,
-   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
-
-#ifndef _GL_POLL_H
-#define _GL_POLL_H
-
-/* fake a poll(2) environment */
-#define POLLIN      0x0001      /* any readable data available   */
-#define POLLPRI     0x0002      /* OOB/Urgent readable data      */
-#define POLLOUT     0x0004      /* file descriptor is writeable  */
-#define POLLERR     0x0008      /* some poll error occurred      */
-#define POLLHUP     0x0010      /* file descriptor was "hung up" */
-#define POLLNVAL    0x0020      /* requested events "invalid"    */
-#define POLLRDNORM  0x0040
-#define POLLRDBAND  0x0080
-#define POLLWRNORM  0x0100
-#define POLLWRBAND  0x0200
-
-struct pollfd
-{
-  int fd;                       /* which file descriptor to poll */
-  short events;                 /* events we are interested in   */
-  short revents;                /* events found on return        */
-};
-
-typedef unsigned long nfds_t;
-
-extern int poll (struct pollfd *pfd, nfds_t nfd, int timeout);
-
-/* Define INFTIM only if doing so conforms to POSIX.  */
-#if !defined (_POSIX_C_SOURCE) && !defined (_XOPEN_SOURCE)
-#define INFTIM (-1)
-#endif
-
-#endif /* _GL_POLL_H */
index 42b95a9b5169125fac7bde3caabb15f4cb6a351d..d015e436d57472bf9560a6f604705f52938c9f19 100644 (file)
@@ -1,5 +1,4 @@
 #include "../../git-compat-util.h"
-#include "../../strbuf.h"
 
 static HANDLE ms_eventlog;
 
@@ -16,13 +15,8 @@ void openlog(const char *ident, int logopt, int facility)
 
 void syslog(int priority, const char *fmt, ...)
 {
-       struct strbuf sb = STRBUF_INIT;
-       struct strbuf_expand_dict_entry dict[] = {
-               {"1", "% 1"},
-               {NULL, NULL}
-       };
        WORD logtype;
-       char *str;
+       char *str, *pos;
        int str_len;
        va_list ap;
 
@@ -39,11 +33,24 @@ void syslog(int priority, const char *fmt, ...)
        }
 
        str = malloc(str_len + 1);
+       if (!str) {
+               warning("malloc failed: '%s'", strerror(errno));
+               return;
+       }
+
        va_start(ap, fmt);
        vsnprintf(str, str_len + 1, fmt, ap);
        va_end(ap);
-       strbuf_expand(&sb, str, strbuf_expand_dict_cb, &dict);
-       free(str);
+
+       while ((pos = strstr(str, "%1")) != NULL) {
+               str = realloc(str, ++str_len + 1);
+               if (!str) {
+                       warning("realloc failed: '%s'", strerror(errno));
+                       return;
+               }
+               memmove(pos + 2, pos + 1, strlen(pos));
+               pos[1] = ' ';
+       }
 
        switch (priority) {
        case LOG_EMERG:
@@ -66,7 +73,6 @@ void syslog(int priority, const char *fmt, ...)
        }
 
        ReportEventA(ms_eventlog, logtype, 0, 0, NULL, 1, 0,
-           (const char **)&sb.buf, NULL);
-
-       strbuf_release(&sb);
+           (const char **)&str, NULL);
+       free(str);
 }
index fc27913c3bb3646a6fd6592356be6a979e9cc7d2..818ba6df00e342f55fb7151b05299848240e61bd 100644 (file)
--- a/config.c
+++ b/config.c
@@ -338,7 +338,7 @@ static int git_parse_file(config_fn_t fn, void *data)
        die("bad config file line %d in %s", cf->linenr, cf->name);
 }
 
-static int parse_unit_factor(const char *end, unsigned long *val)
+static int parse_unit_factor(const char *end, uintmax_t *val)
 {
        if (!*end)
                return 1;
@@ -361,11 +361,23 @@ static int git_parse_long(const char *value, long *ret)
 {
        if (value && *value) {
                char *end;
-               long val = strtol(value, &end, 0);
-               unsigned long factor = 1;
+               intmax_t val;
+               uintmax_t uval;
+               uintmax_t factor = 1;
+
+               errno = 0;
+               val = strtoimax(value, &end, 0);
+               if (errno == ERANGE)
+                       return 0;
                if (!parse_unit_factor(end, &factor))
                        return 0;
-               *ret = val * factor;
+               uval = abs(val);
+               uval *= factor;
+               if ((uval > maximum_signed_value_of_type(long)) ||
+                   (abs(val) > uval))
+                       return 0;
+               val *= factor;
+               *ret = val;
                return 1;
        }
        return 0;
@@ -375,9 +387,19 @@ int git_parse_ulong(const char *value, unsigned long *ret)
 {
        if (value && *value) {
                char *end;
-               unsigned long val = strtoul(value, &end, 0);
+               uintmax_t val;
+               uintmax_t oldval;
+
+               errno = 0;
+               val = strtoumax(value, &end, 0);
+               if (errno == ERANGE)
+                       return 0;
+               oldval = val;
                if (!parse_unit_factor(end, &val))
                        return 0;
+               if ((val > maximum_unsigned_value_of_type(long)) ||
+                   (oldval > val))
+                       return 0;
                *ret = val;
                return 1;
        }
@@ -558,7 +580,7 @@ static int git_default_core_config(const char *var, const char *value)
 
        if (!strcmp(var, "core.packedgitwindowsize")) {
                int pgsz_x2 = getpagesize() * 2;
-               packed_git_window_size = git_config_int(var, value);
+               packed_git_window_size = git_config_ulong(var, value);
 
                /* This value must be multiple of (pagesize * 2) */
                packed_git_window_size /= pgsz_x2;
@@ -569,18 +591,17 @@ static int git_default_core_config(const char *var, const char *value)
        }
 
        if (!strcmp(var, "core.bigfilethreshold")) {
-               long n = git_config_int(var, value);
-               big_file_threshold = 0 < n ? n : 0;
+               big_file_threshold = git_config_ulong(var, value);
                return 0;
        }
 
        if (!strcmp(var, "core.packedgitlimit")) {
-               packed_git_limit = git_config_int(var, value);
+               packed_git_limit = git_config_ulong(var, value);
                return 0;
        }
 
        if (!strcmp(var, "core.deltabasecachelimit")) {
-               delta_base_cache_limit = git_config_int(var, value);
+               delta_base_cache_limit = git_config_ulong(var, value);
                return 0;
        }
 
@@ -802,6 +823,10 @@ int git_default_config(const char *var, const char *value, void *dummy)
                return 0;
        }
 
+       if (!strcmp(var, "pack.packsizelimit")) {
+               pack_size_limit_cfg = git_config_ulong(var, value);
+               return 0;
+       }
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
 }
@@ -870,12 +895,12 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
 
        home = getenv("HOME");
        if (home) {
-               char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
+               char buf[PATH_MAX];
+               char *user_config = mksnpath(buf, sizeof(buf), "%s/.gitconfig", home);
                if (!access(user_config, R_OK)) {
                        ret += git_config_from_file(fn, user_config, data);
                        found += 1;
                }
-               free(user_config);
        }
 
        if (repo_config && !access(repo_config, R_OK)) {
@@ -1103,6 +1128,12 @@ contline:
        return offset;
 }
 
+int git_config_set_in_file(const char *config_filename,
+                       const char *key, const char *value)
+{
+       return git_config_set_multivar_in_file(config_filename, key, value, NULL, 0);
+}
+
 int git_config_set(const char *key, const char *value)
 {
        return git_config_set_multivar(key, value, NULL, 0);
@@ -1200,19 +1231,14 @@ out_free_ret_1:
  * - the config file is removed and the lock file rename()d to it.
  *
  */
-int git_config_set_multivar(const char *key, const char *value,
-       const char *value_regex, int multi_replace)
+int git_config_set_multivar_in_file(const char *config_filename,
+                               const char *key, const char *value,
+                               const char *value_regex, int multi_replace)
 {
        int fd = -1, in_fd;
        int ret;
-       char *config_filename;
        struct lock_file *lock = NULL;
 
-       if (config_exclusive_filename)
-               config_filename = xstrdup(config_exclusive_filename);
-       else
-               config_filename = git_pathdup("config");
-
        /* parse-key returns negative; flip the sign to feed exit(3) */
        ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
        if (ret)
@@ -1389,7 +1415,6 @@ int git_config_set_multivar(const char *key, const char *value,
 out_free:
        if (lock)
                rollback_lock_file(lock);
-       free(config_filename);
        return ret;
 
 write_err_out:
@@ -1398,6 +1423,24 @@ write_err_out:
 
 }
 
+int git_config_set_multivar(const char *key, const char *value,
+                       const char *value_regex, int multi_replace)
+{
+       const char *config_filename;
+       char *buf = NULL;
+       int ret;
+
+       if (config_exclusive_filename)
+               config_filename = config_exclusive_filename;
+       else
+               config_filename = buf = git_pathdup("config");
+
+       ret = git_config_set_multivar_in_file(config_filename, key, value,
+                                       value_regex, multi_replace);
+       free(buf);
+       return ret;
+}
+
 static int section_name_match (const char *buf, const char *name)
 {
        int i = 0, j = 0, dot = 0;
index ab371012a22f39bf31f512e276e12f55b6d1b8b7..b2ba7104ebc87c0f2fe261f16461793fcc8b8cd3 100644 (file)
@@ -35,6 +35,9 @@ NO_CURL=@NO_CURL@
 NO_EXPAT=@NO_EXPAT@
 NO_LIBGEN_H=@NO_LIBGEN_H@
 HAVE_PATHS_H=@HAVE_PATHS_H@
+HAVE_LIBCHARSET_H=@HAVE_LIBCHARSET_H@
+NO_GETTEXT=@NO_GETTEXT@
+LIBC_CONTAINS_LIBINTL=@LIBC_CONTAINS_LIBINTL@
 NEEDS_LIBICONV=@NEEDS_LIBICONV@
 NEEDS_SOCKET=@NEEDS_SOCKET@
 NEEDS_RESOLV=@NEEDS_RESOLV@
@@ -71,3 +74,4 @@ SNPRINTF_RETURNS_BOGUS=@SNPRINTF_RETURNS_BOGUS@
 NO_PTHREADS=@NO_PTHREADS@
 PTHREAD_CFLAGS=@PTHREAD_CFLAGS@
 PTHREAD_LIBS=@PTHREAD_LIBS@
+CHARSET_LIB=@CHARSET_LIB@
index 048a1d4972769184ff857fb7681bc67b2ebdfb99..8bb0f44b489e0e963c70e88d04e5878dd78012d5 100644 (file)
@@ -636,6 +636,23 @@ AC_CHECK_LIB([c], [basename],
 AC_SUBST(NEEDS_LIBGEN)
 test -n "$NEEDS_LIBGEN" && LIBS="$LIBS -lgen"
 
+AC_CHECK_LIB([c], [gettext],
+[LIBC_CONTAINS_LIBINTL=YesPlease],
+[LIBC_CONTAINS_LIBINTL=])
+AC_SUBST(LIBC_CONTAINS_LIBINTL)
+
+#
+# Define NO_GETTEXT if you don't want Git output to be translated.
+# A translated Git requires GNU libintl or another gettext implementation
+AC_CHECK_HEADER([libintl.h],
+[NO_GETTEXT=],
+[NO_GETTEXT=YesPlease])
+AC_SUBST(NO_GETTEXT)
+
+if test -z "$NO_GETTEXT"; then
+    test -n "$LIBC_CONTAINS_LIBINTL" || LIBS="$LIBS -lintl"
+fi
+
 ## Checks for header files.
 AC_MSG_NOTICE([CHECKS for header files])
 #
@@ -818,6 +835,22 @@ AC_CHECK_HEADER([paths.h],
 [HAVE_PATHS_H=])
 AC_SUBST(HAVE_PATHS_H)
 #
+# Define HAVE_LIBCHARSET_H if have libcharset.h
+AC_CHECK_HEADER([libcharset.h],
+[HAVE_LIBCHARSET_H=YesPlease],
+[HAVE_LIBCHARSET_H=])
+AC_SUBST(HAVE_LIBCHARSET_H)
+# Define CHARSET_LIB if libiconv does not export the locale_charset symbol
+# and libcharset does
+CHARSET_LIB=
+AC_CHECK_LIB([iconv], [locale_charset],
+       [],
+       [AC_CHECK_LIB([charset], [locale_charset],
+                     [CHARSET_LIB=-lcharset])
+       ]
+)
+AC_SUBST(CHARSET_LIB)
+#
 # Define NO_STRCASESTR if you don't have strcasestr.
 GIT_CHECK_FUNC(strcasestr,
 [NO_STRCASESTR=],
index ee1d4b4b46f789168c155eb170b87dbed85fb590..912cddeea8c4f09ec523ce19e677358c84eda9b7 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -22,7 +22,7 @@ static int check_ref(const char *name, int len, unsigned int flags)
        len -= 5;
 
        /* REF_NORMAL means that we don't want the magic fake tag refs */
-       if ((flags & REF_NORMAL) && check_ref_format(name) < 0)
+       if ((flags & REF_NORMAL) && check_refname_format(name, 0))
                return 0;
 
        /* REF_HEADS means that we want regular branch heads */
@@ -53,7 +53,6 @@ static void add_extra_have(struct extra_have_objects *extra, unsigned char *sha1
  * Read all the refs from the other end
  */
 struct ref **get_remote_heads(int in, struct ref **list,
-                             int nr_match, char **match,
                              unsigned int flags,
                              struct extra_have_objects *extra_have)
 {
@@ -92,8 +91,6 @@ struct ref **get_remote_heads(int in, struct ref **list,
 
                if (!check_ref(name, name_len, flags))
                        continue;
-               if (nr_match && !path_match(name, nr_match, match))
-                       continue;
                ref = alloc_ref(buffer + 41);
                hashcpy(ref->old_sha1, old_sha1);
                *list = ref;
@@ -104,29 +101,27 @@ struct ref **get_remote_heads(int in, struct ref **list,
 
 int server_supports(const char *feature)
 {
-       return server_capabilities &&
-               strstr(server_capabilities, feature) != NULL;
+       return !!parse_feature_request(server_capabilities, feature);
 }
 
-int path_match(const char *path, int nr, char **match)
+const char *parse_feature_request(const char *feature_list, const char *feature)
 {
-       int i;
-       int pathlen = strlen(path);
-
-       for (i = 0; i < nr; i++) {
-               char *s = match[i];
-               int len = strlen(s);
-
-               if (!len || len > pathlen)
-                       continue;
-               if (memcmp(path + pathlen - len, s, len))
-                       continue;
-               if (pathlen > len && path[pathlen - len - 1] != '/')
-                       continue;
-               *s = 0;
-               return (i + 1);
+       int len;
+
+       if (!feature_list)
+               return NULL;
+
+       len = strlen(feature);
+       while (*feature_list) {
+               const char *found = strstr(feature_list, feature);
+               if (!found)
+                       return NULL;
+               if ((feature_list == found || isspace(found[-1])) &&
+                   (!found[len] || isspace(found[len]) || found[len] == '='))
+                       return found;
+               feature_list = found + 1;
        }
-       return 0;
+       return NULL;
 }
 
 enum protocol {
@@ -175,6 +170,15 @@ static void get_host_and_port(char **host, const char **port)
        }
 }
 
+static void enable_keepalive(int sockfd)
+{
+       int ka = 1;
+
+       if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka)) < 0)
+               fprintf(stderr, "unable to set SO_KEEPALIVE on socket: %s\n",
+                       strerror(errno));
+}
+
 #ifndef NO_IPV6
 
 static const char *ai_name(const struct addrinfo *ai)
@@ -239,6 +243,8 @@ static int git_tcp_connect_sock(char *host, int flags)
        if (sockfd < 0)
                die("unable to connect to %s:\n%s", host, error_message.buf);
 
+       enable_keepalive(sockfd);
+
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "done.\n");
 
@@ -312,6 +318,8 @@ static int git_tcp_connect_sock(char *host, int flags)
        if (sockfd < 0)
                die("unable to connect to %s:\n%s", host, error_message.buf);
 
+       enable_keepalive(sockfd);
+
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "done.\n");
 
@@ -619,47 +627,3 @@ int finish_connect(struct child_process *conn)
        free(conn);
        return code;
 }
-
-char *git_getpass(const char *prompt)
-{
-       const char *askpass;
-       struct child_process pass;
-       const char *args[3];
-       static struct strbuf buffer = STRBUF_INIT;
-
-       askpass = getenv("GIT_ASKPASS");
-       if (!askpass)
-               askpass = askpass_program;
-       if (!askpass)
-               askpass = getenv("SSH_ASKPASS");
-       if (!askpass || !(*askpass)) {
-               char *result = getpass(prompt);
-               if (!result)
-                       die_errno("Could not read password");
-               return result;
-       }
-
-       args[0] = askpass;
-       args[1] = prompt;
-       args[2] = NULL;
-
-       memset(&pass, 0, sizeof(pass));
-       pass.argv = args;
-       pass.out = -1;
-
-       if (start_command(&pass))
-               exit(1);
-
-       strbuf_reset(&buffer);
-       if (strbuf_read(&buffer, pass.out, 20) < 0)
-               die("failed to read password from %s\n", askpass);
-
-       close(pass.out);
-
-       if (finish_command(&pass))
-               exit(1);
-
-       strbuf_setlen(&buffer, strcspn(buffer.buf, "\r\n"));
-
-       return buffer.buf;
-}
diff --git a/connected.c b/connected.c
new file mode 100644 (file)
index 0000000..d762423
--- /dev/null
@@ -0,0 +1,62 @@
+#include "cache.h"
+#include "run-command.h"
+#include "sigchain.h"
+#include "connected.h"
+
+/*
+ * If we feed all the commits we want to verify to this command
+ *
+ *  $ git rev-list --verify-objects --stdin --not --all
+ *
+ * and if it does not error out, that means everything reachable from
+ * these commits locally exists and is connected to some of our
+ * existing refs.
+ *
+ * Returns 0 if everything is connected, non-zero otherwise.
+ */
+int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data)
+{
+       struct child_process rev_list;
+       const char *argv[] = {"rev-list", "--verify-objects",
+                             "--stdin", "--not", "--all", NULL, NULL};
+       char commit[41];
+       unsigned char sha1[20];
+       int err = 0;
+
+       if (fn(cb_data, sha1))
+               return err;
+
+       if (quiet)
+               argv[5] = "--quiet";
+
+       memset(&rev_list, 0, sizeof(rev_list));
+       rev_list.argv = argv;
+       rev_list.git_cmd = 1;
+       rev_list.in = -1;
+       rev_list.no_stdout = 1;
+       rev_list.no_stderr = quiet;
+       if (start_command(&rev_list))
+               return error(_("Could not run 'git rev-list'"));
+
+       sigchain_push(SIGPIPE, SIG_IGN);
+
+       commit[40] = '\n';
+       do {
+               memcpy(commit, sha1_to_hex(sha1), 40);
+               if (write_in_full(rev_list.in, commit, 41) < 0) {
+                       if (errno != EPIPE && errno != EINVAL)
+                               error(_("failed write to rev-list: %s"),
+                                     strerror(errno));
+                       err = -1;
+                       break;
+               }
+       } while (!fn(cb_data, sha1));
+
+       if (close(rev_list.in)) {
+               error(_("failed to close rev-list's stdin: %s"), strerror(errno));
+               err = -1;
+       }
+
+       sigchain_pop(SIGPIPE);
+       return finish_command(&rev_list) || err;
+}
diff --git a/connected.h b/connected.h
new file mode 100644 (file)
index 0000000..7e4585a
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef CONNECTED_H
+#define CONNECTED_H
+
+/*
+ * Take callback data, and return next object name in the buffer.
+ * When called after returning the name for the last object, return -1
+ * to signal EOF, otherwise return 0.
+ */
+typedef int (*sha1_iterate_fn)(void *, unsigned char [20]);
+
+/*
+ * Make sure that our object store has all the commits necessary to
+ * connect the ancestry chain to some of our existing refs, and all
+ * the trees and blobs that these commits use.
+ *
+ * Return 0 if Ok, non zero otherwise (i.e. some missing objects)
+ */
+extern int check_everything_connected(sha1_iterate_fn, int quiet, void *cb_data);
+
+#endif /* CONNECTED_H */
index 8648a36e7b8239e969dc8971f451431c298e405e..0acbdda3b8af423fc4911af4d337658a4841c899 100755 (executable)
 #       per-repository basis by setting the bash.showUpstream config
 #       variable.
 #
-#
-# To submit patches:
-#
-#    *) Read Documentation/SubmittingPatches
-#    *) Send all patches to the current maintainer:
-#
-#       "Shawn O. Pearce" <spearce@spearce.org>
-#
-#    *) Always CC the Git mailing list:
-#
-#       git@vger.kernel.org
-#
 
 if [[ -n ${ZSH_VERSION-} ]]; then
        autoload -U +X bashcompinit && bashcompinit
@@ -110,7 +98,8 @@ __git_ps1_show_upstream ()
        local upstream=git legacy="" verbose=""
 
        # get some config options from git-config
-       while read key value; do
+       local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
+       while read -r key value; do
                case "$key" in
                bash.showupstream)
                        GIT_PS1_SHOWUPSTREAM="$value"
@@ -125,7 +114,7 @@ __git_ps1_show_upstream ()
                        upstream=svn+git # default upstream is SVN if available, else git
                        ;;
                esac
-       done < <(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')
+       done <<< "$output"
 
        # parse configuration values
        for option in ${GIT_PS1_SHOWUPSTREAM}; do
@@ -297,13 +286,13 @@ __git_ps1 ()
                                fi
                        fi
                        if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
-                               git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
+                               git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
                        fi
 
                        if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
-                          if [ -n "$(git ls-files --others --exclude-standard)" ]; then
-                             u="%"
-                          fi
+                               if [ -n "$(git ls-files --others --exclude-standard)" ]; then
+                                       u="%"
+                               fi
                        fi
 
                        if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
@@ -312,7 +301,7 @@ __git_ps1 ()
                fi
 
                local f="$w$i$s$u"
-               printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
+               printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
        fi
 }
 
@@ -485,15 +474,17 @@ _get_comp_words_by_ref ()
 fi
 fi
 
-# __gitcomp accepts 1, 2, 3, or 4 arguments
-# generates completion reply with compgen
+# Generates completion reply with compgen, appending a space to possible
+# completion words, if necessary.
+# It accepts 1 to 4 arguments:
+# 1: List of possible completion words.
+# 2: A prefix to be added to each possible completion word (optional).
+# 3: Generate possible completion matches for this word (optional).
+# 4: A suffix to be appended to each possible completion word (optional).
 __gitcomp ()
 {
-       local cur_="$cur"
+       local cur_="${3-$cur}"
 
-       if [ $# -gt 2 ]; then
-               cur_="$3"
-       fi
        case "$cur_" in
        --*=)
                COMPREPLY=()
@@ -507,42 +498,39 @@ __gitcomp ()
        esac
 }
 
-# __git_heads accepts 0 or 1 arguments (to pass to __gitdir)
+# Generates completion reply with compgen from newline-separated possible
+# completion words by appending a space to all of them.
+# It accepts 1 to 4 arguments:
+# 1: List of possible completion words, separated by a single newline.
+# 2: A prefix to be added to each possible completion word (optional).
+# 3: Generate possible completion matches for this word (optional).
+# 4: A suffix to be appended to each possible completion word instead of
+#    the default space (optional).  If specified but empty, nothing is
+#    appended.
+__gitcomp_nl ()
+{
+       local IFS=$'\n'
+       COMPREPLY=($(compgen -P "${2-}" -S "${4- }" -W "$1" -- "${3-$cur}"))
+}
+
 __git_heads ()
 {
-       local cmd i is_hash=y dir="$(__gitdir "${1-}")"
+       local dir="$(__gitdir)"
        if [ -d "$dir" ]; then
                git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
                        refs/heads
                return
        fi
-       for i in $(git ls-remote "${1-}" 2>/dev/null); do
-               case "$is_hash,$i" in
-               y,*) is_hash=n ;;
-               n,*^{}) is_hash=y ;;
-               n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
-               n,*) is_hash=y; echo "$i" ;;
-               esac
-       done
 }
 
-# __git_tags accepts 0 or 1 arguments (to pass to __gitdir)
 __git_tags ()
 {
-       local cmd i is_hash=y dir="$(__gitdir "${1-}")"
+       local dir="$(__gitdir)"
        if [ -d "$dir" ]; then
                git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
                        refs/tags
                return
        fi
-       for i in $(git ls-remote "${1-}" 2>/dev/null); do
-               case "$is_hash,$i" in
-               y,*) is_hash=n ;;
-               n,*^{}) is_hash=y ;;
-               n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;;
-               n,*) is_hash=y; echo "$i" ;;
-               esac
-       done
 }
 
 # __git_refs accepts 0, 1 (to pass to __gitdir), or 2 arguments
@@ -550,7 +538,7 @@ __git_tags ()
 # by checkout for tracking branches
 __git_refs ()
 {
-       local i is_hash=y dir="$(__gitdir "${1-}")" track="${2-}"
+       local i hash dir="$(__gitdir "${1-}")" track="${2-}"
        local format refs
        if [ -d "$dir" ]; then
                case "$cur" in
@@ -576,7 +564,7 @@ __git_refs ()
                        local ref entry
                        git --git-dir="$dir" for-each-ref --shell --format="ref=%(refname:short)" \
                                "refs/remotes/" | \
-                       while read entry; do
+                       while read -r entry; do
                                eval "$entry"
                                ref="${ref#*/}"
                                if [[ "$ref" == "$cur"* ]]; then
@@ -586,16 +574,27 @@ __git_refs ()
                fi
                return
        fi
-       for i in $(git ls-remote "$dir" 2>/dev/null); do
-               case "$is_hash,$i" in
-               y,*) is_hash=n ;;
-               n,*^{}) is_hash=y ;;
-               n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;;
-               n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
-               n,refs/remotes/*) is_hash=y; echo "${i#refs/remotes/}" ;;
-               n,*) is_hash=y; echo "$i" ;;
-               esac
-       done
+       case "$cur" in
+       refs|refs/*)
+               git ls-remote "$dir" "$cur*" 2>/dev/null | \
+               while read -r hash i; do
+                       case "$i" in
+                       *^{}) ;;
+                       *) echo "$i" ;;
+                       esac
+               done
+               ;;
+       *)
+               git ls-remote "$dir" HEAD ORIG_HEAD 'refs/tags/*' 'refs/heads/*' 'refs/remotes/*' 2>/dev/null | \
+               while read -r hash i; do
+                       case "$i" in
+                       *^{}) ;;
+                       refs/*) echo "${i#refs/*/}" ;;
+                       *) echo "$i" ;;
+                       esac
+               done
+               ;;
+       esac
 }
 
 # __git_refs2 requires 1 argument (to pass to __git_refs)
@@ -610,30 +609,17 @@ __git_refs2 ()
 # __git_refs_remotes requires 1 argument (to pass to ls-remote)
 __git_refs_remotes ()
 {
-       local cmd i is_hash=y
-       for i in $(git ls-remote "$1" 2>/dev/null); do
-               case "$is_hash,$i" in
-               n,refs/heads/*)
-                       is_hash=y
-                       echo "$i:refs/remotes/$1/${i#refs/heads/}"
-                       ;;
-               y,*) is_hash=n ;;
-               n,*^{}) is_hash=y ;;
-               n,refs/tags/*) is_hash=y;;
-               n,*) is_hash=y; ;;
-               esac
+       local i hash
+       git ls-remote "$1" 'refs/heads/*' 2>/dev/null | \
+       while read -r hash i; do
+               echo "$i:refs/remotes/$1/${i#refs/heads/}"
        done
 }
 
 __git_remotes ()
 {
-       local i ngoff IFS=$'\n' d="$(__gitdir)"
-       __git_shopt -q nullglob || ngoff=1
-       __git_shopt -s nullglob
-       for i in "$d/remotes"/*; do
-               echo ${i#$d/remotes/}
-       done
-       [ "$ngoff" ] && __git_shopt -u nullglob
+       local i IFS=$'\n' d="$(__gitdir)"
+       test -d "$d/remotes" && ls -1 "$d/remotes"
        for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do
                i="${i#remote.}"
                echo "${i/.url*/}"
@@ -660,7 +646,8 @@ __git_merge_strategies=
 # is needed.
 __git_compute_merge_strategies ()
 {
-       : ${__git_merge_strategies:=$(__git_list_merge_strategies)}
+       test -n "$__git_merge_strategies" ||
+       __git_merge_strategies=$(__git_list_merge_strategies)
 }
 
 __git_complete_revlist_file ()
@@ -711,15 +698,15 @@ __git_complete_revlist_file ()
        *...*)
                pfx="${cur_%...*}..."
                cur_="${cur_#*...}"
-               __gitcomp "$(__git_refs)" "$pfx" "$cur_"
+               __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
                ;;
        *..*)
                pfx="${cur_%..*}.."
                cur_="${cur_#*..}"
-               __gitcomp "$(__git_refs)" "$pfx" "$cur_"
+               __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
                ;;
        *)
-               __gitcomp "$(__git_refs)"
+               __gitcomp_nl "$(__git_refs)"
                ;;
        esac
 }
@@ -759,7 +746,7 @@ __git_complete_remote_or_refspec ()
                c=$((++c))
        done
        if [ -z "$remote" ]; then
-               __gitcomp "$(__git_remotes)"
+               __gitcomp_nl "$(__git_remotes)"
                return
        fi
        if [ $no_complete_refspec = 1 ]; then
@@ -784,23 +771,23 @@ __git_complete_remote_or_refspec ()
        case "$cmd" in
        fetch)
                if [ $lhs = 1 ]; then
-                       __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur_"
+                       __gitcomp_nl "$(__git_refs2 "$remote")" "$pfx" "$cur_"
                else
-                       __gitcomp "$(__git_refs)" "$pfx" "$cur_"
+                       __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
                fi
                ;;
        pull)
                if [ $lhs = 1 ]; then
-                       __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_"
+                       __gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_"
                else
-                       __gitcomp "$(__git_refs)" "$pfx" "$cur_"
+                       __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
                fi
                ;;
        push)
                if [ $lhs = 1 ]; then
-                       __gitcomp "$(__git_refs)" "$pfx" "$cur_"
+                       __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
                else
-                       __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_"
+                       __gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_"
                fi
                ;;
        esac
@@ -838,7 +825,8 @@ __git_list_all_commands ()
 __git_all_commands=
 __git_compute_all_commands ()
 {
-       : ${__git_all_commands:=$(__git_list_all_commands)}
+       test -n "$__git_all_commands" ||
+       __git_all_commands=$(__git_list_all_commands)
 }
 
 __git_list_porcelain_commands ()
@@ -931,7 +919,8 @@ __git_porcelain_commands=
 __git_compute_porcelain_commands ()
 {
        __git_compute_all_commands
-       : ${__git_porcelain_commands:=$(__git_list_porcelain_commands)}
+       test -n "$__git_porcelain_commands" ||
+       __git_porcelain_commands=$(__git_list_porcelain_commands)
 }
 
 __git_pretty_aliases ()
@@ -1079,7 +1068,7 @@ _git_archive ()
                return
                ;;
        --remote=*)
-               __gitcomp "$(__git_remotes)" "" "${cur##--remote=}"
+               __gitcomp_nl "$(__git_remotes)" "" "${cur##--remote=}"
                return
                ;;
        --*)
@@ -1110,7 +1099,7 @@ _git_bisect ()
 
        case "$subcommand" in
        bad|good|reset|skip|start)
-               __gitcomp "$(__git_refs)"
+               __gitcomp_nl "$(__git_refs)"
                ;;
        *)
                COMPREPLY=()
@@ -1136,14 +1125,14 @@ _git_branch ()
                __gitcomp "
                        --color --no-color --verbose --abbrev= --no-abbrev
                        --track --no-track --contains --merged --no-merged
-                       --set-upstream
+                       --set-upstream --edit-description --list
                        "
                ;;
        *)
                if [ $only_local_ref = "y" -a $has_r = "n" ]; then
-                       __gitcomp "$(__git_heads)"
+                       __gitcomp_nl "$(__git_heads)"
                else
-                       __gitcomp "$(__git_refs)"
+                       __gitcomp_nl "$(__git_refs)"
                fi
                ;;
        esac
@@ -1190,7 +1179,7 @@ _git_checkout ()
                if [ -n "$(__git_find_on_cmdline "$flags")" ]; then
                        track=''
                fi
-               __gitcomp "$(__git_refs '' $track)"
+               __gitcomp_nl "$(__git_refs '' $track)"
                ;;
        esac
 }
@@ -1207,7 +1196,7 @@ _git_cherry_pick ()
                __gitcomp "--edit --no-commit"
                ;;
        *)
-               __gitcomp "$(__git_refs)"
+               __gitcomp_nl "$(__git_refs)"
                ;;
        esac
 }
@@ -1259,12 +1248,9 @@ _git_commit ()
                        " "" "${cur##--cleanup=}"
                return
                ;;
-       --reuse-message=*)
-               __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}"
-               return
-               ;;
-       --reedit-message=*)
-               __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}"
+       --reuse-message=*|--reedit-message=*|\
+       --fixup=*|--squash=*)
+               __gitcomp_nl "$(__git_refs)" "" "${cur#*=}"
                return
                ;;
        --untracked-files=*)
@@ -1278,7 +1264,7 @@ _git_commit ()
                        --dry-run --reuse-message= --reedit-message=
                        --reset-author --file= --message= --template=
                        --cleanup= --untracked-files --untracked-files=
-                       --verbose --quiet
+                       --verbose --quiet --fixup= --squash=
                        "
                return
        esac
@@ -1295,7 +1281,7 @@ _git_describe ()
                        "
                return
        esac
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 __git_diff_common_options="--stat --numstat --shortstat --summary
@@ -1432,6 +1418,10 @@ _git_gitk ()
        _gitk
 }
 
+__git_match_ctag() {
+       awk "/^${1////\\/}/ { print \$1 }" "$2"
+}
+
 _git_grep ()
 {
        __git_has_doubledash && return
@@ -1454,7 +1444,16 @@ _git_grep ()
                ;;
        esac
 
-       __gitcomp "$(__git_refs)"
+       case "$cword,$prev" in
+       2,*|*,-*)
+               if test -r tags; then
+                       __gitcomp_nl "$(__git_match_ctag "$cur" tags)"
+                       return
+               fi
+               ;;
+       esac
+
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_help ()
@@ -1512,7 +1511,7 @@ _git_ls_files ()
 
 _git_ls_remote ()
 {
-       __gitcomp "$(__git_remotes)"
+       __gitcomp_nl "$(__git_remotes)"
 }
 
 _git_ls_tree ()
@@ -1556,14 +1555,9 @@ _git_log ()
                merge="--merge"
        fi
        case "$cur" in
-       --pretty=*)
-               __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
-                       " "" "${cur##--pretty=}"
-               return
-               ;;
-       --format=*)
+       --pretty=*|--format=*)
                __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
-                       " "" "${cur##--format=}"
+                       " "" "${cur#*=}"
                return
                ;;
        --date=*)
@@ -1601,7 +1595,7 @@ _git_log ()
 
 __git_merge_options="
        --no-commit --no-stat --log --no-log --squash --strategy
-       --commit --stat --no-squash --ff --no-ff --ff-only
+       --commit --stat --no-squash --ff --no-ff --ff-only --edit --no-edit
 "
 
 _git_merge ()
@@ -1613,7 +1607,7 @@ _git_merge ()
                __gitcomp "$__git_merge_options"
                return
        esac
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_mergetool ()
@@ -1633,7 +1627,7 @@ _git_mergetool ()
 
 _git_merge_base ()
 {
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_mv ()
@@ -1664,18 +1658,16 @@ _git_notes ()
        ,*)
                case "${words[cword-1]}" in
                --ref)
-                       __gitcomp "$(__git_refs)"
+                       __gitcomp_nl "$(__git_refs)"
                        ;;
                *)
                        __gitcomp "$subcommands --ref"
                        ;;
                esac
                ;;
-       add,--reuse-message=*|append,--reuse-message=*)
-               __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}"
-               ;;
+       add,--reuse-message=*|append,--reuse-message=*|\
        add,--reedit-message=*|append,--reedit-message=*)
-               __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}"
+               __gitcomp_nl "$(__git_refs)" "" "${cur#*=}"
                ;;
        add,--*|append,--*)
                __gitcomp '--file= --message= --reedit-message=
@@ -1694,7 +1686,7 @@ _git_notes ()
                -m|-F)
                        ;;
                *)
-                       __gitcomp "$(__git_refs)"
+                       __gitcomp_nl "$(__git_refs)"
                        ;;
                esac
                ;;
@@ -1722,18 +1714,18 @@ _git_push ()
 {
        case "$prev" in
        --repo)
-               __gitcomp "$(__git_remotes)"
+               __gitcomp_nl "$(__git_remotes)"
                return
        esac
        case "$cur" in
        --repo=*)
-               __gitcomp "$(__git_remotes)" "" "${cur##--repo=}"
+               __gitcomp_nl "$(__git_remotes)" "" "${cur##--repo=}"
                return
                ;;
        --*)
                __gitcomp "
                        --all --mirror --tags --dry-run --force --verbose
-                       --receive-pack= --repo=
+                       --receive-pack= --repo= --set-upstream
                "
                return
                ;;
@@ -1765,7 +1757,7 @@ _git_rebase ()
 
                return
        esac
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_reflog ()
@@ -1776,7 +1768,7 @@ _git_reflog ()
        if [ -z "$subcommand" ]; then
                __gitcomp "$subcommands"
        else
-               __gitcomp "$(__git_refs)"
+               __gitcomp_nl "$(__git_refs)"
        fi
 }
 
@@ -1844,7 +1836,7 @@ __git_config_get_set_variables ()
        done
 
        git --git-dir="$(__gitdir)" config $config_file --list 2>/dev/null |
-       while read line
+       while read -r line
        do
                case "$line" in
                *.*=*)
@@ -1858,23 +1850,27 @@ _git_config ()
 {
        case "$prev" in
        branch.*.remote)
-               __gitcomp "$(__git_remotes)"
+               __gitcomp_nl "$(__git_remotes)"
                return
                ;;
        branch.*.merge)
-               __gitcomp "$(__git_refs)"
+               __gitcomp_nl "$(__git_refs)"
                return
                ;;
        remote.*.fetch)
                local remote="${prev#remote.}"
                remote="${remote%.fetch}"
-               __gitcomp "$(__git_refs_remotes "$remote")"
+               if [ -z "$cur" ]; then
+                       COMPREPLY=("refs/heads/")
+                       return
+               fi
+               __gitcomp_nl "$(__git_refs_remotes "$remote")"
                return
                ;;
        remote.*.push)
                local remote="${prev#remote.}"
                remote="${remote%.push}"
-               __gitcomp "$(git --git-dir="$(__gitdir)" \
+               __gitcomp_nl "$(git --git-dir="$(__gitdir)" \
                        for-each-ref --format='%(refname):%(refname)' \
                        refs/heads)"
                return
@@ -1921,7 +1917,7 @@ _git_config ()
                return
                ;;
        --get|--get-all|--unset|--unset-all)
-               __gitcomp "$(__git_config_get_set_variables)"
+               __gitcomp_nl "$(__git_config_get_set_variables)"
                return
                ;;
        *.*)
@@ -1947,7 +1943,7 @@ _git_config ()
                ;;
        branch.*)
                local pfx="${cur%.*}." cur_="${cur#*.}"
-               __gitcomp "$(__git_heads)" "$pfx" "$cur_" "."
+               __gitcomp_nl "$(__git_heads)" "$pfx" "$cur_" "."
                return
                ;;
        guitool.*.*)
@@ -1976,7 +1972,7 @@ _git_config ()
        pager.*)
                local pfx="${cur%.*}." cur_="${cur#*.}"
                __git_compute_all_commands
-               __gitcomp "$__git_all_commands" "$pfx" "$cur_"
+               __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_"
                return
                ;;
        remote.*.*)
@@ -1989,7 +1985,7 @@ _git_config ()
                ;;
        remote.*)
                local pfx="${cur%.*}." cur_="${cur#*.}"
-               __gitcomp "$(__git_remotes)" "$pfx" "$cur_" "."
+               __gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
                return
                ;;
        url.*.*)
@@ -2290,7 +2286,7 @@ _git_remote ()
 
        case "$subcommand" in
        rename|rm|show|prune)
-               __gitcomp "$(__git_remotes)"
+               __gitcomp_nl "$(__git_remotes)"
                ;;
        update)
                local i c='' IFS=$'\n'
@@ -2308,7 +2304,7 @@ _git_remote ()
 
 _git_replace ()
 {
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_reset ()
@@ -2321,7 +2317,7 @@ _git_reset ()
                return
                ;;
        esac
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_revert ()
@@ -2332,7 +2328,7 @@ _git_revert ()
                return
                ;;
        esac
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_rm ()
@@ -2370,14 +2366,9 @@ _git_show ()
        __git_has_doubledash && return
 
        case "$cur" in
-       --pretty=*)
-               __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
-                       " "" "${cur##--pretty=}"
-               return
-               ;;
-       --format=*)
+       --pretty=*|--format=*)
                __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
-                       " "" "${cur##--format=}"
+                       " "" "${cur#*=}"
                return
                ;;
        --*)
@@ -2436,7 +2427,7 @@ _git_stash ()
                        COMPREPLY=()
                        ;;
                show,*|apply,*|drop,*|pop,*|branch,*)
-                       __gitcomp "$(git --git-dir="$(__gitdir)" stash list \
+                       __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \
                                        | sed -n -e 's/:.*//p')"
                        ;;
                *)
@@ -2570,7 +2561,7 @@ _git_tag ()
                i="${words[c]}"
                case "$i" in
                -d|-v)
-                       __gitcomp "$(__git_tags)"
+                       __gitcomp_nl "$(__git_tags)"
                        return
                        ;;
                -f)
@@ -2586,13 +2577,13 @@ _git_tag ()
                ;;
        -*|tag)
                if [ $f = 1 ]; then
-                       __gitcomp "$(__git_tags)"
+                       __gitcomp_nl "$(__git_tags)"
                else
                        COMPREPLY=()
                fi
                ;;
        *)
-               __gitcomp "$(__git_refs)"
+               __gitcomp_nl "$(__git_refs)"
                ;;
        esac
 }
@@ -2613,6 +2604,10 @@ _git ()
                # workaround zsh's bug that leaves 'words' as a special
                # variable in versions < 4.3.12
                typeset -h words
+
+               # workaround zsh's bug that quotes spaces in the COMPREPLY
+               # array if IFS doesn't contain spaces.
+               typeset -h IFS
        fi
 
        local cur words cword prev
@@ -2669,6 +2664,10 @@ _gitk ()
                # workaround zsh's bug that leaves 'words' as a special
                # variable in versions < 4.3.12
                typeset -h words
+
+               # workaround zsh's bug that quotes spaces in the COMPREPLY
+               # array if IFS doesn't contain spaces.
+               typeset -h IFS
        fi
 
        local cur words cword prev
@@ -2707,33 +2706,3 @@ if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
 complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \
        || complete -o default -o nospace -F _git git.exe
 fi
-
-if [[ -n ${ZSH_VERSION-} ]]; then
-       __git_shopt () {
-               local option
-               if [ $# -ne 2 ]; then
-                       echo "USAGE: $0 (-q|-s|-u) <option>" >&2
-                       return 1
-               fi
-               case "$2" in
-               nullglob)
-                       option="$2"
-                       ;;
-               *)
-                       echo "$0: invalid option: $2" >&2
-                       return 1
-               esac
-               case "$1" in
-               -q)     setopt | grep -q "$option" ;;
-               -u)     unsetopt "$option" ;;
-               -s)     setopt "$option" ;;
-               *)
-                       echo "$0: invalid flag: $1" >&2
-                       return 1
-               esac
-       }
-else
-       __git_shopt () {
-               shopt "$@"
-       }
-fi
diff --git a/contrib/credential/osxkeychain/.gitignore b/contrib/credential/osxkeychain/.gitignore
new file mode 100644 (file)
index 0000000..6c5b702
--- /dev/null
@@ -0,0 +1 @@
+git-credential-osxkeychain
diff --git a/contrib/credential/osxkeychain/Makefile b/contrib/credential/osxkeychain/Makefile
new file mode 100644 (file)
index 0000000..75c07f8
--- /dev/null
@@ -0,0 +1,14 @@
+all:: git-credential-osxkeychain
+
+CC = gcc
+RM = rm -f
+CFLAGS = -g -Wall
+
+git-credential-osxkeychain: git-credential-osxkeychain.o
+       $(CC) -o $@ $< -Wl,-framework -Wl,Security
+
+git-credential-osxkeychain.o: git-credential-osxkeychain.c
+       $(CC) -c $(CFLAGS) $<
+
+clean:
+       $(RM) git-credential-osxkeychain git-credential-osxkeychain.o
diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
new file mode 100644 (file)
index 0000000..6beed12
--- /dev/null
@@ -0,0 +1,173 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <Security/Security.h>
+
+static SecProtocolType protocol;
+static char *host;
+static char *path;
+static char *username;
+static char *password;
+static UInt16 port;
+
+static void die(const char *err, ...)
+{
+       char msg[4096];
+       va_list params;
+       va_start(params, err);
+       vsnprintf(msg, sizeof(msg), err, params);
+       fprintf(stderr, "%s\n", msg);
+       va_end(params);
+       exit(1);
+}
+
+static void *xstrdup(const char *s1)
+{
+       void *ret = strdup(s1);
+       if (!ret)
+               die("Out of memory");
+       return ret;
+}
+
+#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
+#define KEYCHAIN_ARGS \
+       NULL, /* default keychain */ \
+       KEYCHAIN_ITEM(host), \
+       0, NULL, /* account domain */ \
+       KEYCHAIN_ITEM(username), \
+       KEYCHAIN_ITEM(path), \
+       port, \
+       protocol, \
+       kSecAuthenticationTypeDefault
+
+static void write_item(const char *what, const char *buf, int len)
+{
+       printf("%s=", what);
+       fwrite(buf, 1, len, stdout);
+       putchar('\n');
+}
+
+static void find_username_in_item(SecKeychainItemRef item)
+{
+       SecKeychainAttributeList list;
+       SecKeychainAttribute attr;
+
+       list.count = 1;
+       list.attr = &attr;
+       attr.tag = kSecAccountItemAttr;
+
+       if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
+               return;
+
+       write_item("username", attr.data, attr.length);
+       SecKeychainItemFreeContent(&list, NULL);
+}
+
+static void find_internet_password(void)
+{
+       void *buf;
+       UInt32 len;
+       SecKeychainItemRef item;
+
+       if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
+               return;
+
+       write_item("password", buf, len);
+       if (!username)
+               find_username_in_item(item);
+
+       SecKeychainItemFreeContent(NULL, buf);
+}
+
+static void delete_internet_password(void)
+{
+       SecKeychainItemRef item;
+
+       /*
+        * Require at least a protocol and host for removal, which is what git
+        * will give us; if you want to do something more fancy, use the
+        * Keychain manager.
+        */
+       if (!protocol || !host)
+               return;
+
+       if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
+               return;
+
+       SecKeychainItemDelete(item);
+}
+
+static void add_internet_password(void)
+{
+       /* Only store complete credentials */
+       if (!protocol || !host || !username || !password)
+               return;
+
+       if (SecKeychainAddInternetPassword(
+             KEYCHAIN_ARGS,
+             KEYCHAIN_ITEM(password),
+             NULL))
+               return;
+}
+
+static void read_credential(void)
+{
+       char buf[1024];
+
+       while (fgets(buf, sizeof(buf), stdin)) {
+               char *v;
+
+               if (!strcmp(buf, "\n"))
+                       break;
+               buf[strlen(buf)-1] = '\0';
+
+               v = strchr(buf, '=');
+               if (!v)
+                       die("bad input: %s", buf);
+               *v++ = '\0';
+
+               if (!strcmp(buf, "protocol")) {
+                       if (!strcmp(v, "https"))
+                               protocol = kSecProtocolTypeHTTPS;
+                       else if (!strcmp(v, "http"))
+                               protocol = kSecProtocolTypeHTTP;
+                       else /* we don't yet handle other protocols */
+                               exit(0);
+               }
+               else if (!strcmp(buf, "host")) {
+                       char *colon = strchr(v, ':');
+                       if (colon) {
+                               *colon++ = '\0';
+                               port = atoi(colon);
+                       }
+                       host = xstrdup(v);
+               }
+               else if (!strcmp(buf, "path"))
+                       path = xstrdup(v);
+               else if (!strcmp(buf, "username"))
+                       username = xstrdup(v);
+               else if (!strcmp(buf, "password"))
+                       password = xstrdup(v);
+       }
+}
+
+int main(int argc, const char **argv)
+{
+       const char *usage =
+               "Usage: git credential-osxkeychain <get|store|erase>";
+
+       if (!argv[1])
+               die(usage);
+
+       read_credential();
+
+       if (!strcmp(argv[1], "get"))
+               find_internet_password();
+       else if (!strcmp(argv[1], "store"))
+               add_internet_password();
+       else if (!strcmp(argv[1], "erase"))
+               delete_internet_password();
+       /* otherwise, ignore unknown action */
+
+       return 0;
+}
diff --git a/contrib/diff-highlight/README b/contrib/diff-highlight/README
new file mode 100644 (file)
index 0000000..1b7b6df
--- /dev/null
@@ -0,0 +1,57 @@
+diff-highlight
+==============
+
+Line oriented diffs are great for reviewing code, because for most
+hunks, you want to see the old and the new segments of code next to each
+other. Sometimes, though, when an old line and a new line are very
+similar, it's hard to immediately see the difference.
+
+You can use "--color-words" to highlight only the changed portions of
+lines. However, this can often be hard to read for code, as it loses
+the line structure, and you end up with oddly formatted bits.
+
+Instead, this script post-processes the line-oriented diff, finds pairs
+of lines, and highlights the differing segments.  It's currently very
+simple and stupid about doing these tasks. In particular:
+
+  1. It will only highlight a pair of lines if they are the only two
+     lines in a hunk.  It could instead try to match up "before" and
+     "after" lines for a given hunk into pairs of similar lines.
+     However, this may end up visually distracting, as the paired
+     lines would have other highlighted lines in between them. And in
+     practice, the lines which most need attention called to their
+     small, hard-to-see changes are touching only a single line.
+
+  2. It will find the common prefix and suffix of two lines, and
+     consider everything in the middle to be "different". It could
+     instead do a real diff of the characters between the two lines and
+     find common subsequences. However, the point of the highlight is to
+     call attention to a certain area. Even if some small subset of the
+     highlighted area actually didn't change, that's OK. In practice it
+     ends up being more readable to just have a single blob on the line
+     showing the interesting bit.
+
+The goal of the script is therefore not to be exact about highlighting
+changes, but to call attention to areas of interest without being
+visually distracting.  Non-diff lines and existing diff coloration is
+preserved; the intent is that the output should look exactly the same as
+the input, except for the occasional highlight.
+
+Use
+---
+
+You can try out the diff-highlight program with:
+
+---------------------------------------------
+git log -p --color | /path/to/diff-highlight
+---------------------------------------------
+
+If you want to use it all the time, drop it in your $PATH and put the
+following in your git configuration:
+
+---------------------------------------------
+[pager]
+       log = diff-highlight | less
+       show = diff-highlight | less
+       diff = diff-highlight | less
+---------------------------------------------
diff --git a/contrib/diff-highlight/diff-highlight b/contrib/diff-highlight/diff-highlight
new file mode 100755 (executable)
index 0000000..d893898
--- /dev/null
@@ -0,0 +1,124 @@
+#!/usr/bin/perl
+
+# Highlight by reversing foreground and background. You could do
+# other things like bold or underline if you prefer.
+my $HIGHLIGHT   = "\x1b[7m";
+my $UNHIGHLIGHT = "\x1b[27m";
+my $COLOR = qr/\x1b\[[0-9;]*m/;
+
+my @window;
+
+while (<>) {
+       # We highlight only single-line changes, so we need
+       # a 4-line window to make a decision on whether
+       # to highlight.
+       push @window, $_;
+       next if @window < 4;
+       if ($window[0] =~ /^$COLOR*(\@| )/ &&
+           $window[1] =~ /^$COLOR*-/ &&
+           $window[2] =~ /^$COLOR*\+/ &&
+           $window[3] !~ /^$COLOR*\+/) {
+               print shift @window;
+               show_pair(shift @window, shift @window);
+       }
+       else {
+               print shift @window;
+       }
+
+       # Most of the time there is enough output to keep things streaming,
+       # but for something like "git log -Sfoo", you can get one early
+       # commit and then many seconds of nothing. We want to show
+       # that one commit as soon as possible.
+       #
+       # Since we can receive arbitrary input, there's no optimal
+       # place to flush. Flushing on a blank line is a heuristic that
+       # happens to match git-log output.
+       if (!length) {
+               local $| = 1;
+       }
+}
+
+# Special case a single-line hunk at the end of file.
+if (@window == 3 &&
+    $window[0] =~ /^$COLOR*(\@| )/ &&
+    $window[1] =~ /^$COLOR*-/ &&
+    $window[2] =~ /^$COLOR*\+/) {
+       print shift @window;
+       show_pair(shift @window, shift @window);
+}
+
+# And then flush any remaining lines.
+while (@window) {
+       print shift @window;
+}
+
+exit 0;
+
+sub show_pair {
+       my @a = split_line(shift);
+       my @b = split_line(shift);
+
+       # Find common prefix, taking care to skip any ansi
+       # color codes.
+       my $seen_plusminus;
+       my ($pa, $pb) = (0, 0);
+       while ($pa < @a && $pb < @b) {
+               if ($a[$pa] =~ /$COLOR/) {
+                       $pa++;
+               }
+               elsif ($b[$pb] =~ /$COLOR/) {
+                       $pb++;
+               }
+               elsif ($a[$pa] eq $b[$pb]) {
+                       $pa++;
+                       $pb++;
+               }
+               elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') {
+                       $seen_plusminus = 1;
+                       $pa++;
+                       $pb++;
+               }
+               else {
+                       last;
+               }
+       }
+
+       # Find common suffix, ignoring colors.
+       my ($sa, $sb) = ($#a, $#b);
+       while ($sa >= $pa && $sb >= $pb) {
+               if ($a[$sa] =~ /$COLOR/) {
+                       $sa--;
+               }
+               elsif ($b[$sb] =~ /$COLOR/) {
+                       $sb--;
+               }
+               elsif ($a[$sa] eq $b[$sb]) {
+                       $sa--;
+                       $sb--;
+               }
+               else {
+                       last;
+               }
+       }
+
+       print highlight(\@a, $pa, $sa);
+       print highlight(\@b, $pb, $sb);
+}
+
+sub split_line {
+       local $_ = shift;
+       return map { /$COLOR/ ? $_ : (split //) }
+              split /($COLOR*)/;
+}
+
+sub highlight {
+       my ($line, $prefix, $suffix) = @_;
+
+       return join('',
+               @{$line}[0..($prefix-1)],
+               $HIGHLIGHT,
+               @{$line}[$prefix..$suffix],
+               $UNHIGHLIGHT,
+               @{$line}[($suffix+1)..$#$line]
+       );
+}
index 2f7b270566471ebe8088cd2f024c0ca5e49eee4c..9ccc87b2055a8f90fbe1fcc80649f6a72bb09a5e 100755 (executable)
@@ -22,37 +22,41 @@ def p4_build_cmd(cmd):
     location. It means that hooking into the environment, or other configuration
     can be done more easily.
     """
-    real_cmd = "%s " % "p4"
+    real_cmd = ["p4"]
 
     user = gitConfig("git-p4.user")
     if len(user) > 0:
-        real_cmd += "-u %s " % user
+        real_cmd += ["-u",user]
 
     password = gitConfig("git-p4.password")
     if len(password) > 0:
-        real_cmd += "-P %s " % password
+        real_cmd += ["-P", password]
 
     port = gitConfig("git-p4.port")
     if len(port) > 0:
-        real_cmd += "-p %s " % port
+        real_cmd += ["-p", port]
 
     host = gitConfig("git-p4.host")
     if len(host) > 0:
-        real_cmd += "-h %s " % host
+        real_cmd += ["-h", host]
 
     client = gitConfig("git-p4.client")
     if len(client) > 0:
-        real_cmd += "-c %s " % client
+        real_cmd += ["-c", client]
 
-    real_cmd += "%s" % (cmd)
-    if verbose:
-        print real_cmd
+
+    if isinstance(cmd,basestring):
+        real_cmd = ' '.join(real_cmd) + ' ' + cmd
+    else:
+        real_cmd += cmd
     return real_cmd
 
 def chdir(dir):
-    if os.name == 'nt':
-        os.environ['PWD']=dir
+    # P4 uses the PWD environment variable rather than getcwd(). Since we're
+    # not using the shell, we have to set it ourselves.  This path could
+    # be relative, so go there first, then figure out where we ended up.
     os.chdir(dir)
+    os.environ['PWD'] = os.getcwd()
 
 def die(msg):
     if verbose:
@@ -61,29 +65,34 @@ def die(msg):
         sys.stderr.write(msg + "\n")
         sys.exit(1)
 
-def write_pipe(c, str):
+def write_pipe(c, stdin):
     if verbose:
-        sys.stderr.write('Writing pipe: %s\n' % c)
+        sys.stderr.write('Writing pipe: %s\n' % str(c))
 
-    pipe = os.popen(c, 'w')
-    val = pipe.write(str)
-    if pipe.close():
-        die('Command failed: %s' % c)
+    expand = isinstance(c,basestring)
+    p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand)
+    pipe = p.stdin
+    val = pipe.write(stdin)
+    pipe.close()
+    if p.wait():
+        die('Command failed: %s' % str(c))
 
     return val
 
-def p4_write_pipe(c, str):
+def p4_write_pipe(c, stdin):
     real_cmd = p4_build_cmd(c)
-    return write_pipe(real_cmd, str)
+    return write_pipe(real_cmd, stdin)
 
 def read_pipe(c, ignore_error=False):
     if verbose:
-        sys.stderr.write('Reading pipe: %s\n' % c)
+        sys.stderr.write('Reading pipe: %s\n' % str(c))
 
-    pipe = os.popen(c, 'rb')
+    expand = isinstance(c,basestring)
+    p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
+    pipe = p.stdout
     val = pipe.read()
-    if pipe.close() and not ignore_error:
-        die('Command failed: %s' % c)
+    if p.wait() and not ignore_error:
+        die('Command failed: %s' % str(c))
 
     return val
 
@@ -93,12 +102,14 @@ def p4_read_pipe(c, ignore_error=False):
 
 def read_pipe_lines(c):
     if verbose:
-        sys.stderr.write('Reading pipe: %s\n' % c)
-    ## todo: check return status
-    pipe = os.popen(c, 'rb')
+        sys.stderr.write('Reading pipe: %s\n' % str(c))
+
+    expand = isinstance(c, basestring)
+    p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
+    pipe = p.stdout
     val = pipe.readlines()
-    if pipe.close():
-        die('Command failed: %s' % c)
+    if pipe.close() or p.wait():
+        die('Command failed: %s' % str(c))
 
     return val
 
@@ -108,23 +119,73 @@ def p4_read_pipe_lines(c):
     return read_pipe_lines(real_cmd)
 
 def system(cmd):
+    expand = isinstance(cmd,basestring)
     if verbose:
-        sys.stderr.write("executing %s\n" % cmd)
-    if os.system(cmd) != 0:
-        die("command failed: %s" % cmd)
+        sys.stderr.write("executing %s\n" % str(cmd))
+    subprocess.check_call(cmd, shell=expand)
 
 def p4_system(cmd):
     """Specifically invoke p4 as the system command. """
     real_cmd = p4_build_cmd(cmd)
-    return system(real_cmd)
+    expand = isinstance(real_cmd, basestring)
+    subprocess.check_call(real_cmd, shell=expand)
+
+def p4_integrate(src, dest):
+    p4_system(["integrate", "-Dt", src, dest])
+
+def p4_sync(path):
+    p4_system(["sync", path])
+
+def p4_add(f):
+    p4_system(["add", f])
 
-def isP4Exec(kind):
-    """Determine if a Perforce 'kind' should have execute permission
+def p4_delete(f):
+    p4_system(["delete", f])
+
+def p4_edit(f):
+    p4_system(["edit", f])
+
+def p4_revert(f):
+    p4_system(["revert", f])
+
+def p4_reopen(type, file):
+    p4_system(["reopen", "-t", type, file])
+
+#
+# Canonicalize the p4 type and return a tuple of the
+# base type, plus any modifiers.  See "p4 help filetypes"
+# for a list and explanation.
+#
+def split_p4_type(p4type):
+
+    p4_filetypes_historical = {
+        "ctempobj": "binary+Sw",
+        "ctext": "text+C",
+        "cxtext": "text+Cx",
+        "ktext": "text+k",
+        "kxtext": "text+kx",
+        "ltext": "text+F",
+        "tempobj": "binary+FSw",
+        "ubinary": "binary+F",
+        "uresource": "resource+F",
+        "uxbinary": "binary+Fx",
+        "xbinary": "binary+x",
+        "xltext": "text+Fx",
+        "xtempobj": "binary+Swx",
+        "xtext": "text+x",
+        "xunicode": "unicode+x",
+        "xutf16": "utf16+x",
+    }
+    if p4type in p4_filetypes_historical:
+        p4type = p4_filetypes_historical[p4type]
+    mods = ""
+    s = p4type.split("+")
+    base = s[0]
+    mods = ""
+    if len(s) > 1:
+        mods = s[1]
+    return (base, mods)
 
-    'p4 help filetypes' gives a list of the types.  If it starts with 'x',
-    or x follows one of a few letters.  Otherwise, if there is an 'x' after
-    a plus sign, it is also executable"""
-    return (re.search(r"(^[cku]?x)|\+.*x", kind) != None)
 
 def setP4ExecBit(file, mode):
     # Reopens an already open file and changes the execute bit to match
@@ -139,12 +200,12 @@ def setP4ExecBit(file, mode):
         if p4Type[-1] == "+":
             p4Type = p4Type[0:-1]
 
-    p4_system("reopen -t %s %s" % (p4Type, file))
+    p4_reopen(p4Type, file)
 
 def getP4OpenedType(file):
     # Returns the perforce file type for the given file.
 
-    result = p4_read_pipe("opened %s" % file)
+    result = p4_read_pipe(["opened", file])
     match = re.match(".*\((.+)\)\r?$", result)
     if match:
         return match.group(1)
@@ -200,9 +261,17 @@ def isModeExecChanged(src_mode, dst_mode):
     return isModeExec(src_mode) != isModeExec(dst_mode)
 
 def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
-    cmd = p4_build_cmd("-G %s" % (cmd))
+
+    if isinstance(cmd,basestring):
+        cmd = "-G " + cmd
+        expand = True
+    else:
+        cmd = ["-G"] + cmd
+        expand = False
+
+    cmd = p4_build_cmd(cmd)
     if verbose:
-        sys.stderr.write("Opening pipe: %s\n" % cmd)
+        sys.stderr.write("Opening pipe: %s\n" % str(cmd))
 
     # Use a temporary file to avoid deadlocks without
     # subprocess.communicate(), which would put another copy
@@ -210,11 +279,16 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
     stdin_file = None
     if stdin is not None:
         stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
-        stdin_file.write(stdin)
+        if isinstance(stdin,basestring):
+            stdin_file.write(stdin)
+        else:
+            for i in stdin:
+                stdin_file.write(i + '\n')
         stdin_file.flush()
         stdin_file.seek(0)
 
-    p4 = subprocess.Popen(cmd, shell=True,
+    p4 = subprocess.Popen(cmd,
+                          shell=expand,
                           stdin=stdin_file,
                           stdout=subprocess.PIPE)
 
@@ -247,7 +321,7 @@ def p4Where(depotPath):
     if not depotPath.endswith("/"):
         depotPath += "/"
     depotPath = depotPath + "..."
-    outputList = p4CmdList("where %s" % depotPath)
+    outputList = p4CmdList(["where", depotPath])
     output = None
     for entry in outputList:
         if "depotFile" in entry:
@@ -288,6 +362,11 @@ def isValidGitDir(path):
 def parseRevision(ref):
     return read_pipe("git rev-parse %s" % ref).strip()
 
+def branchExists(ref):
+    rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref],
+                     ignore_error=True)
+    return len(rev) > 0
+
 def extractLogMessageFromGitCommit(commit):
     logMessage = ""
 
@@ -449,8 +528,10 @@ def originP4BranchesExist():
 
 def p4ChangesForPaths(depotPaths, changeRange):
     assert depotPaths
-    output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
-                                                        for p in depotPaths]))
+    cmd = ['changes']
+    for p in depotPaths:
+        cmd += ["%s...%s" % (p, changeRange)]
+    output = p4_read_pipe_lines(cmd)
 
     changes = {}
     for line in output:
@@ -474,6 +555,46 @@ def p4PathStartsWith(path, prefix):
         return path.lower().startswith(prefix.lower())
     return path.startswith(prefix)
 
+def getClientSpec():
+    """Look at the p4 client spec, create a View() object that contains
+       all the mappings, and return it."""
+
+    specList = p4CmdList("client -o")
+    if len(specList) != 1:
+        die('Output from "client -o" is %d lines, expecting 1' %
+            len(specList))
+
+    # dictionary of all client parameters
+    entry = specList[0]
+
+    # just the keys that start with "View"
+    view_keys = [ k for k in entry.keys() if k.startswith("View") ]
+
+    # hold this new View
+    view = View()
+
+    # append the lines, in order, to the view
+    for view_num in range(len(view_keys)):
+        k = "View%d" % view_num
+        if k not in view_keys:
+            die("Expected view key %s missing" % k)
+        view.append(entry[k])
+
+    return view
+
+def getClientRoot():
+    """Grab the client directory."""
+
+    output = p4CmdList("client -o")
+    if len(output) != 1:
+        die('Output from "client -o" is %d lines, expecting 1' % len(output))
+
+    entry = output[0]
+    if "Root" not in entry:
+        die('Client has no "Root"')
+
+    return entry["Root"]
+
 class Command:
     def __init__(self):
         self.usage = "usage: %prog [options]"
@@ -533,7 +654,7 @@ class P4Debug(Command):
 
     def run(self, args):
         j = 0
-        for output in p4CmdList(" ".join(args)):
+        for output in p4CmdList(args):
             print 'Element: %d' % j
             j += 1
             print output
@@ -687,7 +808,7 @@ class P4Submit(Command, P4UserMap):
                 break
         if not client:
             die("could not get client spec")
-        results = p4CmdList("changes -c %s -m 1" % client)
+        results = p4CmdList(["changes", "-c", client, "-m", "1"])
         for r in results:
             if r.has_key('change'):
                 return r['change']
@@ -750,7 +871,7 @@ class P4Submit(Command, P4UserMap):
         # remove lines in the Files section that show changes to files outside the depot path we're committing into
         template = ""
         inFilesSection = False
-        for line in p4_read_pipe_lines("change -o"):
+        for line in p4_read_pipe_lines(['change', '-o']):
             if line.endswith("\r\n"):
                 line = line[:-2] + "\n"
             if inFilesSection:
@@ -772,6 +893,41 @@ class P4Submit(Command, P4UserMap):
 
         return template
 
+    def edit_template(self, template_file):
+        """Invoke the editor to let the user change the submission
+           message.  Return true if okay to continue with the submit."""
+
+        # if configured to skip the editing part, just submit
+        if gitConfig("git-p4.skipSubmitEdit") == "true":
+            return True
+
+        # look at the modification time, to check later if the user saved
+        # the file
+        mtime = os.stat(template_file).st_mtime
+
+        # invoke the editor
+        if os.environ.has_key("P4EDITOR"):
+            editor = os.environ.get("P4EDITOR")
+        else:
+            editor = read_pipe("git var GIT_EDITOR").strip()
+        system(editor + " " + template_file)
+
+        # If the file was not saved, prompt to see if this patch should
+        # be skipped.  But skip this verification step if configured so.
+        if gitConfig("git-p4.skipSubmitEditCheck") == "true":
+            return True
+
+        # modification time updated means user saved the file
+        if os.stat(template_file).st_mtime > mtime:
+            return True
+
+        while True:
+            response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
+            if response == 'y':
+                return True
+            if response == 'n':
+                return False
+
     def applyCommit(self, id):
         print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
 
@@ -807,7 +963,7 @@ class P4Submit(Command, P4UserMap):
             modifier = diff['status']
             path = diff['src']
             if modifier == "M":
-                p4_system("edit \"%s\"" % path)
+                p4_edit(path)
                 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
                     filesToChangeExecBit[path] = diff['dst_mode']
                 editedFiles.add(path)
@@ -822,21 +978,21 @@ class P4Submit(Command, P4UserMap):
                     filesToAdd.remove(path)
             elif modifier == "C":
                 src, dest = diff['src'], diff['dst']
-                p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
+                p4_integrate(src, dest)
                 if diff['src_sha1'] != diff['dst_sha1']:
-                    p4_system("edit \"%s\"" % (dest))
+                    p4_edit(dest)
                 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
-                    p4_system("edit \"%s\"" % (dest))
+                    p4_edit(dest)
                     filesToChangeExecBit[dest] = diff['dst_mode']
                 os.unlink(dest)
                 editedFiles.add(dest)
             elif modifier == "R":
                 src, dest = diff['src'], diff['dst']
-                p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
+                p4_integrate(src, dest)
                 if diff['src_sha1'] != diff['dst_sha1']:
-                    p4_system("edit \"%s\"" % (dest))
+                    p4_edit(dest)
                 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
-                    p4_system("edit \"%s\"" % (dest))
+                    p4_edit(dest)
                     filesToChangeExecBit[dest] = diff['dst_mode']
                 os.unlink(dest)
                 editedFiles.add(dest)
@@ -859,9 +1015,9 @@ class P4Submit(Command, P4UserMap):
             if response == "s":
                 print "Skipping! Good luck with the next patches..."
                 for f in editedFiles:
-                    p4_system("revert \"%s\"" % f);
+                    p4_revert(f)
                 for f in filesToAdd:
-                    system("rm %s" %f)
+                    os.remove(f)
                 return
             elif response == "a":
                 os.system(applyPatchCmd)
@@ -882,10 +1038,10 @@ class P4Submit(Command, P4UserMap):
         system(applyPatchCmd)
 
         for f in filesToAdd:
-            p4_system("add \"%s\"" % f)
+            p4_add(f)
         for f in filesToDelete:
-            p4_system("revert \"%s\"" % f)
-            p4_system("delete \"%s\"" % f)
+            p4_revert(f)
+            p4_delete(f)
 
         # Set/clear executable bits
         for f in filesToChangeExecBit.keys():
@@ -907,7 +1063,7 @@ class P4Submit(Command, P4UserMap):
                 del(os.environ["P4DIFF"])
             diff = ""
             for editedFile in editedFiles:
-                diff += p4_read_pipe("diff -du %r" % editedFile)
+                diff += p4_read_pipe(['diff', '-du', editedFile])
 
             newdiff = ""
             for newFile in filesToAdd:
@@ -926,7 +1082,7 @@ class P4Submit(Command, P4UserMap):
 
             separatorLine = "######## everything below this line is just the diff #######\n"
 
-            [handle, fileName] = tempfile.mkstemp()
+            (handle, fileName) = tempfile.mkstemp()
             tmpFile = os.fdopen(handle, "w+")
             if self.isWindows:
                 submitTemplate = submitTemplate.replace("\n", "\r\n")
@@ -934,46 +1090,32 @@ class P4Submit(Command, P4UserMap):
                 newdiff = newdiff.replace("\n", "\r\n")
             tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
             tmpFile.close()
-            mtime = os.stat(fileName).st_mtime
-            if os.environ.has_key("P4EDITOR"):
-                editor = os.environ.get("P4EDITOR")
-            else:
-                editor = read_pipe("git var GIT_EDITOR").strip()
-            system(editor + " " + fileName)
 
-            if gitConfig("git-p4.skipSubmitEditCheck") == "true":
-                checkModTime = False
-            else:
-                checkModTime = True
-
-            response = "y"
-            if checkModTime and (os.stat(fileName).st_mtime <= mtime):
-                response = "x"
-                while response != "y" and response != "n":
-                    response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
-
-            if response == "y":
+            if self.edit_template(fileName):
+                # read the edited message and submit
                 tmpFile = open(fileName, "rb")
                 message = tmpFile.read()
                 tmpFile.close()
                 submitTemplate = message[:message.index(separatorLine)]
                 if self.isWindows:
                     submitTemplate = submitTemplate.replace("\r\n", "\n")
-                p4_write_pipe("submit -i", submitTemplate)
+                p4_write_pipe(['submit', '-i'], submitTemplate)
 
                 if self.preserveUser:
                     if p4User:
                         # Get last changelist number. Cannot easily get it from
-                        # the submit command output as the output is unmarshalled.
+                        # the submit command output as the output is
+                        # unmarshalled.
                         changelist = self.lastP4Changelist()
                         self.modifyChangelistUser(changelist, p4User)
-
             else:
+                # skip this patch
+                print "Submission cancelled, undoing p4 changes."
                 for f in editedFiles:
-                    p4_system("revert \"%s\"" % f);
+                    p4_revert(f)
                 for f in filesToAdd:
-                    p4_system("revert \"%s\"" % f);
-                    system("rm %s" %f)
+                    p4_revert(f)
+                    os.remove(f)
 
             os.remove(fileName)
         else:
@@ -992,6 +1134,8 @@ class P4Submit(Command, P4UserMap):
                 die("Detecting current git branch failed!")
         elif len(args) == 1:
             self.master = args[0]
+            if not branchExists(self.master):
+                die("Branch %s does not exist" % self.master)
         else:
             return False
 
@@ -1015,19 +1159,31 @@ class P4Submit(Command, P4UserMap):
             print "Internal error: cannot locate perforce depot path from existing branches"
             sys.exit(128)
 
-        self.clientPath = p4Where(self.depotPath)
+        self.useClientSpec = False
+        if gitConfig("git-p4.useclientspec", "--bool") == "true":
+            self.useClientSpec = True
+        if self.useClientSpec:
+            self.clientSpecDirs = getClientSpec()
+
+        if self.useClientSpec:
+            # all files are relative to the client spec
+            self.clientPath = getClientRoot()
+        else:
+            self.clientPath = p4Where(self.depotPath)
 
-        if len(self.clientPath) == 0:
-            print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
-            sys.exit(128)
+        if self.clientPath == "":
+            die("Error: Cannot locate perforce checkout of %s in client view" % self.depotPath)
 
         print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
         self.oldWorkingDirectory = os.getcwd()
 
+        # ensure the clientPath exists
+        if not os.path.exists(self.clientPath):
+            os.makedirs(self.clientPath)
+
         chdir(self.clientPath)
         print "Synchronizing p4 checkout..."
-        p4_system("sync ...")
-
+        p4_sync("...")
         self.check()
 
         commits = []
@@ -1062,6 +1218,218 @@ class P4Submit(Command, P4UserMap):
 
         return True
 
+class View(object):
+    """Represent a p4 view ("p4 help views"), and map files in a
+       repo according to the view."""
+
+    class Path(object):
+        """A depot or client path, possibly containing wildcards.
+           The only one supported is ... at the end, currently.
+           Initialize with the full path, with //depot or //client."""
+
+        def __init__(self, path, is_depot):
+            self.path = path
+            self.is_depot = is_depot
+            self.find_wildcards()
+            # remember the prefix bit, useful for relative mappings
+            m = re.match("(//[^/]+/)", self.path)
+            if not m:
+                die("Path %s does not start with //prefix/" % self.path)
+            prefix = m.group(1)
+            if not self.is_depot:
+                # strip //client/ on client paths
+                self.path = self.path[len(prefix):]
+
+        def find_wildcards(self):
+            """Make sure wildcards are valid, and set up internal
+               variables."""
+
+            self.ends_triple_dot = False
+            # There are three wildcards allowed in p4 views
+            # (see "p4 help views").  This code knows how to
+            # handle "..." (only at the end), but cannot deal with
+            # "%%n" or "*".  Only check the depot_side, as p4 should
+            # validate that the client_side matches too.
+            if re.search(r'%%[1-9]', self.path):
+                die("Can't handle %%n wildcards in view: %s" % self.path)
+            if self.path.find("*") >= 0:
+                die("Can't handle * wildcards in view: %s" % self.path)
+            triple_dot_index = self.path.find("...")
+            if triple_dot_index >= 0:
+                if not self.path.endswith("..."):
+                    die("Can handle ... wildcard only at end of path: %s" %
+                        self.path)
+                self.ends_triple_dot = True
+
+        def ensure_compatible(self, other_path):
+            """Make sure the wildcards agree."""
+            if self.ends_triple_dot != other_path.ends_triple_dot:
+                 die("Both paths must end with ... if either does;\n" +
+                     "paths: %s %s" % (self.path, other_path.path))
+
+        def match_wildcards(self, test_path):
+            """See if this test_path matches us, and fill in the value
+               of the wildcards if so.  Returns a tuple of
+               (True|False, wildcards[]).  For now, only the ... at end
+               is supported, so at most one wildcard."""
+            if self.ends_triple_dot:
+                dotless = self.path[:-3]
+                if test_path.startswith(dotless):
+                    wildcard = test_path[len(dotless):]
+                    return (True, [ wildcard ])
+            else:
+                if test_path == self.path:
+                    return (True, [])
+            return (False, [])
+
+        def match(self, test_path):
+            """Just return if it matches; don't bother with the wildcards."""
+            b, _ = self.match_wildcards(test_path)
+            return b
+
+        def fill_in_wildcards(self, wildcards):
+            """Return the relative path, with the wildcards filled in
+               if there are any."""
+            if self.ends_triple_dot:
+                return self.path[:-3] + wildcards[0]
+            else:
+                return self.path
+
+    class Mapping(object):
+        def __init__(self, depot_side, client_side, overlay, exclude):
+            # depot_side is without the trailing /... if it had one
+            self.depot_side = View.Path(depot_side, is_depot=True)
+            self.client_side = View.Path(client_side, is_depot=False)
+            self.overlay = overlay  # started with "+"
+            self.exclude = exclude  # started with "-"
+            assert not (self.overlay and self.exclude)
+            self.depot_side.ensure_compatible(self.client_side)
+
+        def __str__(self):
+            c = " "
+            if self.overlay:
+                c = "+"
+            if self.exclude:
+                c = "-"
+            return "View.Mapping: %s%s -> %s" % \
+                   (c, self.depot_side, self.client_side)
+
+        def map_depot_to_client(self, depot_path):
+            """Calculate the client path if using this mapping on the
+               given depot path; does not consider the effect of other
+               mappings in a view.  Even excluded mappings are returned."""
+            matches, wildcards = self.depot_side.match_wildcards(depot_path)
+            if not matches:
+                return ""
+            client_path = self.client_side.fill_in_wildcards(wildcards)
+            return client_path
+
+    #
+    # View methods
+    #
+    def __init__(self):
+        self.mappings = []
+
+    def append(self, view_line):
+        """Parse a view line, splitting it into depot and client
+           sides.  Append to self.mappings, preserving order."""
+
+        # Split the view line into exactly two words.  P4 enforces
+        # structure on these lines that simplifies this quite a bit.
+        #
+        # Either or both words may be double-quoted.
+        # Single quotes do not matter.
+        # Double-quote marks cannot occur inside the words.
+        # A + or - prefix is also inside the quotes.
+        # There are no quotes unless they contain a space.
+        # The line is already white-space stripped.
+        # The two words are separated by a single space.
+        #
+        if view_line[0] == '"':
+            # First word is double quoted.  Find its end.
+            close_quote_index = view_line.find('"', 1)
+            if close_quote_index <= 0:
+                die("No first-word closing quote found: %s" % view_line)
+            depot_side = view_line[1:close_quote_index]
+            # skip closing quote and space
+            rhs_index = close_quote_index + 1 + 1
+        else:
+            space_index = view_line.find(" ")
+            if space_index <= 0:
+                die("No word-splitting space found: %s" % view_line)
+            depot_side = view_line[0:space_index]
+            rhs_index = space_index + 1
+
+        if view_line[rhs_index] == '"':
+            # Second word is double quoted.  Make sure there is a
+            # double quote at the end too.
+            if not view_line.endswith('"'):
+                die("View line with rhs quote should end with one: %s" %
+                    view_line)
+            # skip the quotes
+            client_side = view_line[rhs_index+1:-1]
+        else:
+            client_side = view_line[rhs_index:]
+
+        # prefix + means overlay on previous mapping
+        overlay = False
+        if depot_side.startswith("+"):
+            overlay = True
+            depot_side = depot_side[1:]
+
+        # prefix - means exclude this path
+        exclude = False
+        if depot_side.startswith("-"):
+            exclude = True
+            depot_side = depot_side[1:]
+
+        m = View.Mapping(depot_side, client_side, overlay, exclude)
+        self.mappings.append(m)
+
+    def map_in_client(self, depot_path):
+        """Return the relative location in the client where this
+           depot file should live.  Returns "" if the file should
+           not be mapped in the client."""
+
+        paths_filled = []
+        client_path = ""
+
+        # look at later entries first
+        for m in self.mappings[::-1]:
+
+            # see where will this path end up in the client
+            p = m.map_depot_to_client(depot_path)
+
+            if p == "":
+                # Depot path does not belong in client.  Must remember
+                # this, as previous items should not cause files to
+                # exist in this path either.  Remember that the list is
+                # being walked from the end, which has higher precedence.
+                # Overlap mappings do not exclude previous mappings.
+                if not m.overlay:
+                    paths_filled.append(m.client_side)
+
+            else:
+                # This mapping matched; no need to search any further.
+                # But, the mapping could be rejected if the client path
+                # has already been claimed by an earlier mapping.
+                already_mapped_in_client = False
+                for f in paths_filled:
+                    # this is View.Path.match
+                    if f.match(p):
+                        already_mapped_in_client = True
+                        break
+                if not already_mapped_in_client:
+                    # Include this file, unless it is from a line that
+                    # explicitly said to exclude it.
+                    if not m.exclude:
+                        client_path = p
+
+                # a match, even if rejected, always stops the search
+                break
+
+        return client_path
+
 class P4Sync(Command, P4UserMap):
     delete_actions = ( "delete", "move/delete", "purge" )
 
@@ -1109,7 +1477,8 @@ class P4Sync(Command, P4UserMap):
         self.p4BranchesInGit = []
         self.cloneExclude = []
         self.useClientSpec = False
-        self.clientSpecDirs = []
+        self.useClientSpec_from_options = False
+        self.clientSpecDirs = None
 
         if gitConfig("git-p4.syncFromOrigin") == "false":
             self.syncWithOrigin = False
@@ -1160,20 +1529,7 @@ class P4Sync(Command, P4UserMap):
 
     def stripRepoPath(self, path, prefixes):
         if self.useClientSpec:
-
-            # if using the client spec, we use the output directory
-            # specified in the client.  For example, a view
-            #   //depot/foo/branch/... //client/branch/foo/...
-            # will end up putting all foo/branch files into
-            #  branch/foo/
-            for val in self.clientSpecDirs:
-                if path.startswith(val[0]):
-                    # replace the depot path with the client path
-                    path = path.replace(val[0], val[1][1])
-                    # now strip out the client (//client/...)
-                    path = re.sub("^(//[^/]+/)", '', path)
-                    # the rest is all path
-                    return path
+            return self.clientSpecDirs.map_in_client(path)
 
         if self.keepRepoPath:
             prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
@@ -1219,38 +1575,66 @@ class P4Sync(Command, P4UserMap):
     # - 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)
         relPath = self.wildcard_decode(relPath)
         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
+        (type_base, type_mods) = split_p4_type(file["type"])
+
+        git_mode = "100644"
+        if "x" in type_mods:
+            git_mode = "100755"
+        if type_base == "symlink":
+            git_mode = "120000"
+            # p4 print on a symlink contains "target\n"; remove the newline
             data = ''.join(contents)
             contents = [data[:-1]]
 
-        if self.isWindows and file["type"].endswith("text"):
+        if type_base == "utf16":
+            # p4 delivers different text in the python output to -G
+            # than it does when using "print -o", or normal p4 client
+            # operations.  utf16 is converted to ascii or utf8, perhaps.
+            # But ascii text saved as -t utf16 is completely mangled.
+            # Invoke print -o to get the real contents.
+            text = p4_read_pipe(['print', '-q', '-o', '-', file['depotFile']])
+            contents = [ text ]
+
+        if type_base == "apple":
+            # Apple filetype files will be streamed as a concatenation of
+            # its appledouble header and the contents.  This is useless
+            # on both macs and non-macs.  If using "print -q -o xx", it
+            # will create "xx" with the data, and "%xx" with the header.
+            # This is also not very useful.
+            #
+            # Ideally, someday, this script can learn how to generate
+            # appledouble files directly and import those to git, but
+            # non-mac machines can never find a use for apple filetype.
+            print "\nIgnoring apple filetype file %s" % file['depotFile']
+            return
+
+        # Perhaps windows wants unicode, utf16 newlines translated too;
+        # but this is not doing it.
+        if self.isWindows and type_base == "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)
+        # Note that we do not try to de-mangle keywords on utf16 files,
+        # even though in theory somebody may want that.
+        if type_base in ("text", "unicode", "binary"):
+            if "ko" in type_mods:
+                text = ''.join(contents)
+                text = re.sub(r'\$(Id|Header):[^$]*\$', r'$\1$', text)
+                contents = [ text ]
+            elif "k" in type_mods:
+                text = ''.join(contents)
+                text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$', r'$\1$', text)
+                contents = [ text ]
 
-        self.gitStream.write("M %s inline %s\n" % (mode, relPath))
+        self.gitStream.write("M %s inline %s\n" % (git_mode, relPath))
 
         # total length...
         length = 0
@@ -1295,19 +1679,17 @@ class P4Sync(Command, P4UserMap):
         filesToDelete = []
 
         for f in files:
-            includeFile = True
-            for val in self.clientSpecDirs:
-                if f['path'].startswith(val[0]):
-                    if val[1][0] <= 0:
-                        includeFile = False
-                    break
+            # if using a client spec, only add the files that have
+            # a path in the client
+            if self.clientSpecDirs:
+                if self.clientSpecDirs.map_in_client(f['path']) == "":
+                    continue
 
-            if includeFile:
-                filesForCommit.append(f)
-                if f['action'] in self.delete_actions:
-                    filesToDelete.append(f)
-                else:
-                    filesToRead.append(f)
+            filesForCommit.append(f)
+            if f['action'] in self.delete_actions:
+                filesToDelete.append(f)
+            else:
+                filesToRead.append(f)
 
         # deleted files...
         for f in filesToDelete:
@@ -1322,10 +1704,11 @@ class P4Sync(Command, P4UserMap):
             def streamP4FilesCbSelf(entry):
                 self.streamP4FilesCb(entry)
 
-            p4CmdList("-x - print",
-                '\n'.join(['%s#%s' % (f['path'], f['rev'])
-                                                  for f in filesToRead]),
-                cb=streamP4FilesCbSelf)
+            fileArgs = ['%s#%s' % (f['path'], f['rev']) for f in filesToRead]
+
+            p4CmdList(["-x", "-", "print"],
+                      stdin=fileArgs,
+                      cb=streamP4FilesCbSelf)
 
             # do the last chunk
             if self.stream_file.has_key('depotFile'):
@@ -1386,8 +1769,8 @@ class P4Sync(Command, P4UserMap):
             if self.verbose:
                 print "Change %s is labelled %s" % (change, labelDetails)
 
-            files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
-                                                    for p in branchPrefixes]))
+            files = p4CmdList(["files"] + ["%s...@%s" % (p, change)
+                                                    for p in branchPrefixes])
 
             if len(files) == len(labelRevisions):
 
@@ -1435,9 +1818,9 @@ class P4Sync(Command, P4UserMap):
             newestChange = 0
             if self.verbose:
                 print "Querying files for label %s" % label
-            for file in p4CmdList("files "
-                                  +  ' '.join (["%s...@%s" % (p, label)
-                                                for p in self.depotPaths])):
+            for file in p4CmdList(["files"] +
+                                      ["%s...@%s" % (p, label)
+                                          for p in self.depotPaths]):
                 revisions[file["depotFile"]] = file["rev"]
                 change = int(file["change"])
                 if change > newestChange:
@@ -1692,10 +2075,9 @@ class P4Sync(Command, P4UserMap):
         newestRevision = 0
 
         fileCnt = 0
-        for info in p4CmdList("files "
-                              +  ' '.join(["%s...%s"
-                                           % (p, revision)
-                                           for p in self.depotPaths])):
+        fileArgs = ["%s...%s" % (p,revision) for p in self.depotPaths]
+
+        for info in p4CmdList(["files"] + fileArgs):
 
             if 'code' in info and info['code'] == 'error':
                 sys.stderr.write("p4 returned an error: %s\n"
@@ -1745,52 +2127,6 @@ class P4Sync(Command, P4UserMap):
             print self.gitError.read()
 
 
-    def getClientSpec(self):
-        specList = p4CmdList( "client -o" )
-        temp = {}
-        for entry in specList:
-            for k,v in entry.iteritems():
-                if k.startswith("View"):
-
-                    # p4 has these %%1 to %%9 arguments in specs to
-                    # reorder paths; which we can't handle (yet :)
-                    if re.match('%%\d', v) != None:
-                        print "Sorry, can't handle %%n arguments in client specs"
-                        sys.exit(1)
-
-                    if v.startswith('"'):
-                        start = 1
-                    else:
-                        start = 0
-                    index = v.find("...")
-
-                    # save the "client view"; i.e the RHS of the view
-                    # line that tells the client where to put the
-                    # files for this view.
-                    cv = v[index+3:].strip() # +3 to remove previous '...'
-
-                    # if the client view doesn't end with a
-                    # ... wildcard, then we're going to mess up the
-                    # output directory, so fail gracefully.
-                    if not cv.endswith('...'):
-                        print 'Sorry, client view in "%s" needs to end with wildcard' % (k)
-                        sys.exit(1)
-                    cv=cv[:-3]
-
-                    # now save the view; +index means included, -index
-                    # means it should be filtered out.
-                    v = v[start:index]
-                    if v.startswith("-"):
-                        v = v[1:]
-                        include = -len(v)
-                    else:
-                        include = len(v)
-
-                    temp[v] = (include, cv)
-
-        self.clientSpecDirs = temp.items()
-        self.clientSpecDirs.sort( lambda x, y: abs( y[1][0] ) - abs( x[1][0] ) )
-
     def run(self, args):
         self.depotPaths = []
         self.changeRange = ""
@@ -1823,8 +2159,15 @@ class P4Sync(Command, P4UserMap):
             if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
                 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
 
-        if self.useClientSpec or gitConfig("git-p4.useclientspec") == "true":
-            self.getClientSpec()
+        # accept either the command-line option, or the configuration variable
+        if self.useClientSpec:
+            # will use this after clone to set the variable
+            self.useClientSpec_from_options = True
+        else:
+            if gitConfig("git-p4.useclientspec", "--bool") == "true":
+                self.useClientSpec = True
+        if self.useClientSpec:
+            self.clientSpecDirs = getClientSpec()
 
         # TODO: should always look at previous commits,
         # merge with previous imports, if possible.
@@ -1896,6 +2239,17 @@ class P4Sync(Command, P4UserMap):
         revision = ""
         self.users = {}
 
+        # Make sure no revision specifiers are used when --changesfile
+        # is specified.
+        bad_changesfile = False
+        if len(self.changesFile) > 0:
+            for p in self.depotPaths:
+                if p.find("@") >= 0 or p.find("#") >= 0:
+                    bad_changesfile = True
+                    break
+        if bad_changesfile:
+            die("Option --changesfile is incompatible with revision specifiers")
+
         newPaths = []
         for p in self.depotPaths:
             if p.find("@") != -1:
@@ -1912,7 +2266,10 @@ class P4Sync(Command, P4UserMap):
                 revision = p[hashIdx:]
                 p = p[:hashIdx]
             elif self.previousDepotPaths == []:
-                revision = "#head"
+                # pay attention to changesfile, if given, else import
+                # the entire p4 tree at the head revision
+                if len(self.changesFile) == 0:
+                    revision = "#head"
 
             p = re.sub ("\.\.\.$", "", p)
             if not p.endswith("/"):
@@ -2125,6 +2482,10 @@ class P4Clone(P4Sync):
             else:
                 print "Could not detect main branch. No checkout/master branch created."
 
+        # auto-set this variable if invoked with --use-client-spec
+        if self.useClientSpec_from_options:
+            system("git config --bool git-p4.useclientspec true")
+
         return True
 
 class P4Branches(Command):
@@ -2207,7 +2568,8 @@ def main():
     args = sys.argv[2:]
 
     if len(options) > 0:
-        options.append(optparse.make_option("--git-dir", dest="gitdir"))
+        if cmd.needsGit:
+            options.append(optparse.make_option("--git-dir", dest="gitdir"))
 
         parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
                                        options,
@@ -2237,6 +2599,7 @@ def main():
 
     if not cmd.run(args):
         parser.print_help()
+        sys.exit(2)
 
 
 if __name__ == '__main__':
diff --git a/contrib/fast-import/git-p4.txt b/contrib/fast-import/git-p4.txt
deleted file mode 100644 (file)
index 52003ae..0000000
+++ /dev/null
@@ -1,289 +0,0 @@
-git-p4 - Perforce <-> Git converter using git-fast-import
-
-Usage
-=====
-
-git-p4 can be used in two different ways:
-
-1) To import changes from Perforce to a Git repository, using "git-p4 sync".
-
-2) To submit changes from Git back to Perforce, using "git-p4 submit".
-
-Importing
-=========
-
-Simply start with
-
-  git-p4 clone //depot/path/project
-
-or
-
-  git-p4 clone //depot/path/project myproject
-
-This will:
-
-1) Create an empty git repository in a subdirectory called "project" (or
-"myproject" with the second command)
-
-2) Import the head revision from the given Perforce path into a git branch
-called "p4" (remotes/p4 actually)
-
-3) Create a master branch based on it and check it out.
-
-If you want the entire history (not just the head revision) then you can simply
-append a "@all" to the depot path:
-
-  git-p4 clone //depot/project/main@all myproject
-
-
-
-If you want more control you can also use the git-p4 sync command directly:
-
-  mkdir repo-git
-  cd repo-git
-  git init
-  git-p4 sync //path/in/your/perforce/depot
-
-This will import the current head revision of the specified depot path into a
-"remotes/p4/master" branch of your git repository. You can use the
---branch=mybranch option to import into a different branch.
-
-If you want to import the entire history of a given depot path simply use:
-
-  git-p4 sync //path/in/depot@all
-
-
-Note:
-
-To achieve optimal compression you may want to run 'git repack -a -d -f' after
-a big import. This may take a while.
-
-Incremental Imports
-===================
-
-After an initial import you can continue to synchronize your git repository
-with newer changes from the Perforce depot by just calling
-
-  git-p4 sync
-
-in your git repository. By default the "remotes/p4/master" branch is updated.
-
-Advanced Setup
-==============
-
-Suppose you have a periodically updated git repository somewhere, containing a
-complete import of a Perforce project. This repository can be cloned and used
-with git-p4. When updating the cloned repository with the "sync" command,
-git-p4 will try to fetch changes from the original repository first. The git
-protocol used with this is usually faster than importing from Perforce
-directly.
-
-This behaviour can be disabled by setting the "git-p4.syncFromOrigin" git
-configuration variable to "false".
-
-Updating
-========
-
-A common working pattern is to fetch the latest changes from the Perforce depot
-and merge them with local uncommitted changes. The recommended way is to use
-git's rebase mechanism to preserve linear history. git-p4 provides a convenient
-
-  git-p4 rebase
-
-command that calls git-p4 sync followed by git rebase to rebase the current
-working branch.
-
-Submitting
-==========
-
-git-p4 has support for submitting changes from a git repository back to the
-Perforce depot. This requires a Perforce checkout separate from your git
-repository. To submit all changes that are in the current git branch but not in
-the "p4" branch (or "origin" if "p4" doesn't exist) simply call
-
-    git-p4 submit
-
-in your git repository. If you want to submit changes in a specific branch that
-is not your current git branch you can also pass that as an argument:
-
-    git-p4 submit mytopicbranch
-
-You can override the reference branch with the --origin=mysourcebranch option.
-
-The Perforce changelists will be created with the user who ran git-p4. If you
-use --preserve-user then git-p4 will attempt to create Perforce changelists
-with the Perforce user corresponding to the git commit author. You need to
-have sufficient permissions within Perforce, and the git users need to have
-Perforce accounts. Permissions can be granted using 'p4 protect'.
-
-If a submit fails you may have to "p4 resolve" and submit manually. You can
-continue importing the remaining changes with
-
-  git-p4 submit --continue
-
-Example
-=======
-
-# Clone a repository
-  git-p4 clone //depot/path/project
-# Enter the newly cloned directory
-  cd project
-# Do some work...
-  vi foo.h
-# ... and commit locally to gi
-  git commit foo.h
-# In the meantime somebody submitted changes to the Perforce depot. Rebase your latest
-# changes against the latest changes in Perforce:
-  git-p4 rebase
-# Submit your locally committed changes back to Perforce
-  git-p4 submit
-# ... and synchronize with Perforce
-  git-p4 rebase
-
-
-Configuration parameters
-========================
-
-git-p4.user ($P4USER)
-
-Allows you to specify the username to use to connect to the Perforce repository.
-
-  git config [--global] git-p4.user public
-
-git-p4.password ($P4PASS)
-
-Allows you to specify the password to use to connect to the Perforce repository.
-Warning this password will be visible on the command-line invocation of the p4 binary.
-
-  git config [--global] git-p4.password public1234
-
-git-p4.port ($P4PORT)
-
-Specify the port to be used to contact the Perforce server. As this will be passed
-directly to the p4 binary, it may be in the format host:port as well.
-
-  git config [--global] git-p4.port codes.zimbra.com:2666
-
-git-p4.host ($P4HOST)
-
-Specify the host to contact for a Perforce repository.
-
-  git config [--global] git-p4.host perforce.example.com
-
-git-p4.client ($P4CLIENT)
-
-Specify the client name to use
-
-  git config [--global] git-p4.client public-view
-
-git-p4.allowSubmit
-
-  git config [--global] git-p4.allowSubmit false
-
-git-p4.syncFromOrigin
-
-A useful setup may be that you have a periodically updated git repository
-somewhere that contains a complete import of a Perforce project. That git
-repository can be used to clone the working repository from and one would
-import from Perforce directly after cloning using git-p4. If the connection to
-the Perforce server is slow and the working repository hasn't been synced for a
-while it may be desirable to fetch changes from the origin git repository using
-the efficient git protocol. git-p4 supports this setup by calling "git fetch origin"
-by default if there is an origin branch. You can disable this using:
-
-  git config [--global] git-p4.syncFromOrigin false
-
-git-p4.useclientspec
-
-  git config [--global] git-p4.useclientspec false
-
-The P4CLIENT environment variable should be correctly set for p4 to be
-able to find the relevant client.  This client spec will be used to
-both filter the files cloned by git and set the directory layout as
-specified in the client (this implies --keep-path style semantics).
-
-git-p4.skipSubmitModTimeCheck
-
-  git config [--global] git-p4.skipSubmitModTimeCheck false
-
-If true, submit will not check if the p4 change template has been modified.
-
-git-p4.preserveUser
-
-  git config [--global] git-p4.preserveUser false
-
-If true, attempt to preserve user names by modifying the p4 changelists. See
-the "--preserve-user" submit option.
-
-git-p4.allowMissingPerforceUsers
-
-  git config [--global] git-p4.allowMissingP4Users false
-
-If git-p4 is setting the perforce user for a commit (--preserve-user) then
-if there is no perforce user corresponding to the git author, git-p4 will
-stop. With allowMissingPerforceUsers set to true, git-p4 will use the
-current user (i.e. the behavior without --preserve-user) and carry on with
-the perforce commit.
-
-git-p4.skipUserNameCheck
-
-  git config [--global] git-p4.skipUserNameCheck false
-
-When submitting, git-p4 checks that the git commits are authored by the current
-p4 user, and warns if they are not. This disables the check.
-
-git-p4.detectRenames
-
-Detect renames when submitting changes to Perforce server. Will enable -M git
-argument. Can be optionally set to a number representing the threshold
-percentage value of the rename detection.
-
-  git config [--global] git-p4.detectRenames true
-  git config [--global] git-p4.detectRenames 50
-
-git-p4.detectCopies
-
-Detect copies when submitting changes to Perforce server. Will enable -C git
-argument. Can be optionally set to a number representing the threshold
-percentage value of the copy detection.
-
-  git config [--global] git-p4.detectCopies true
-  git config [--global] git-p4.detectCopies 80
-
-git-p4.detectCopiesHarder
-
-Detect copies even between files that did not change when submitting changes to
-Perforce server. Will enable --find-copies-harder git argument.
-
-  git config [--global] git-p4.detectCopies true
-
-git-p4.branchUser
-
-Only use branch specifications defined by the selected username.
-
-  git config [--global] git-p4.branchUser username
-
-git-p4.branchList
-
-List of branches to be imported when branch detection is enabled.
-
-  git config [--global] git-p4.branchList main:branchA
-  git config [--global] --add git-p4.branchList main:branchB
-
-Implementation Details...
-=========================
-
-* Changesets from Perforce are imported using git fast-import.
-* The import does not require anything from the Perforce client view as it just uses
-  "p4 print //depot/path/file#revision" to get the actual file contents.
-* Every imported changeset has a special [git-p4...] line at the
-  end of the log message that gives information about the corresponding
-  Perforce change number and is also used by git-p4 itself to find out
-  where to continue importing when doing incremental imports.
-  Basically when syncing it extracts the perforce change number of the
-  latest commit in the "p4" branch and uses "p4 changes //depot/path/...@changenum,#head"
-  to find out which changes need to be imported.
-* git-p4 submit uses "git rev-list" to pick the commits between the "p4" branch
-  and the current branch.
-  The commits themselves are applied using git diff/format-patch ... | git apply
-
diff --git a/contrib/git-jump/README b/contrib/git-jump/README
new file mode 100644 (file)
index 0000000..1cebc32
--- /dev/null
@@ -0,0 +1,92 @@
+git-jump
+========
+
+Git-jump is a script for helping you jump to "interesting" parts of your
+project in your editor. It works by outputting a set of interesting
+spots in the "quickfix" format, which editors like vim can use as a
+queue of places to visit (this feature is usually used to jump to errors
+produced by a compiler). For example, given a diff like this:
+
+------------------------------------
+diff --git a/foo.c b/foo.c
+index a655540..5a59044 100644
+--- a/foo.c
++++ b/foo.c
+@@ -1,3 +1,3 @@
+ int main(void) {
+-  printf("hello word!\n");
++  printf("hello world!\n");
+ }
+-----------------------------------
+
+git-jump will feed this to the editor:
+
+-----------------------------------
+foo.c:2: printf("hello word!\n");
+-----------------------------------
+
+Obviously this trivial case isn't that interesting; you could just open
+`foo.c` yourself. But when you have many changes scattered across a
+project, you can use the editor's support to "jump" from point to point.
+
+Git-jump can generate three types of interesting lists:
+
+  1. The beginning of any diff hunks.
+
+  2. The beginning of any merge conflict markers.
+
+  3. Any grep matches.
+
+
+Using git-jump
+--------------
+
+To use it, just drop git-jump in your PATH, and then invoke it like
+this:
+
+--------------------------------------------------
+# jump to changes not yet staged for commit
+git jump diff
+
+# jump to changes that are staged for commit; you can give
+# arbitrary diff options
+git jump diff --cached
+
+# jump to merge conflicts
+git jump merge
+
+# jump to all instances of foo_bar
+git jump grep foo_bar
+
+# same as above, but case-insensitive; you can give
+# arbitrary grep options
+git jump grep -i foo_bar
+--------------------------------------------------
+
+
+Related Programs
+----------------
+
+You can accomplish some of the same things with individual tools. For
+example, you can use `git mergetool` to start vimdiff on each unmerged
+file. `git jump merge` is for the vim-wielding luddite who just wants to
+jump straight to the conflict text with no fanfare.
+
+As of git v1.7.2, `git grep` knows the `--open-files-in-pager` option,
+which does something similar to `git jump grep`. However, it is limited
+to positioning the cursor to the correct line in only the first file,
+leaving you to locate subsequent hits in that file or other files using
+the editor or pager. By contrast, git-jump provides the editor with a
+complete list of files and line numbers for each match.
+
+
+Limitations
+-----------
+
+This scripts was written and tested with vim. Given that the quickfix
+format is the same as what gcc produces, I expect emacs users have a
+similar feature for iterating through the list, but I know nothing about
+how to activate it.
+
+The shell snippets to generate the quickfix lines will almost certainly
+choke on filenames with exotic characters (like newlines).
diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump
new file mode 100755 (executable)
index 0000000..a33674e
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+usage() {
+       cat <<\EOF
+usage: git jump <mode> [<args>]
+
+Jump to interesting elements in an editor.
+The <mode> parameter is one of:
+
+diff: elements are diff hunks. Arguments are given to diff.
+
+merge: elements are merge conflicts. Arguments are ignored.
+
+grep: elements are grep hits. Arguments are given to grep.
+EOF
+}
+
+open_editor() {
+       editor=`git var GIT_EDITOR`
+       eval "$editor -q \$1"
+}
+
+mode_diff() {
+       git diff --relative "$@" |
+       perl -ne '
+       if (m{^\+\+\+ b/(.*)}) { $file = $1; next }
+       defined($file) or next;
+       if (m/^@@ .*\+(\d+)/) { $line = $1; next }
+       defined($line) or next;
+       if (/^ /) { $line++; next }
+       if (/^[-+]\s*(.*)/) {
+               print "$file:$line: $1\n";
+               $line = undef;
+       }
+       '
+}
+
+mode_merge() {
+       git ls-files -u |
+       perl -pe 's/^.*?\t//' |
+       sort -u |
+       while IFS= read fn; do
+               grep -Hn '^<<<<<<<' "$fn"
+       done
+}
+
+# Grep -n generates nice quickfix-looking lines by itself,
+# but let's clean up extra whitespace, so they look better if the
+# editor shows them to us in the status bar.
+mode_grep() {
+       git grep -n "$@" |
+       perl -pe '
+       s/[ \t]+/ /g;
+       s/^ *//;
+       '
+}
+
+if test $# -lt 1; then
+       usage >&2
+       exit 1
+fi
+mode=$1; shift
+
+trap 'rm -f "$tmp"' 0 1 2 3 15
+tmp=`mktemp -t git-jump.XXXXXX` || exit 1
+type "mode_$mode" >/dev/null 2>&1 || { usage >&2; exit 1; }
+"mode_$mode" "$@" >"$tmp"
+test -s "$tmp" || exit 0
+open_editor "$tmp"
index ba077c13f96779d5b29237e83974b0910b4bbdde..01af9df15e180288723caadf42b500fe78a8c694 100755 (executable)
@@ -85,7 +85,6 @@ prep_for_email()
        oldrev=$(git rev-parse $1)
        newrev=$(git rev-parse $2)
        refname="$3"
-       maxlines=$4
 
        # --- Interpret
        # 0000->1234 (create)
@@ -461,7 +460,7 @@ generate_delete_branch_email()
 {
        echo "       was  $oldrev"
        echo ""
-       echo $LOGEND
+       echo $LOGBEGIN
        git show -s --pretty=oneline $oldrev
        echo $LOGEND
 }
@@ -561,7 +560,7 @@ generate_delete_atag_email()
 {
        echo "       was  $oldrev"
        echo ""
-       echo $LOGEND
+       echo $LOGBEGIN
        git show -s --pretty=oneline $oldrev
        echo $LOGEND
 }
@@ -626,7 +625,7 @@ generate_delete_general_email()
 {
        echo "       was  $oldrev"
        echo ""
-       echo $LOGEND
+       echo $LOGBEGIN
        git show -s --pretty=oneline $oldrev
        echo $LOGEND
 }
diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki
new file mode 100755 (executable)
index 0000000..c18bfa1
--- /dev/null
@@ -0,0 +1,827 @@
+#! /usr/bin/perl
+
+# Copyright (C) 2011
+#     Jérémie Nikaes <jeremie.nikaes@ensimag.imag.fr>
+#     Arnaud Lacurie <arnaud.lacurie@ensimag.imag.fr>
+#     Claire Fousse <claire.fousse@ensimag.imag.fr>
+#     David Amouyal <david.amouyal@ensimag.imag.fr>
+#     Matthieu Moy <matthieu.moy@grenoble-inp.fr>
+# License: GPL v2 or later
+
+# Gateway between Git and MediaWiki.
+#   https://github.com/Bibzball/Git-Mediawiki/wiki
+#
+# Known limitations:
+#
+# - Only wiki pages are managed, no support for [[File:...]]
+#   attachments.
+#
+# - Poor performance in the best case: it takes forever to check
+#   whether we're up-to-date (on fetch or push) or to fetch a few
+#   revisions from a large wiki, because we use exclusively a
+#   page-based synchronization. We could switch to a wiki-wide
+#   synchronization when the synchronization involves few revisions
+#   but the wiki is large.
+#
+# - Git renames could be turned into MediaWiki renames (see TODO
+#   below)
+#
+# - login/password support requires the user to write the password
+#   cleartext in a file (see TODO below).
+#
+# - No way to import "one page, and all pages included in it"
+#
+# - Multiple remote MediaWikis have not been very well tested.
+
+use strict;
+use MediaWiki::API;
+use DateTime::Format::ISO8601;
+use encoding 'utf8';
+
+# use encoding 'utf8' doesn't change STDERROR
+# but we're going to output UTF-8 filenames to STDERR
+binmode STDERR, ":utf8";
+
+use URI::Escape;
+use warnings;
+
+# Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced
+use constant SLASH_REPLACEMENT => "%2F";
+
+# It's not always possible to delete pages (may require some
+# priviledges). Deleted pages are replaced with this content.
+use constant DELETED_CONTENT => "[[Category:Deleted]]\n";
+
+# It's not possible to create empty pages. New empty files in Git are
+# sent with this content instead.
+use constant EMPTY_CONTENT => "<!-- empty page -->\n";
+
+# used to reflect file creation or deletion in diff.
+use constant NULL_SHA1 => "0000000000000000000000000000000000000000";
+
+my $remotename = $ARGV[0];
+my $url = $ARGV[1];
+
+# Accept both space-separated and multiple keys in config file.
+# Spaces should be written as _ anyway because we'll use chomp.
+my @tracked_pages = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".pages"));
+chomp(@tracked_pages);
+
+# Just like @tracked_pages, but for MediaWiki categories.
+my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".categories"));
+chomp(@tracked_categories);
+
+my $wiki_login = run_git("config --get remote.". $remotename .".mwLogin");
+# TODO: ideally, this should be able to read from keyboard, but we're
+# inside a remote helper, so our stdin is connect to git, not to a
+# terminal.
+my $wiki_passwd = run_git("config --get remote.". $remotename .".mwPassword");
+my $wiki_domain = run_git("config --get remote.". $remotename .".mwDomain");
+chomp($wiki_login);
+chomp($wiki_passwd);
+chomp($wiki_domain);
+
+# Import only last revisions (both for clone and fetch)
+my $shallow_import = run_git("config --get --bool remote.". $remotename .".shallow");
+chomp($shallow_import);
+$shallow_import = ($shallow_import eq "true");
+
+# Dumb push: don't update notes and mediawiki ref to reflect the last push.
+#
+# Configurable with mediawiki.dumbPush, or per-remote with
+# remote.<remotename>.dumbPush.
+#
+# This means the user will have to re-import the just-pushed
+# revisions. On the other hand, this means that the Git revisions
+# corresponding to MediaWiki revisions are all imported from the wiki,
+# regardless of whether they were initially created in Git or from the
+# web interface, hence all users will get the same history (i.e. if
+# the push from Git to MediaWiki loses some information, everybody
+# will get the history with information lost). If the import is
+# deterministic, this means everybody gets the same sha1 for each
+# MediaWiki revision.
+my $dumb_push = run_git("config --get --bool remote.$remotename.dumbPush");
+unless ($dumb_push) {
+       $dumb_push = run_git("config --get --bool mediawiki.dumbPush");
+}
+chomp($dumb_push);
+$dumb_push = ($dumb_push eq "true");
+
+my $wiki_name = $url;
+$wiki_name =~ s/[^\/]*:\/\///;
+# If URL is like http://user:password@example.com/, we clearly don't
+# want the password in $wiki_name. While we're there, also remove user
+# and '@' sign, to avoid author like MWUser@HTTPUser@host.com
+$wiki_name =~ s/^.*@//;
+
+# Commands parser
+my $entry;
+my @cmd;
+while (<STDIN>) {
+       chomp;
+       @cmd = split(/ /);
+       if (defined($cmd[0])) {
+               # Line not blank
+               if ($cmd[0] eq "capabilities") {
+                       die("Too many arguments for capabilities") unless (!defined($cmd[1]));
+                       mw_capabilities();
+               } elsif ($cmd[0] eq "list") {
+                       die("Too many arguments for list") unless (!defined($cmd[2]));
+                       mw_list($cmd[1]);
+               } elsif ($cmd[0] eq "import") {
+                       die("Invalid arguments for import") unless ($cmd[1] ne "" && !defined($cmd[2]));
+                       mw_import($cmd[1]);
+               } elsif ($cmd[0] eq "option") {
+                       die("Too many arguments for option") unless ($cmd[1] ne "" && $cmd[2] ne "" && !defined($cmd[3]));
+                       mw_option($cmd[1],$cmd[2]);
+               } elsif ($cmd[0] eq "push") {
+                       mw_push($cmd[1]);
+               } else {
+                       print STDERR "Unknown command. Aborting...\n";
+                       last;
+               }
+       } else {
+               # blank line: we should terminate
+               last;
+       }
+
+       BEGIN { $| = 1 } # flush STDOUT, to make sure the previous
+                        # command is fully processed.
+}
+
+########################## Functions ##############################
+
+# MediaWiki API instance, created lazily.
+my $mediawiki;
+
+sub mw_connect_maybe {
+       if ($mediawiki) {
+           return;
+       }
+       $mediawiki = MediaWiki::API->new;
+       $mediawiki->{config}->{api_url} = "$url/api.php";
+       if ($wiki_login) {
+               if (!$mediawiki->login({
+                       lgname => $wiki_login,
+                       lgpassword => $wiki_passwd,
+                       lgdomain => $wiki_domain,
+               })) {
+                       print STDERR "Failed to log in mediawiki user \"$wiki_login\" on $url\n";
+                       print STDERR "(error " .
+                           $mediawiki->{error}->{code} . ': ' .
+                           $mediawiki->{error}->{details} . ")\n";
+                       exit 1;
+               } else {
+                       print STDERR "Logged in with user \"$wiki_login\".\n";
+               }
+       }
+}
+
+sub get_mw_first_pages {
+       my $some_pages = shift;
+       my @some_pages = @{$some_pages};
+
+       my $pages = shift;
+
+       # pattern 'page1|page2|...' required by the API
+       my $titles = join('|', @some_pages);
+
+       my $mw_pages = $mediawiki->api({
+               action => 'query',
+               titles => $titles,
+       });
+       if (!defined($mw_pages)) {
+               print STDERR "fatal: could not query the list of wiki pages.\n";
+               print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+               print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
+               exit 1;
+       }
+       while (my ($id, $page) = each(%{$mw_pages->{query}->{pages}})) {
+               if ($id < 0) {
+                       print STDERR "Warning: page $page->{title} not found on wiki\n";
+               } else {
+                       $pages->{$page->{title}} = $page;
+               }
+       }
+}
+
+sub get_mw_pages {
+       mw_connect_maybe();
+
+       my %pages; # hash on page titles to avoid duplicates
+       my $user_defined;
+       if (@tracked_pages) {
+               $user_defined = 1;
+               # The user provided a list of pages titles, but we
+               # still need to query the API to get the page IDs.
+
+               my @some_pages = @tracked_pages;
+               while (@some_pages) {
+                       my $last = 50;
+                       if ($#some_pages < $last) {
+                               $last = $#some_pages;
+                       }
+                       my @slice = @some_pages[0..$last];
+                       get_mw_first_pages(\@slice, \%pages);
+                       @some_pages = @some_pages[51..$#some_pages];
+               }
+       }
+       if (@tracked_categories) {
+               $user_defined = 1;
+               foreach my $category (@tracked_categories) {
+                       if (index($category, ':') < 0) {
+                               # Mediawiki requires the Category
+                               # prefix, but let's not force the user
+                               # to specify it.
+                               $category = "Category:" . $category;
+                       }
+                       my $mw_pages = $mediawiki->list( {
+                               action => 'query',
+                               list => 'categorymembers',
+                               cmtitle => $category,
+                               cmlimit => 'max' } )
+                           || die $mediawiki->{error}->{code} . ': ' . $mediawiki->{error}->{details};
+                       foreach my $page (@{$mw_pages}) {
+                               $pages{$page->{title}} = $page;
+                       }
+               }
+       }
+       if (!$user_defined) {
+               # No user-provided list, get the list of pages from
+               # the API.
+               my $mw_pages = $mediawiki->list({
+                       action => 'query',
+                       list => 'allpages',
+                       aplimit => 500,
+               });
+               if (!defined($mw_pages)) {
+                       print STDERR "fatal: could not get the list of wiki pages.\n";
+                       print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+                       print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
+                       exit 1;
+               }
+               foreach my $page (@{$mw_pages}) {
+                       $pages{$page->{title}} = $page;
+               }
+       }
+       return values(%pages);
+}
+
+sub run_git {
+       open(my $git, "-|:encoding(UTF-8)", "git " . $_[0]);
+       my $res = do { local $/; <$git> };
+       close($git);
+
+       return $res;
+}
+
+
+sub get_last_local_revision {
+       # Get note regarding last mediawiki revision
+       my $note = run_git("notes --ref=$remotename/mediawiki show refs/mediawiki/$remotename/master 2>/dev/null");
+       my @note_info = split(/ /, $note);
+
+       my $lastrevision_number;
+       if (!(defined($note_info[0]) && $note_info[0] eq "mediawiki_revision:")) {
+               print STDERR "No previous mediawiki revision found";
+               $lastrevision_number = 0;
+       } else {
+               # Notes are formatted : mediawiki_revision: #number
+               $lastrevision_number = $note_info[1];
+               chomp($lastrevision_number);
+               print STDERR "Last local mediawiki revision found is $lastrevision_number";
+       }
+       return $lastrevision_number;
+}
+
+# Remember the timestamp corresponding to a revision id.
+my %basetimestamps;
+
+sub get_last_remote_revision {
+       mw_connect_maybe();
+
+       my @pages = get_mw_pages();
+
+       my $max_rev_num = 0;
+
+       foreach my $page (@pages) {
+               my $id = $page->{pageid};
+
+               my $query = {
+                       action => 'query',
+                       prop => 'revisions',
+                       rvprop => 'ids|timestamp',
+                       pageids => $id,
+               };
+
+               my $result = $mediawiki->api($query);
+
+               my $lastrev = pop(@{$result->{query}->{pages}->{$id}->{revisions}});
+
+               $basetimestamps{$lastrev->{revid}} = $lastrev->{timestamp};
+
+               $max_rev_num = ($lastrev->{revid} > $max_rev_num ? $lastrev->{revid} : $max_rev_num);
+       }
+
+       print STDERR "Last remote revision found is $max_rev_num.\n";
+       return $max_rev_num;
+}
+
+# Clean content before sending it to MediaWiki
+sub mediawiki_clean {
+       my $string = shift;
+       my $page_created = shift;
+       # Mediawiki does not allow blank space at the end of a page and ends with a single \n.
+       # This function right trims a string and adds a \n at the end to follow this rule
+       $string =~ s/\s+$//;
+       if ($string eq "" && $page_created) {
+               # Creating empty pages is forbidden.
+               $string = EMPTY_CONTENT;
+       }
+       return $string."\n";
+}
+
+# Filter applied on MediaWiki data before adding them to Git
+sub mediawiki_smudge {
+       my $string = shift;
+       if ($string eq EMPTY_CONTENT) {
+               $string = "";
+       }
+       # This \n is important. This is due to mediawiki's way to handle end of files.
+       return $string."\n";
+}
+
+sub mediawiki_clean_filename {
+       my $filename = shift;
+       $filename =~ s/@{[SLASH_REPLACEMENT]}/\//g;
+       # [, ], |, {, and } are forbidden by MediaWiki, even URL-encoded.
+       # Do a variant of URL-encoding, i.e. looks like URL-encoding,
+       # but with _ added to prevent MediaWiki from thinking this is
+       # an actual special character.
+       $filename =~ s/[\[\]\{\}\|]/sprintf("_%%_%x", ord($&))/ge;
+       # If we use the uri escape before
+       # we should unescape here, before anything
+
+       return $filename;
+}
+
+sub mediawiki_smudge_filename {
+       my $filename = shift;
+       $filename =~ s/\//@{[SLASH_REPLACEMENT]}/g;
+       $filename =~ s/ /_/g;
+       # Decode forbidden characters encoded in mediawiki_clean_filename
+       $filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf("%c", hex($1))/ge;
+       return $filename;
+}
+
+sub literal_data {
+       my ($content) = @_;
+       print STDOUT "data ", bytes::length($content), "\n", $content;
+}
+
+sub mw_capabilities {
+       # Revisions are imported to the private namespace
+       # refs/mediawiki/$remotename/ by the helper and fetched into
+       # refs/remotes/$remotename later by fetch.
+       print STDOUT "refspec refs/heads/*:refs/mediawiki/$remotename/*\n";
+       print STDOUT "import\n";
+       print STDOUT "list\n";
+       print STDOUT "push\n";
+       print STDOUT "\n";
+}
+
+sub mw_list {
+       # MediaWiki do not have branches, we consider one branch arbitrarily
+       # called master, and HEAD pointing to it.
+       print STDOUT "? refs/heads/master\n";
+       print STDOUT "\@refs/heads/master HEAD\n";
+       print STDOUT "\n";
+}
+
+sub mw_option {
+       print STDERR "remote-helper command 'option $_[0]' not yet implemented\n";
+       print STDOUT "unsupported\n";
+}
+
+sub fetch_mw_revisions_for_page {
+       my $page = shift;
+       my $id = shift;
+       my $fetch_from = shift;
+       my @page_revs = ();
+       my $query = {
+               action => 'query',
+               prop => 'revisions',
+               rvprop => 'ids',
+               rvdir => 'newer',
+               rvstartid => $fetch_from,
+               rvlimit => 500,
+               pageids => $id,
+       };
+
+       my $revnum = 0;
+       # Get 500 revisions at a time due to the mediawiki api limit
+       while (1) {
+               my $result = $mediawiki->api($query);
+
+               # Parse each of those 500 revisions
+               foreach my $revision (@{$result->{query}->{pages}->{$id}->{revisions}}) {
+                       my $page_rev_ids;
+                       $page_rev_ids->{pageid} = $page->{pageid};
+                       $page_rev_ids->{revid} = $revision->{revid};
+                       push(@page_revs, $page_rev_ids);
+                       $revnum++;
+               }
+               last unless $result->{'query-continue'};
+               $query->{rvstartid} = $result->{'query-continue'}->{revisions}->{rvstartid};
+       }
+       if ($shallow_import && @page_revs) {
+               print STDERR "  Found 1 revision (shallow import).\n";
+               @page_revs = sort {$b->{revid} <=> $a->{revid}} (@page_revs);
+               return $page_revs[0];
+       }
+       print STDERR "  Found ", $revnum, " revision(s).\n";
+       return @page_revs;
+}
+
+sub fetch_mw_revisions {
+       my $pages = shift; my @pages = @{$pages};
+       my $fetch_from = shift;
+
+       my @revisions = ();
+       my $n = 1;
+       foreach my $page (@pages) {
+               my $id = $page->{pageid};
+
+               print STDERR "page $n/", scalar(@pages), ": ". $page->{title} ."\n";
+               $n++;
+               my @page_revs = fetch_mw_revisions_for_page($page, $id, $fetch_from);
+               @revisions = (@page_revs, @revisions);
+       }
+
+       return ($n, @revisions);
+}
+
+sub import_file_revision {
+       my $commit = shift;
+       my %commit = %{$commit};
+       my $full_import = shift;
+       my $n = shift;
+
+       my $title = $commit{title};
+       my $comment = $commit{comment};
+       my $content = $commit{content};
+       my $author = $commit{author};
+       my $date = $commit{date};
+
+       print STDOUT "commit refs/mediawiki/$remotename/master\n";
+       print STDOUT "mark :$n\n";
+       print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n";
+       literal_data($comment);
+
+       # If it's not a clone, we need to know where to start from
+       if (!$full_import && $n == 1) {
+               print STDOUT "from refs/mediawiki/$remotename/master^0\n";
+       }
+       if ($content ne DELETED_CONTENT) {
+               print STDOUT "M 644 inline $title.mw\n";
+               literal_data($content);
+               print STDOUT "\n\n";
+       } else {
+               print STDOUT "D $title.mw\n";
+       }
+
+       # mediawiki revision number in the git note
+       if ($full_import && $n == 1) {
+               print STDOUT "reset refs/notes/$remotename/mediawiki\n";
+       }
+       print STDOUT "commit refs/notes/$remotename/mediawiki\n";
+       print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n";
+       literal_data("Note added by git-mediawiki during import");
+       if (!$full_import && $n == 1) {
+               print STDOUT "from refs/notes/$remotename/mediawiki^0\n";
+       }
+       print STDOUT "N inline :$n\n";
+       literal_data("mediawiki_revision: " . $commit{mw_revision});
+       print STDOUT "\n\n";
+}
+
+# parse a sequence of
+# <cmd> <arg1>
+# <cmd> <arg2>
+# \n
+# (like batch sequence of import and sequence of push statements)
+sub get_more_refs {
+       my $cmd = shift;
+       my @refs;
+       while (1) {
+               my $line = <STDIN>;
+               if ($line =~ m/^$cmd (.*)$/) {
+                       push(@refs, $1);
+               } elsif ($line eq "\n") {
+                       return @refs;
+               } else {
+                       die("Invalid command in a '$cmd' batch: ". $_);
+               }
+       }
+}
+
+sub mw_import {
+       # multiple import commands can follow each other.
+       my @refs = (shift, get_more_refs("import"));
+       foreach my $ref (@refs) {
+               mw_import_ref($ref);
+       }
+       print STDOUT "done\n";
+}
+
+sub mw_import_ref {
+       my $ref = shift;
+       # The remote helper will call "import HEAD" and
+       # "import refs/heads/master".
+       # Since HEAD is a symbolic ref to master (by convention,
+       # followed by the output of the command "list" that we gave),
+       # we don't need to do anything in this case.
+       if ($ref eq "HEAD") {
+               return;
+       }
+
+       mw_connect_maybe();
+
+       my @pages = get_mw_pages();
+
+       print STDERR "Searching revisions...\n";
+       my $last_local = get_last_local_revision();
+       my $fetch_from = $last_local + 1;
+       if ($fetch_from == 1) {
+               print STDERR ", fetching from beginning.\n";
+       } else {
+               print STDERR ", fetching from here.\n";
+       }
+       my ($n, @revisions) = fetch_mw_revisions(\@pages, $fetch_from);
+
+       # Creation of the fast-import stream
+       print STDERR "Fetching & writing export data...\n";
+
+       $n = 0;
+       my $last_timestamp = 0; # Placeholer in case $rev->timestamp is undefined
+
+       foreach my $pagerevid (sort {$a->{revid} <=> $b->{revid}} @revisions) {
+               # fetch the content of the pages
+               my $query = {
+                       action => 'query',
+                       prop => 'revisions',
+                       rvprop => 'content|timestamp|comment|user|ids',
+                       revids => $pagerevid->{revid},
+               };
+
+               my $result = $mediawiki->api($query);
+
+               my $rev = pop(@{$result->{query}->{pages}->{$pagerevid->{pageid}}->{revisions}});
+
+               $n++;
+
+               my %commit;
+               $commit{author} = $rev->{user} || 'Anonymous';
+               $commit{comment} = $rev->{comment} || '*Empty MediaWiki Message*';
+               $commit{title} = mediawiki_smudge_filename(
+                       $result->{query}->{pages}->{$pagerevid->{pageid}}->{title}
+                   );
+               $commit{mw_revision} = $pagerevid->{revid};
+               $commit{content} = mediawiki_smudge($rev->{'*'});
+
+               if (!defined($rev->{timestamp})) {
+                       $last_timestamp++;
+               } else {
+                       $last_timestamp = $rev->{timestamp};
+               }
+               $commit{date} = DateTime::Format::ISO8601->parse_datetime($last_timestamp);
+
+               print STDERR "$n/", scalar(@revisions), ": Revision #$pagerevid->{revid} of $commit{title}\n";
+
+               import_file_revision(\%commit, ($fetch_from == 1), $n);
+       }
+
+       if ($fetch_from == 1 && $n == 0) {
+               print STDERR "You appear to have cloned an empty MediaWiki.\n";
+               # Something has to be done remote-helper side. If nothing is done, an error is
+               # thrown saying that HEAD is refering to unknown object 0000000000000000000
+               # and the clone fails.
+       }
+}
+
+sub error_non_fast_forward {
+       my $advice = run_git("config --bool advice.pushNonFastForward");
+       chomp($advice);
+       if ($advice ne "false") {
+               # Native git-push would show this after the summary.
+               # We can't ask it to display it cleanly, so print it
+               # ourselves before.
+               print STDERR "To prevent you from losing history, non-fast-forward updates were rejected\n";
+               print STDERR "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n";
+               print STDERR "'Note about fast-forwards' section of 'git push --help' for details.\n";
+       }
+       print STDOUT "error $_[0] \"non-fast-forward\"\n";
+       return 0;
+}
+
+sub mw_push_file {
+       my $diff_info = shift;
+       # $diff_info contains a string in this format:
+       # 100644 100644 <sha1_of_blob_before_commit> <sha1_of_blob_now> <status>
+       my @diff_info_split = split(/[ \t]/, $diff_info);
+
+       # Filename, including .mw extension
+       my $complete_file_name = shift;
+       # Commit message
+       my $summary = shift;
+       # MediaWiki revision number. Keep the previous one by default,
+       # in case there's no edit to perform.
+       my $newrevid = shift;
+
+       my $new_sha1 = $diff_info_split[3];
+       my $old_sha1 = $diff_info_split[2];
+       my $page_created = ($old_sha1 eq NULL_SHA1);
+       my $page_deleted = ($new_sha1 eq NULL_SHA1);
+       $complete_file_name = mediawiki_clean_filename($complete_file_name);
+
+       if (substr($complete_file_name,-3) eq ".mw") {
+               my $title = substr($complete_file_name,0,-3);
+
+               my $file_content;
+               if ($page_deleted) {
+                       # Deleting a page usually requires
+                       # special priviledges. A common
+                       # convention is to replace the page
+                       # with this content instead:
+                       $file_content = DELETED_CONTENT;
+               } else {
+                       $file_content = run_git("cat-file blob $new_sha1");
+               }
+
+               mw_connect_maybe();
+
+               my $result = $mediawiki->edit( {
+                       action => 'edit',
+                       summary => $summary,
+                       title => $title,
+                       basetimestamp => $basetimestamps{$newrevid},
+                       text => mediawiki_clean($file_content, $page_created),
+                                 }, {
+                                         skip_encoding => 1 # Helps with names with accentuated characters
+                                 });
+               if (!$result) {
+                       if ($mediawiki->{error}->{code} == 3) {
+                               # edit conflicts, considered as non-fast-forward
+                               print STDERR 'Warning: Error ' .
+                                   $mediawiki->{error}->{code} .
+                                   ' from mediwiki: ' . $mediawiki->{error}->{details} .
+                                   ".\n";
+                               return ($newrevid, "non-fast-forward");
+                       } else {
+                               # Other errors. Shouldn't happen => just die()
+                               die 'Fatal: Error ' .
+                                   $mediawiki->{error}->{code} .
+                                   ' from mediwiki: ' . $mediawiki->{error}->{details};
+                       }
+               }
+               $newrevid = $result->{edit}->{newrevid};
+               print STDERR "Pushed file: $new_sha1 - $title\n";
+       } else {
+               print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n"
+       }
+       return ($newrevid, "ok");
+}
+
+sub mw_push {
+       # multiple push statements can follow each other
+       my @refsspecs = (shift, get_more_refs("push"));
+       my $pushed;
+       for my $refspec (@refsspecs) {
+               my ($force, $local, $remote) = $refspec =~ /^(\+)?([^:]*):([^:]*)$/
+                   or die("Invalid refspec for push. Expected <src>:<dst> or +<src>:<dst>");
+               if ($force) {
+                       print STDERR "Warning: forced push not allowed on a MediaWiki.\n";
+               }
+               if ($local eq "") {
+                       print STDERR "Cannot delete remote branch on a MediaWiki\n";
+                       print STDOUT "error $remote cannot delete\n";
+                       next;
+               }
+               if ($remote ne "refs/heads/master") {
+                       print STDERR "Only push to the branch 'master' is supported on a MediaWiki\n";
+                       print STDOUT "error $remote only master allowed\n";
+                       next;
+               }
+               if (mw_push_revision($local, $remote)) {
+                       $pushed = 1;
+               }
+       }
+
+       # Notify Git that the push is done
+       print STDOUT "\n";
+
+       if ($pushed && $dumb_push) {
+               print STDERR "Just pushed some revisions to MediaWiki.\n";
+               print STDERR "The pushed revisions now have to be re-imported, and your current branch\n";
+               print STDERR "needs to be updated with these re-imported commits. You can do this with\n";
+               print STDERR "\n";
+               print STDERR "  git pull --rebase\n";
+               print STDERR "\n";
+       }
+}
+
+sub mw_push_revision {
+       my $local = shift;
+       my $remote = shift; # actually, this has to be "refs/heads/master" at this point.
+       my $last_local_revid = get_last_local_revision();
+       print STDERR ".\n"; # Finish sentence started by get_last_local_revision()
+       my $last_remote_revid = get_last_remote_revision();
+       my $mw_revision = $last_remote_revid;
+
+       # Get sha1 of commit pointed by local HEAD
+       my $HEAD_sha1 = run_git("rev-parse $local 2>/dev/null"); chomp($HEAD_sha1);
+       # Get sha1 of commit pointed by remotes/$remotename/master
+       my $remoteorigin_sha1 = run_git("rev-parse refs/remotes/$remotename/master 2>/dev/null");
+       chomp($remoteorigin_sha1);
+
+       if ($last_local_revid > 0 &&
+           $last_local_revid < $last_remote_revid) {
+               return error_non_fast_forward($remote);
+       }
+
+       if ($HEAD_sha1 eq $remoteorigin_sha1) {
+               # nothing to push
+               return 0;
+       }
+
+       # Get every commit in between HEAD and refs/remotes/origin/master,
+       # including HEAD and refs/remotes/origin/master
+       my @commit_pairs = ();
+       if ($last_local_revid > 0) {
+               my $parsed_sha1 = $remoteorigin_sha1;
+               # Find a path from last MediaWiki commit to pushed commit
+               while ($parsed_sha1 ne $HEAD_sha1) {
+                       my @commit_info =  grep(/^$parsed_sha1/, split(/\n/, run_git("rev-list --children $local")));
+                       if (!@commit_info) {
+                               return error_non_fast_forward($remote);
+                       }
+                       my @commit_info_split = split(/ |\n/, $commit_info[0]);
+                       # $commit_info_split[1] is the sha1 of the commit to export
+                       # $commit_info_split[0] is the sha1 of its direct child
+                       push(@commit_pairs, \@commit_info_split);
+                       $parsed_sha1 = $commit_info_split[1];
+               }
+       } else {
+               # No remote mediawiki revision. Export the whole
+               # history (linearized with --first-parent)
+               print STDERR "Warning: no common ancestor, pushing complete history\n";
+               my $history = run_git("rev-list --first-parent --children $local");
+               my @history = split('\n', $history);
+               @history = @history[1..$#history];
+               foreach my $line (reverse @history) {
+                       my @commit_info_split = split(/ |\n/, $line);
+                       push(@commit_pairs, \@commit_info_split);
+               }
+       }
+
+       foreach my $commit_info_split (@commit_pairs) {
+               my $sha1_child = @{$commit_info_split}[0];
+               my $sha1_commit = @{$commit_info_split}[1];
+               my $diff_infos = run_git("diff-tree -r --raw -z $sha1_child $sha1_commit");
+               # TODO: we could detect rename, and encode them with a #redirect on the wiki.
+               # TODO: for now, it's just a delete+add
+               my @diff_info_list = split(/\0/, $diff_infos);
+               # Keep the first line of the commit message as mediawiki comment for the revision
+               my $commit_msg = (split(/\n/, run_git("show --pretty=format:\"%s\" $sha1_commit")))[0];
+               chomp($commit_msg);
+               # Push every blob
+               while (@diff_info_list) {
+                       my $status;
+                       # git diff-tree -z gives an output like
+                       # <metadata>\0<filename1>\0
+                       # <metadata>\0<filename2>\0
+                       # and we've split on \0.
+                       my $info = shift(@diff_info_list);
+                       my $file = shift(@diff_info_list);
+                       ($mw_revision, $status) = mw_push_file($info, $file, $commit_msg, $mw_revision);
+                       if ($status eq "non-fast-forward") {
+                               # we may already have sent part of the
+                               # commit to MediaWiki, but it's too
+                               # late to cancel it. Stop the push in
+                               # the middle, but still give an
+                               # accurate error message.
+                               return error_non_fast_forward($remote);
+                       }
+                       if ($status ne "ok") {
+                               die("Unknown error from mw_push_file()");
+                       }
+               }
+               unless ($dumb_push) {
+                       run_git("notes --ref=$remotename/mediawiki add -m \"mediawiki_revision: $mw_revision\" $sha1_commit");
+                       run_git("update-ref -m \"Git-MediaWiki push\" refs/mediawiki/$remotename/master $sha1_commit $sha1_child");
+               }
+       }
+
+       print STDOUT "ok $remote\n";
+       return 1;
+}
diff --git a/contrib/mw-to-git/git-remote-mediawiki.txt b/contrib/mw-to-git/git-remote-mediawiki.txt
new file mode 100644 (file)
index 0000000..4d211f5
--- /dev/null
@@ -0,0 +1,7 @@
+Git-Mediawiki is a project which aims the creation of a gate
+between git and mediawiki, allowing git users to push and pull
+objects from mediawiki just as one would do with a classic git
+repository thanks to remote-helpers.
+
+For more information, visit the wiki at
+https://github.com/Bibzball/Git-Mediawiki/wiki
index 3bb5a4dd57c669bc59be0e2317ef33b64b024992..4534e2c2b003998a590637fc43c0549ff23f71b2 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -2,6 +2,7 @@
 #include "attr.h"
 #include "run-command.h"
 #include "quote.h"
+#include "sigchain.h"
 
 /*
  * convert.c - convert a file when checking it out and checking it in.
@@ -195,9 +196,17 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
        char *dst;
 
        if (crlf_action == CRLF_BINARY ||
-           (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) || !len)
+           (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) ||
+           (src && !len))
                return 0;
 
+       /*
+        * If we are doing a dry-run and have no source buffer, there is
+        * nothing to analyze; we must assume we would convert.
+        */
+       if (!buf && !src)
+               return 1;
+
        gather_stats(src, len, &stats);
 
        if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) {
@@ -231,6 +240,13 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
        if (!stats.cr)
                return 0;
 
+       /*
+        * At this point all of our source analysis is done, and we are sure we
+        * would convert. If we are in dry-run mode, we can give an answer.
+        */
+       if (!buf)
+               return 1;
+
        /* only grow if not in place */
        if (strbuf_avail(buf) + buf->len < len)
                strbuf_grow(buf, len - buf->len);
@@ -360,12 +376,16 @@ static int filter_buffer(int in, int out, void *data)
        if (start_command(&child_process))
                return error("cannot fork to run external filter %s", params->cmd);
 
+       sigchain_push(SIGPIPE, SIG_IGN);
+
        write_err = (write_in_full(child_process.in, params->src, params->size) < 0);
        if (close(child_process.in))
                write_err = 1;
        if (write_err)
                error("cannot feed the input to external filter %s", params->cmd);
 
+       sigchain_pop(SIGPIPE);
+
        status = finish_command(&child_process);
        if (status)
                error("external filter %s failed %d", params->cmd, status);
@@ -391,6 +411,9 @@ static int apply_filter(const char *path, const char *src, size_t len,
        if (!cmd)
                return 0;
 
+       if (!dst)
+               return 1;
+
        memset(&async, 0, sizeof(async));
        async.proc = filter_buffer;
        async.data = &params;
@@ -522,9 +545,12 @@ static int ident_to_git(const char *path, const char *src, size_t len,
 {
        char *dst, *dollar;
 
-       if (!ident || !count_ident(src, len))
+       if (!ident || (src && !count_ident(src, len)))
                return 0;
 
+       if (!buf)
+               return 1;
+
        /* only grow if not in place */
        if (strbuf_avail(buf) + buf->len < len)
                strbuf_grow(buf, len - buf->len);
@@ -641,7 +667,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
        return 1;
 }
 
-static int git_path_check_crlf(const char *path, struct git_attr_check *check)
+static enum crlf_action git_path_check_crlf(const char *path, struct git_attr_check *check)
 {
        const char *value = check->value;
 
@@ -658,7 +684,7 @@ static int git_path_check_crlf(const char *path, struct git_attr_check *check)
        return CRLF_GUESS;
 }
 
-static int git_path_check_eol(const char *path, struct git_attr_check *check)
+static enum eol git_path_check_eol(const char *path, struct git_attr_check *check)
 {
        const char *value = check->value;
 
@@ -754,13 +780,13 @@ int convert_to_git(const char *path, const char *src, size_t len,
                filter = ca.drv->clean;
 
        ret |= apply_filter(path, src, len, dst, filter);
-       if (ret) {
+       if (ret && dst) {
                src = dst->buf;
                len = dst->len;
        }
        ca.crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
        ret |= crlf_to_git(path, src, len, dst, ca.crlf_action, checksafe);
-       if (ret) {
+       if (ret && dst) {
                src = dst->buf;
                len = dst->len;
        }
@@ -811,7 +837,7 @@ int renormalize_buffer(const char *path, const char *src, size_t len, struct str
                src = dst->buf;
                len = dst->len;
        }
-       return ret | convert_to_git(path, src, len, dst, 0);
+       return ret | convert_to_git(path, src, len, dst, SAFE_CRLF_FALSE);
 }
 
 /*****************************************************************
@@ -876,43 +902,109 @@ int is_null_stream_filter(struct stream_filter *filter)
 /*
  * LF-to-CRLF filter
  */
+
+struct lf_to_crlf_filter {
+       struct stream_filter filter;
+       unsigned has_held:1;
+       char held;
+};
+
 static int lf_to_crlf_filter_fn(struct stream_filter *filter,
                                const char *input, size_t *isize_p,
                                char *output, size_t *osize_p)
 {
-       size_t count;
+       size_t count, o = 0;
+       struct lf_to_crlf_filter *lf_to_crlf = (struct lf_to_crlf_filter *)filter;
+
+       /*
+        * We may be holding onto the CR to see if it is followed by a
+        * LF, in which case we would need to go to the main loop.
+        * Otherwise, just emit it to the output stream.
+        */
+       if (lf_to_crlf->has_held && (lf_to_crlf->held != '\r' || !input)) {
+               output[o++] = lf_to_crlf->held;
+               lf_to_crlf->has_held = 0;
+       }
+
+       /* We are told to drain */
+       if (!input) {
+               *osize_p -= o;
+               return 0;
+       }
 
-       if (!input)
-               return 0; /* we do not keep any states */
        count = *isize_p;
-       if (count) {
-               size_t i, o;
-               for (i = o = 0; o < *osize_p && i < count; i++) {
+       if (count || lf_to_crlf->has_held) {
+               size_t i;
+               int was_cr = 0;
+
+               if (lf_to_crlf->has_held) {
+                       was_cr = 1;
+                       lf_to_crlf->has_held = 0;
+               }
+
+               for (i = 0; o < *osize_p && i < count; i++) {
                        char ch = input[i];
+
                        if (ch == '\n') {
-                               if (o + 1 < *osize_p)
-                                       output[o++] = '\r';
-                               else
-                                       break;
+                               output[o++] = '\r';
+                       } else if (was_cr) {
+                               /*
+                                * Previous round saw CR and it is not followed
+                                * by a LF; emit the CR before processing the
+                                * current character.
+                                */
+                               output[o++] = '\r';
+                       }
+
+                       /*
+                        * We may have consumed the last output slot,
+                        * in which case we need to break out of this
+                        * loop; hold the current character before
+                        * returning.
+                        */
+                       if (*osize_p <= o) {
+                               lf_to_crlf->has_held = 1;
+                               lf_to_crlf->held = ch;
+                               continue; /* break but increment i */
                        }
+
+                       if (ch == '\r') {
+                               was_cr = 1;
+                               continue;
+                       }
+
+                       was_cr = 0;
                        output[o++] = ch;
                }
 
                *osize_p -= o;
                *isize_p -= i;
+
+               if (!lf_to_crlf->has_held && was_cr) {
+                       lf_to_crlf->has_held = 1;
+                       lf_to_crlf->held = '\r';
+               }
        }
        return 0;
 }
 
+static void lf_to_crlf_free_fn(struct stream_filter *filter)
+{
+       free(filter);
+}
+
 static struct stream_filter_vtbl lf_to_crlf_vtbl = {
        lf_to_crlf_filter_fn,
-       null_free_fn,
+       lf_to_crlf_free_fn,
 };
 
-static struct stream_filter lf_to_crlf_filter_singleton = {
-       &lf_to_crlf_vtbl,
-};
+static struct stream_filter *lf_to_crlf_filter(void)
+{
+       struct lf_to_crlf_filter *lf_to_crlf = xcalloc(1, sizeof(*lf_to_crlf));
 
+       lf_to_crlf->filter.vtbl = &lf_to_crlf_vtbl;
+       return (struct stream_filter *)lf_to_crlf;
+}
 
 /*
  * Cascade filter
@@ -1194,7 +1286,7 @@ struct stream_filter *get_stream_filter(const char *path, const unsigned char *s
 
        else if (output_eol(crlf_action) == EOL_CRLF &&
                 !(crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS))
-               filter = cascade_filter(filter, &lf_to_crlf_filter_singleton);
+               filter = cascade_filter(filter, lf_to_crlf_filter());
 
        return filter;
 }
index d799a165b4731f2a44a44eaceffc588d1e9fcfc3..ec5fd69430908915cf9c0de62a2021882fd2c658 100644 (file)
--- a/convert.h
+++ b/convert.h
@@ -40,6 +40,11 @@ extern int convert_to_working_tree(const char *path, const char *src,
                                   size_t len, struct strbuf *dst);
 extern int renormalize_buffer(const char *path, const char *src, size_t len,
                              struct strbuf *dst);
+static inline int would_convert_to_git(const char *path, const char *src,
+                                      size_t len, enum safe_crlf checksafe)
+{
+       return convert_to_git(path, src, len, NULL, checksafe);
+}
 
 /*****************************************************************
  *
diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c
new file mode 100644 (file)
index 0000000..390f194
--- /dev/null
@@ -0,0 +1,269 @@
+#include "cache.h"
+#include "credential.h"
+#include "unix-socket.h"
+#include "sigchain.h"
+
+static const char *socket_path;
+
+static void cleanup_socket(void)
+{
+       if (socket_path)
+               unlink(socket_path);
+}
+
+static void cleanup_socket_on_signal(int sig)
+{
+       cleanup_socket();
+       sigchain_pop(sig);
+       raise(sig);
+}
+
+struct credential_cache_entry {
+       struct credential item;
+       unsigned long expiration;
+};
+static struct credential_cache_entry *entries;
+static int entries_nr;
+static int entries_alloc;
+
+static void cache_credential(struct credential *c, int timeout)
+{
+       struct credential_cache_entry *e;
+
+       ALLOC_GROW(entries, entries_nr + 1, entries_alloc);
+       e = &entries[entries_nr++];
+
+       /* take ownership of pointers */
+       memcpy(&e->item, c, sizeof(*c));
+       memset(c, 0, sizeof(*c));
+       e->expiration = time(NULL) + timeout;
+}
+
+static struct credential_cache_entry *lookup_credential(const struct credential *c)
+{
+       int i;
+       for (i = 0; i < entries_nr; i++) {
+               struct credential *e = &entries[i].item;
+               if (credential_match(c, e))
+                       return &entries[i];
+       }
+       return NULL;
+}
+
+static void remove_credential(const struct credential *c)
+{
+       struct credential_cache_entry *e;
+
+       e = lookup_credential(c);
+       if (e)
+               e->expiration = 0;
+}
+
+static int check_expirations(void)
+{
+       static unsigned long wait_for_entry_until;
+       int i = 0;
+       unsigned long now = time(NULL);
+       unsigned long next = (unsigned long)-1;
+
+       /*
+        * Initially give the client 30 seconds to actually contact us
+        * and store a credential before we decide there's no point in
+        * keeping the daemon around.
+        */
+       if (!wait_for_entry_until)
+               wait_for_entry_until = now + 30;
+
+       while (i < entries_nr) {
+               if (entries[i].expiration <= now) {
+                       entries_nr--;
+                       credential_clear(&entries[i].item);
+                       if (i != entries_nr)
+                               memcpy(&entries[i], &entries[entries_nr], sizeof(*entries));
+                       /*
+                        * Stick around 30 seconds in case a new credential
+                        * shows up (e.g., because we just removed a failed
+                        * one, and we will soon get the correct one).
+                        */
+                       wait_for_entry_until = now + 30;
+               }
+               else {
+                       if (entries[i].expiration < next)
+                               next = entries[i].expiration;
+                       i++;
+               }
+       }
+
+       if (!entries_nr) {
+               if (wait_for_entry_until <= now)
+                       return 0;
+               next = wait_for_entry_until;
+       }
+
+       return next - now;
+}
+
+static int read_request(FILE *fh, struct credential *c,
+                       struct strbuf *action, int *timeout) {
+       static struct strbuf item = STRBUF_INIT;
+       const char *p;
+
+       strbuf_getline(&item, fh, '\n');
+       p = skip_prefix(item.buf, "action=");
+       if (!p)
+               return error("client sent bogus action line: %s", item.buf);
+       strbuf_addstr(action, p);
+
+       strbuf_getline(&item, fh, '\n');
+       p = skip_prefix(item.buf, "timeout=");
+       if (!p)
+               return error("client sent bogus timeout line: %s", item.buf);
+       *timeout = atoi(p);
+
+       if (credential_read(c, fh) < 0)
+               return -1;
+       return 0;
+}
+
+static void serve_one_client(FILE *in, FILE *out)
+{
+       struct credential c = CREDENTIAL_INIT;
+       struct strbuf action = STRBUF_INIT;
+       int timeout = -1;
+
+       if (read_request(in, &c, &action, &timeout) < 0)
+               /* ignore error */ ;
+       else if (!strcmp(action.buf, "get")) {
+               struct credential_cache_entry *e = lookup_credential(&c);
+               if (e) {
+                       fprintf(out, "username=%s\n", e->item.username);
+                       fprintf(out, "password=%s\n", e->item.password);
+               }
+       }
+       else if (!strcmp(action.buf, "exit"))
+               exit(0);
+       else if (!strcmp(action.buf, "erase"))
+               remove_credential(&c);
+       else if (!strcmp(action.buf, "store")) {
+               if (timeout < 0)
+                       warning("cache client didn't specify a timeout");
+               else if (!c.username || !c.password)
+                       warning("cache client gave us a partial credential");
+               else {
+                       remove_credential(&c);
+                       cache_credential(&c, timeout);
+               }
+       }
+       else
+               warning("cache client sent unknown action: %s", action.buf);
+
+       credential_clear(&c);
+       strbuf_release(&action);
+}
+
+static int serve_cache_loop(int fd)
+{
+       struct pollfd pfd;
+       unsigned long wakeup;
+
+       wakeup = check_expirations();
+       if (!wakeup)
+               return 0;
+
+       pfd.fd = fd;
+       pfd.events = POLLIN;
+       if (poll(&pfd, 1, 1000 * wakeup) < 0) {
+               if (errno != EINTR)
+                       die_errno("poll failed");
+               return 1;
+       }
+
+       if (pfd.revents & POLLIN) {
+               int client, client2;
+               FILE *in, *out;
+
+               client = accept(fd, NULL, NULL);
+               if (client < 0) {
+                       warning("accept failed: %s", strerror(errno));
+                       return 1;
+               }
+               client2 = dup(client);
+               if (client2 < 0) {
+                       warning("dup failed: %s", strerror(errno));
+                       close(client);
+                       return 1;
+               }
+
+               in = xfdopen(client, "r");
+               out = xfdopen(client2, "w");
+               serve_one_client(in, out);
+               fclose(in);
+               fclose(out);
+       }
+       return 1;
+}
+
+static void serve_cache(const char *socket_path)
+{
+       int fd;
+
+       fd = unix_stream_listen(socket_path);
+       if (fd < 0)
+               die_errno("unable to bind to '%s'", socket_path);
+
+       printf("ok\n");
+       fclose(stdout);
+
+       while (serve_cache_loop(fd))
+               ; /* nothing */
+
+       close(fd);
+       unlink(socket_path);
+}
+
+static const char permissions_advice[] =
+"The permissions on your socket directory are too loose; other\n"
+"users may be able to read your cached credentials. Consider running:\n"
+"\n"
+"      chmod 0700 %s";
+static void check_socket_directory(const char *path)
+{
+       struct stat st;
+       char *path_copy = xstrdup(path);
+       char *dir = dirname(path_copy);
+
+       if (!stat(dir, &st)) {
+               if (st.st_mode & 077)
+                       die(permissions_advice, dir);
+               free(path_copy);
+               return;
+       }
+
+       /*
+        * We must be sure to create the directory with the correct mode,
+        * not just chmod it after the fact; otherwise, there is a race
+        * condition in which somebody can chdir to it, sleep, then try to open
+        * our protected socket.
+        */
+       if (safe_create_leading_directories_const(dir) < 0)
+               die_errno("unable to create directories for '%s'", dir);
+       if (mkdir(dir, 0700) < 0)
+               die_errno("unable to mkdir '%s'", dir);
+       free(path_copy);
+}
+
+int main(int argc, const char **argv)
+{
+       socket_path = argv[1];
+
+       if (!socket_path)
+               die("usage: git-credential-cache--daemon <socket_path>");
+       check_socket_directory(socket_path);
+
+       atexit(cleanup_socket);
+       sigchain_push_common(cleanup_socket_on_signal);
+
+       serve_cache(socket_path);
+
+       return 0;
+}
diff --git a/credential-cache.c b/credential-cache.c
new file mode 100644 (file)
index 0000000..9a03792
--- /dev/null
@@ -0,0 +1,123 @@
+#include "cache.h"
+#include "credential.h"
+#include "string-list.h"
+#include "parse-options.h"
+#include "unix-socket.h"
+#include "run-command.h"
+
+#define FLAG_SPAWN 0x1
+#define FLAG_RELAY 0x2
+
+static int send_request(const char *socket, const struct strbuf *out)
+{
+       int got_data = 0;
+       int fd = unix_stream_connect(socket);
+
+       if (fd < 0)
+               return -1;
+
+       if (write_in_full(fd, out->buf, out->len) < 0)
+               die_errno("unable to write to cache daemon");
+       shutdown(fd, SHUT_WR);
+
+       while (1) {
+               char in[1024];
+               int r;
+
+               r = read_in_full(fd, in, sizeof(in));
+               if (r == 0)
+                       break;
+               if (r < 0)
+                       die_errno("read error from cache daemon");
+               write_or_die(1, in, r);
+               got_data = 1;
+       }
+       return got_data;
+}
+
+static void spawn_daemon(const char *socket)
+{
+       struct child_process daemon;
+       const char *argv[] = { NULL, NULL, NULL };
+       char buf[128];
+       int r;
+
+       memset(&daemon, 0, sizeof(daemon));
+       argv[0] = "git-credential-cache--daemon";
+       argv[1] = socket;
+       daemon.argv = argv;
+       daemon.no_stdin = 1;
+       daemon.out = -1;
+
+       if (start_command(&daemon))
+               die_errno("unable to start cache daemon");
+       r = read_in_full(daemon.out, buf, sizeof(buf));
+       if (r < 0)
+               die_errno("unable to read result code from cache daemon");
+       if (r != 3 || memcmp(buf, "ok\n", 3))
+               die("cache daemon did not start: %.*s", r, buf);
+       close(daemon.out);
+}
+
+static void do_cache(const char *socket, const char *action, int timeout,
+                    int flags)
+{
+       struct strbuf buf = STRBUF_INIT;
+
+       strbuf_addf(&buf, "action=%s\n", action);
+       strbuf_addf(&buf, "timeout=%d\n", timeout);
+       if (flags & FLAG_RELAY) {
+               if (strbuf_read(&buf, 0, 0) < 0)
+                       die_errno("unable to relay credential");
+       }
+
+       if (send_request(socket, &buf) < 0) {
+               if (errno != ENOENT && errno != ECONNREFUSED)
+                       die_errno("unable to connect to cache daemon");
+               if (flags & FLAG_SPAWN) {
+                       spawn_daemon(socket);
+                       if (send_request(socket, &buf) < 0)
+                               die_errno("unable to connect to cache daemon");
+               }
+       }
+       strbuf_release(&buf);
+}
+
+int main(int argc, const char **argv)
+{
+       char *socket_path = NULL;
+       int timeout = 900;
+       const char *op;
+       const char * const usage[] = {
+               "git credential-cache [options] <action>",
+               NULL
+       };
+       struct option options[] = {
+               OPT_INTEGER(0, "timeout", &timeout,
+                           "number of seconds to cache credentials"),
+               OPT_STRING(0, "socket", &socket_path, "path",
+                          "path of cache-daemon socket"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, NULL, options, usage, 0);
+       if (!argc)
+               usage_with_options(usage, options);
+       op = argv[0];
+
+       if (!socket_path)
+               socket_path = expand_user_path("~/.git-credential-cache/socket");
+       if (!socket_path)
+               die("unable to find a suitable socket path; use --socket");
+
+       if (!strcmp(op, "exit"))
+               do_cache(socket_path, op, timeout, 0);
+       else if (!strcmp(op, "get") || !strcmp(op, "erase"))
+               do_cache(socket_path, op, timeout, FLAG_RELAY);
+       else if (!strcmp(op, "store"))
+               do_cache(socket_path, op, timeout, FLAG_RELAY|FLAG_SPAWN);
+       else
+               ; /* ignore unknown operation */
+
+       return 0;
+}
diff --git a/credential-store.c b/credential-store.c
new file mode 100644 (file)
index 0000000..26f7589
--- /dev/null
@@ -0,0 +1,157 @@
+#include "cache.h"
+#include "credential.h"
+#include "string-list.h"
+#include "parse-options.h"
+
+static struct lock_file credential_lock;
+
+static void parse_credential_file(const char *fn,
+                                 struct credential *c,
+                                 void (*match_cb)(struct credential *),
+                                 void (*other_cb)(struct strbuf *))
+{
+       FILE *fh;
+       struct strbuf line = STRBUF_INIT;
+       struct credential entry = CREDENTIAL_INIT;
+
+       fh = fopen(fn, "r");
+       if (!fh) {
+               if (errno != ENOENT)
+                       die_errno("unable to open %s", fn);
+               return;
+       }
+
+       while (strbuf_getline(&line, fh, '\n') != EOF) {
+               credential_from_url(&entry, line.buf);
+               if (entry.username && entry.password &&
+                   credential_match(c, &entry)) {
+                       if (match_cb) {
+                               match_cb(&entry);
+                               break;
+                       }
+               }
+               else if (other_cb)
+                       other_cb(&line);
+       }
+
+       credential_clear(&entry);
+       strbuf_release(&line);
+       fclose(fh);
+}
+
+static void print_entry(struct credential *c)
+{
+       printf("username=%s\n", c->username);
+       printf("password=%s\n", c->password);
+}
+
+static void print_line(struct strbuf *buf)
+{
+       strbuf_addch(buf, '\n');
+       write_or_die(credential_lock.fd, buf->buf, buf->len);
+}
+
+static void rewrite_credential_file(const char *fn, struct credential *c,
+                                   struct strbuf *extra)
+{
+       if (hold_lock_file_for_update(&credential_lock, fn, 0) < 0)
+               die_errno("unable to get credential storage lock");
+       if (extra)
+               print_line(extra);
+       parse_credential_file(fn, c, NULL, print_line);
+       if (commit_lock_file(&credential_lock) < 0)
+               die_errno("unable to commit credential store");
+}
+
+static void store_credential(const char *fn, struct credential *c)
+{
+       struct strbuf buf = STRBUF_INIT;
+
+       /*
+        * Sanity check that what we are storing is actually sensible.
+        * In particular, we can't make a URL without a protocol field.
+        * Without either a host or pathname (depending on the scheme),
+        * we have no primary key. And without a username and password,
+        * we are not actually storing a credential.
+        */
+       if (!c->protocol || !(c->host || c->path) ||
+           !c->username || !c->password)
+               return;
+
+       strbuf_addf(&buf, "%s://", c->protocol);
+       strbuf_addstr_urlencode(&buf, c->username, 1);
+       strbuf_addch(&buf, ':');
+       strbuf_addstr_urlencode(&buf, c->password, 1);
+       strbuf_addch(&buf, '@');
+       if (c->host)
+               strbuf_addstr_urlencode(&buf, c->host, 1);
+       if (c->path) {
+               strbuf_addch(&buf, '/');
+               strbuf_addstr_urlencode(&buf, c->path, 0);
+       }
+
+       rewrite_credential_file(fn, c, &buf);
+       strbuf_release(&buf);
+}
+
+static void remove_credential(const char *fn, struct credential *c)
+{
+       /*
+        * Sanity check that we actually have something to match
+        * against. The input we get is a restrictive pattern,
+        * so technically a blank credential means "erase everything".
+        * But it is too easy to accidentally send this, since it is equivalent
+        * to empty input. So explicitly disallow it, and require that the
+        * pattern have some actual content to match.
+        */
+       if (c->protocol || c->host || c->path || c->username)
+               rewrite_credential_file(fn, c, NULL);
+}
+
+static int lookup_credential(const char *fn, struct credential *c)
+{
+       parse_credential_file(fn, c, print_entry, NULL);
+       return c->username && c->password;
+}
+
+int main(int argc, const char **argv)
+{
+       const char * const usage[] = {
+               "git credential-store [options] <action>",
+               NULL
+       };
+       const char *op;
+       struct credential c = CREDENTIAL_INIT;
+       char *file = NULL;
+       struct option options[] = {
+               OPT_STRING(0, "file", &file, "path",
+                          "fetch and store credentials in <path>"),
+               OPT_END()
+       };
+
+       umask(077);
+
+       argc = parse_options(argc, argv, NULL, options, usage, 0);
+       if (argc != 1)
+               usage_with_options(usage, options);
+       op = argv[0];
+
+       if (!file)
+               file = expand_user_path("~/.git-credentials");
+       if (!file)
+               die("unable to set up default path; use --file");
+
+       if (credential_read(&c, stdin) < 0)
+               die("unable to read credential");
+
+       if (!strcmp(op, "get"))
+               lookup_credential(file, &c);
+       else if (!strcmp(op, "erase"))
+               remove_credential(file, &c);
+       else if (!strcmp(op, "store"))
+               store_credential(file, &c);
+       else
+               ; /* Ignore unknown operation. */
+
+       return 0;
+}
diff --git a/credential.c b/credential.c
new file mode 100644 (file)
index 0000000..62d1c56
--- /dev/null
@@ -0,0 +1,365 @@
+#include "cache.h"
+#include "credential.h"
+#include "string-list.h"
+#include "run-command.h"
+#include "url.h"
+#include "prompt.h"
+
+void credential_init(struct credential *c)
+{
+       memset(c, 0, sizeof(*c));
+       c->helpers.strdup_strings = 1;
+}
+
+void credential_clear(struct credential *c)
+{
+       free(c->protocol);
+       free(c->host);
+       free(c->path);
+       free(c->username);
+       free(c->password);
+       string_list_clear(&c->helpers, 0);
+
+       credential_init(c);
+}
+
+int credential_match(const struct credential *want,
+                    const struct credential *have)
+{
+#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x)))
+       return CHECK(protocol) &&
+              CHECK(host) &&
+              CHECK(path) &&
+              CHECK(username);
+#undef CHECK
+}
+
+static int credential_config_callback(const char *var, const char *value,
+                                     void *data)
+{
+       struct credential *c = data;
+       const char *key, *dot;
+
+       key = skip_prefix(var, "credential.");
+       if (!key)
+               return 0;
+
+       if (!value)
+               return config_error_nonbool(var);
+
+       dot = strrchr(key, '.');
+       if (dot) {
+               struct credential want = CREDENTIAL_INIT;
+               char *url = xmemdupz(key, dot - key);
+               int matched;
+
+               credential_from_url(&want, url);
+               matched = credential_match(&want, c);
+
+               credential_clear(&want);
+               free(url);
+
+               if (!matched)
+                       return 0;
+               key = dot + 1;
+       }
+
+       if (!strcmp(key, "helper"))
+               string_list_append(&c->helpers, value);
+       else if (!strcmp(key, "username")) {
+               if (!c->username)
+                       c->username = xstrdup(value);
+       }
+       else if (!strcmp(key, "usehttppath"))
+               c->use_http_path = git_config_bool(var, value);
+
+       return 0;
+}
+
+static int proto_is_http(const char *s)
+{
+       if (!s)
+               return 0;
+       return !strcmp(s, "https") || !strcmp(s, "http");
+}
+
+static void credential_apply_config(struct credential *c)
+{
+       if (c->configured)
+               return;
+       git_config(credential_config_callback, c);
+       c->configured = 1;
+
+       if (!c->use_http_path && proto_is_http(c->protocol)) {
+               free(c->path);
+               c->path = NULL;
+       }
+}
+
+static void credential_describe(struct credential *c, struct strbuf *out)
+{
+       if (!c->protocol)
+               return;
+       strbuf_addf(out, "%s://", c->protocol);
+       if (c->username && *c->username)
+               strbuf_addf(out, "%s@", c->username);
+       if (c->host)
+               strbuf_addstr(out, c->host);
+       if (c->path)
+               strbuf_addf(out, "/%s", c->path);
+}
+
+static char *credential_ask_one(const char *what, struct credential *c,
+                               int flags)
+{
+       struct strbuf desc = STRBUF_INIT;
+       struct strbuf prompt = STRBUF_INIT;
+       char *r;
+
+       credential_describe(c, &desc);
+       if (desc.len)
+               strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
+       else
+               strbuf_addf(&prompt, "%s: ", what);
+
+       r = git_prompt(prompt.buf, flags);
+
+       strbuf_release(&desc);
+       strbuf_release(&prompt);
+       return xstrdup(r);
+}
+
+static void credential_getpass(struct credential *c)
+{
+       if (!c->username)
+               c->username = credential_ask_one("Username", c,
+                                                PROMPT_ASKPASS|PROMPT_ECHO);
+       if (!c->password)
+               c->password = credential_ask_one("Password", c,
+                                                PROMPT_ASKPASS);
+}
+
+int credential_read(struct credential *c, FILE *fp)
+{
+       struct strbuf line = STRBUF_INIT;
+
+       while (strbuf_getline(&line, fp, '\n') != EOF) {
+               char *key = line.buf;
+               char *value = strchr(key, '=');
+
+               if (!line.len)
+                       break;
+
+               if (!value) {
+                       warning("invalid credential line: %s", key);
+                       strbuf_release(&line);
+                       return -1;
+               }
+               *value++ = '\0';
+
+               if (!strcmp(key, "username")) {
+                       free(c->username);
+                       c->username = xstrdup(value);
+               } else if (!strcmp(key, "password")) {
+                       free(c->password);
+                       c->password = xstrdup(value);
+               } else if (!strcmp(key, "protocol")) {
+                       free(c->protocol);
+                       c->protocol = xstrdup(value);
+               } else if (!strcmp(key, "host")) {
+                       free(c->host);
+                       c->host = xstrdup(value);
+               } else if (!strcmp(key, "path")) {
+                       free(c->path);
+                       c->path = xstrdup(value);
+               }
+               /*
+                * Ignore other lines; we don't know what they mean, but
+                * this future-proofs us when later versions of git do
+                * learn new lines, and the helpers are updated to match.
+                */
+       }
+
+       strbuf_release(&line);
+       return 0;
+}
+
+static void credential_write_item(FILE *fp, const char *key, const char *value)
+{
+       if (!value)
+               return;
+       fprintf(fp, "%s=%s\n", key, value);
+}
+
+static void credential_write(const struct credential *c, FILE *fp)
+{
+       credential_write_item(fp, "protocol", c->protocol);
+       credential_write_item(fp, "host", c->host);
+       credential_write_item(fp, "path", c->path);
+       credential_write_item(fp, "username", c->username);
+       credential_write_item(fp, "password", c->password);
+}
+
+static int run_credential_helper(struct credential *c,
+                                const char *cmd,
+                                int want_output)
+{
+       struct child_process helper;
+       const char *argv[] = { NULL, NULL };
+       FILE *fp;
+
+       memset(&helper, 0, sizeof(helper));
+       argv[0] = cmd;
+       helper.argv = argv;
+       helper.use_shell = 1;
+       helper.in = -1;
+       if (want_output)
+               helper.out = -1;
+       else
+               helper.no_stdout = 1;
+
+       if (start_command(&helper) < 0)
+               return -1;
+
+       fp = xfdopen(helper.in, "w");
+       credential_write(c, fp);
+       fclose(fp);
+
+       if (want_output) {
+               int r;
+               fp = xfdopen(helper.out, "r");
+               r = credential_read(c, fp);
+               fclose(fp);
+               if (r < 0) {
+                       finish_command(&helper);
+                       return -1;
+               }
+       }
+
+       if (finish_command(&helper))
+               return -1;
+       return 0;
+}
+
+static int credential_do(struct credential *c, const char *helper,
+                        const char *operation)
+{
+       struct strbuf cmd = STRBUF_INIT;
+       int r;
+
+       if (helper[0] == '!')
+               strbuf_addstr(&cmd, helper + 1);
+       else if (is_absolute_path(helper))
+               strbuf_addstr(&cmd, helper);
+       else
+               strbuf_addf(&cmd, "git credential-%s", helper);
+
+       strbuf_addf(&cmd, " %s", operation);
+       r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get"));
+
+       strbuf_release(&cmd);
+       return r;
+}
+
+void credential_fill(struct credential *c)
+{
+       int i;
+
+       if (c->username && c->password)
+               return;
+
+       credential_apply_config(c);
+
+       for (i = 0; i < c->helpers.nr; i++) {
+               credential_do(c, c->helpers.items[i].string, "get");
+               if (c->username && c->password)
+                       return;
+       }
+
+       credential_getpass(c);
+       if (!c->username && !c->password)
+               die("unable to get password from user");
+}
+
+void credential_approve(struct credential *c)
+{
+       int i;
+
+       if (c->approved)
+               return;
+       if (!c->username || !c->password)
+               return;
+
+       credential_apply_config(c);
+
+       for (i = 0; i < c->helpers.nr; i++)
+               credential_do(c, c->helpers.items[i].string, "store");
+       c->approved = 1;
+}
+
+void credential_reject(struct credential *c)
+{
+       int i;
+
+       credential_apply_config(c);
+
+       for (i = 0; i < c->helpers.nr; i++)
+               credential_do(c, c->helpers.items[i].string, "erase");
+
+       free(c->username);
+       c->username = NULL;
+       free(c->password);
+       c->password = NULL;
+       c->approved = 0;
+}
+
+void credential_from_url(struct credential *c, const char *url)
+{
+       const char *at, *colon, *cp, *slash, *host, *proto_end;
+
+       credential_clear(c);
+
+       /*
+        * Match one of:
+        *   (1) proto://<host>/...
+        *   (2) proto://<user>@<host>/...
+        *   (3) proto://<user>:<pass>@<host>/...
+        */
+       proto_end = strstr(url, "://");
+       if (!proto_end)
+               return;
+       cp = proto_end + 3;
+       at = strchr(cp, '@');
+       colon = strchr(cp, ':');
+       slash = strchrnul(cp, '/');
+
+       if (!at || slash <= at) {
+               /* Case (1) */
+               host = cp;
+       }
+       else if (!colon || at <= colon) {
+               /* Case (2) */
+               c->username = url_decode_mem(cp, at - cp);
+               host = at + 1;
+       } else {
+               /* Case (3) */
+               c->username = url_decode_mem(cp, colon - cp);
+               c->password = url_decode_mem(colon + 1, at - (colon + 1));
+               host = at + 1;
+       }
+
+       if (proto_end - url > 0)
+               c->protocol = xmemdupz(url, proto_end - url);
+       if (slash - host > 0)
+               c->host = url_decode_mem(host, slash - host);
+       /* Trim leading and trailing slashes from path */
+       while (*slash == '/')
+               slash++;
+       if (*slash) {
+               char *p;
+               c->path = url_decode(slash);
+               p = c->path + strlen(c->path) - 1;
+               while (p > c->path && *p == '/')
+                       *p-- = '\0';
+       }
+}
diff --git a/credential.h b/credential.h
new file mode 100644 (file)
index 0000000..96ea41b
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef CREDENTIAL_H
+#define CREDENTIAL_H
+
+#include "string-list.h"
+
+struct credential {
+       struct string_list helpers;
+       unsigned approved:1,
+                configured:1,
+                use_http_path:1;
+
+       char *username;
+       char *password;
+       char *protocol;
+       char *host;
+       char *path;
+};
+
+#define CREDENTIAL_INIT { STRING_LIST_INIT_DUP }
+
+void credential_init(struct credential *);
+void credential_clear(struct credential *);
+
+void credential_fill(struct credential *);
+void credential_approve(struct credential *);
+void credential_reject(struct credential *);
+
+int credential_read(struct credential *, FILE *);
+void credential_from_url(struct credential *, const char *url);
+int credential_match(const struct credential *have,
+                    const struct credential *want);
+
+#endif /* CREDENTIAL_H */
index fc97d6e04528b5c5b55fc211a462f3cb828f3d49..53f5375b6ca3368de6647cf5edcd7fb4dec79657 100644 (file)
@@ -158,6 +158,26 @@ struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp
        return f;
 }
 
+void sha1file_checkpoint(struct sha1file *f, struct sha1file_checkpoint *checkpoint)
+{
+       sha1flush(f);
+       checkpoint->offset = f->total;
+       checkpoint->ctx = f->ctx;
+}
+
+int sha1file_truncate(struct sha1file *f, struct sha1file_checkpoint *checkpoint)
+{
+       off_t offset = checkpoint->offset;
+
+       if (ftruncate(f->fd, offset) ||
+           lseek(f->fd, offset, SEEK_SET) != offset)
+               return -1;
+       f->total = offset;
+       f->ctx = checkpoint->ctx;
+       f->offset = 0; /* sha1flush() was called in checkpoint */
+       return 0;
+}
+
 void crc32_begin(struct sha1file *f)
 {
        f->crc32 = crc32(0, NULL, 0);
index 6a7967c6bf604076c7d68ce139f65f34df3bc30e..3b540bdc21d2edf8d3d6fc2b5e52cc840aa19395 100644 (file)
@@ -17,6 +17,15 @@ struct sha1file {
        unsigned char buffer[8192];
 };
 
+/* Checkpoint */
+struct sha1file_checkpoint {
+       off_t offset;
+       git_SHA_CTX ctx;
+};
+
+extern void sha1file_checkpoint(struct sha1file *, struct sha1file_checkpoint *);
+extern int sha1file_truncate(struct sha1file *, struct sha1file_checkpoint *);
+
 /* sha1close flags */
 #define CSUM_CLOSE     1
 #define CSUM_FSYNC     2
diff --git a/ctype.c b/ctype.c
index b5d856fd26bd892a5f18202b054fc53e7c953429..af722f957fa5a1899dc906196d9b0372acab946d 100644 (file)
--- a/ctype.c
+++ b/ctype.c
@@ -3,7 +3,7 @@
  *
  * No surprises, and works with signed and unsigned chars.
  */
-#include "cache.h"
+#include "git-compat-util.h"
 
 enum {
        S = GIT_SPACE,
index 4c8346d5a1fe1325de2dd335a4e61a2dad994211..15ce918a21e1cce6cb603caa8638e4d854eee9dd 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -20,6 +20,7 @@
 static int log_syslog;
 static int verbose;
 static int reuseaddr;
+static int informative_errors;
 
 static const char daemon_usage[] =
 "git daemon [--verbose] [--syslog] [--export-all]\n"
@@ -108,11 +109,11 @@ static void NORETURN daemon_die(const char *err, va_list params)
        exit(1);
 }
 
-static char *path_ok(char *directory)
+static const char *path_ok(char *directory)
 {
        static char rpath[PATH_MAX];
        static char interp_path[PATH_MAX];
-       char *path;
+       const char *path;
        char *dir;
 
        dir = directory;
@@ -247,6 +248,14 @@ static int git_daemon_config(const char *var, const char *value, void *cb)
        return 0;
 }
 
+static int daemon_error(const char *dir, const char *msg)
+{
+       if (!informative_errors)
+               msg = "access denied or repository not exported";
+       packet_write(1, "ERR %s: %s", msg, dir);
+       return -1;
+}
+
 static int run_service(char *dir, struct daemon_service *service)
 {
        const char *path;
@@ -257,11 +266,11 @@ static int run_service(char *dir, struct daemon_service *service)
        if (!enabled && !service->overridable) {
                logerror("'%s': service not enabled.", service->name);
                errno = EACCES;
-               return -1;
+               return daemon_error(dir, "service not enabled");
        }
 
        if (!(path = path_ok(dir)))
-               return -1;
+               return daemon_error(dir, "no such repository");
 
        /*
         * Security on the cheap.
@@ -277,7 +286,7 @@ static int run_service(char *dir, struct daemon_service *service)
        if (!export_all_trees && access("git-daemon-export-ok", F_OK)) {
                logerror("'%s': repository not exported.", path);
                errno = EACCES;
-               return -1;
+               return daemon_error(dir, "repository not exported");
        }
 
        if (service->overridable) {
@@ -291,7 +300,7 @@ static int run_service(char *dir, struct daemon_service *service)
                logerror("'%s': service not enabled for '%s'",
                         service->name, path);
                errno = EACCES;
-               return -1;
+               return daemon_error(dir, "service not enabled");
        }
 
        /*
@@ -734,6 +743,29 @@ struct socketlist {
        size_t alloc;
 };
 
+static const char *ip2str(int family, struct sockaddr *sin, socklen_t len)
+{
+#ifdef NO_IPV6
+       static char ip[INET_ADDRSTRLEN];
+#else
+       static char ip[INET6_ADDRSTRLEN];
+#endif
+
+       switch (family) {
+#ifndef NO_IPV6
+       case AF_INET6:
+               inet_ntop(family, &((struct sockaddr_in6*)sin)->sin6_addr, ip, len);
+               break;
+#endif
+       case AF_INET:
+               inet_ntop(family, &((struct sockaddr_in*)sin)->sin_addr, ip, len);
+               break;
+       default:
+               strcpy(ip, "<unknown>");
+       }
+       return ip;
+}
+
 #ifndef NO_IPV6
 
 static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist)
@@ -780,15 +812,22 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis
 #endif
 
                if (set_reuse_addr(sockfd)) {
+                       logerror("Could not set SO_REUSEADDR: %s", strerror(errno));
                        close(sockfd);
                        continue;
                }
 
                if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
+                       logerror("Could not bind to %s: %s",
+                                ip2str(ai->ai_family, ai->ai_addr, ai->ai_addrlen),
+                                strerror(errno));
                        close(sockfd);
                        continue;       /* not fatal */
                }
                if (listen(sockfd, 5) < 0) {
+                       logerror("Could not listen to %s: %s",
+                                ip2str(ai->ai_family, ai->ai_addr, ai->ai_addrlen),
+                                strerror(errno));
                        close(sockfd);
                        continue;       /* not fatal */
                }
@@ -835,16 +874,23 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis
                return 0;
 
        if (set_reuse_addr(sockfd)) {
+               logerror("Could not set SO_REUSEADDR: %s", strerror(errno));
                close(sockfd);
                return 0;
        }
 
        if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) {
+               logerror("Could not listen to %s: %s",
+                        ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)),
+                        strerror(errno));
                close(sockfd);
                return 0;
        }
 
        if (listen(sockfd, 5) < 0) {
+               logerror("Could not listen to %s: %s",
+                        ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)),
+                        strerror(errno));
                close(sockfd);
                return 0;
        }
@@ -1053,6 +1099,8 @@ int main(int argc, char **argv)
        struct credentials *cred = NULL;
        int i;
 
+       git_setup_gettext();
+
        git_extract_argv0_path(argv[0]);
 
        for (i = 1; i < argc; i++) {
@@ -1167,6 +1215,14 @@ int main(int argc, char **argv)
                        make_service_overridable(arg + 18, 0);
                        continue;
                }
+               if (!prefixcmp(arg, "--informative-errors")) {
+                       informative_errors = 1;
+                       continue;
+               }
+               if (!prefixcmp(arg, "--no-informative-errors")) {
+                       informative_errors = 0;
+                       continue;
+               }
                if (!strcmp(arg, "--")) {
                        ok_paths = &argv[i+1];
                        break;
diff --git a/date.c b/date.c
index 353e0a5e53f13c36549e65452931f254de3423c4..a5055ca09dc1fafce2b9434c4fda02ad4f8e117f 100644 (file)
--- a/date.c
+++ b/date.c
@@ -597,6 +597,33 @@ static int date_string(unsigned long date, int offset, char *buf, int len)
        return snprintf(buf, len, "%lu %c%02d%02d", date, sign, offset / 60, offset % 60);
 }
 
+/*
+ * Parse a string like "0 +0000" as ancient timestamp near epoch, but
+ * only when it appears not as part of any other string.
+ */
+static int match_object_header_date(const char *date, unsigned long *timestamp, int *offset)
+{
+       char *end;
+       unsigned long stamp;
+       int ofs;
+
+       if (*date < '0' || '9' <= *date)
+               return -1;
+       stamp = strtoul(date, &end, 10);
+       if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))
+               return -1;
+       date = end + 2;
+       ofs = strtol(date, &end, 10);
+       if ((*end != '\0' && (*end != '\n')) || end != date + 4)
+               return -1;
+       ofs = (ofs / 100) * 60 + (ofs % 100);
+       if (date[-1] == '-')
+               ofs = -ofs;
+       *timestamp = stamp;
+       *offset = ofs;
+       return 0;
+}
+
 /* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
    (i.e. English) day/month names, and it doesn't work correctly with %z. */
 int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)
@@ -622,6 +649,9 @@ int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)
        *offset = -1;
        tm_gmt = 0;
 
+       if (*date == '@' &&
+           !match_object_header_date(date + 1, timestamp, offset))
+               return 0; /* success */
        for (;;) {
                int match = 0;
                unsigned char c = *date;
diff --git a/diff.c b/diff.c
index d922b77aef2da84824a8e14fc21961e36e6d2e36..5388ded2140d9d4268fbda702603902bf4530981 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -1113,6 +1113,15 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                        diff_words_append(line, len,
                                          &ecbdata->diff_words->plus);
                        return;
+               } else if (!prefixcmp(line, "\\ ")) {
+                       /*
+                        * Eat the "no newline at eof" marker as if we
+                        * saw a "+" or "-" line with nothing on it,
+                        * and return without diff_words_flush() to
+                        * defer processing. If this is the end of
+                        * preimage, more "+" lines may come after it.
+                        */
+                       return;
                }
                diff_words_flush(ecbdata);
                if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
@@ -1267,13 +1276,15 @@ const char mime_boundary_leader[] = "------------";
 
 static int scale_linear(int it, int width, int max_change)
 {
+       if (!it)
+               return 0;
        /*
-        * make sure that at least one '-' is printed if there were deletions,
-        * and likewise for '+'.
+        * make sure that at least one '-' or '+' is printed if
+        * there is any change to this path. The easiest way is to
+        * scale linearly as if the alloted width is one column shorter
+        * than it is, and then add 1 to the result.
         */
-       if (max_change < 2)
-               return it;
-       return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
+       return 1 + (it * (width - 1) / max_change);
 }
 
 static void show_name(FILE *file,
@@ -1313,6 +1324,55 @@ static void fill_print_name(struct diffstat_file *file)
        file->print_name = pname;
 }
 
+int print_stat_summary(FILE *fp, int files, int insertions, int deletions)
+{
+       struct strbuf sb = STRBUF_INIT;
+       int ret;
+
+       if (!files) {
+               assert(insertions == 0 && deletions == 0);
+               return fputs(_(" 0 files changed\n"), fp);
+       }
+
+       strbuf_addf(&sb,
+                   Q_(" %d file changed", " %d files changed", files),
+                   files);
+
+       /*
+        * For binary diff, the caller may want to print "x files
+        * changed" with insertions == 0 && deletions == 0.
+        *
+        * Not omitting "0 insertions(+), 0 deletions(-)" in this case
+        * is probably less confusing (i.e skip over "2 files changed
+        * but nothing about added/removed lines? Is this a bug in Git?").
+        */
+       if (insertions || deletions == 0) {
+               /*
+                * TRANSLATORS: "+" in (+) is a line addition marker;
+                * do not translate it.
+                */
+               strbuf_addf(&sb,
+                           Q_(", %d insertion(+)", ", %d insertions(+)",
+                              insertions),
+                           insertions);
+       }
+
+       if (deletions || insertions == 0) {
+               /*
+                * TRANSLATORS: "-" in (-) is a line removal marker;
+                * do not translate it.
+                */
+               strbuf_addf(&sb,
+                           Q_(", %d deletion(-)", ", %d deletions(-)",
+                              deletions),
+                           deletions);
+       }
+       strbuf_addch(&sb, '\n');
+       ret = fputs(sb.buf, fp);
+       strbuf_release(&sb);
+       return ret;
+}
+
 static void show_stats(struct diffstat_t *data, struct diff_options *options)
 {
        int i, len, add, del, adds = 0, dels = 0;
@@ -1440,8 +1500,19 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                dels += del;
 
                if (width <= max_change) {
-                       add = scale_linear(add, width, max_change);
-                       del = scale_linear(del, width, max_change);
+                       int total = add + del;
+
+                       total = scale_linear(add + del, width, max_change);
+                       if (total < 2 && add && del)
+                               /* width >= 2 due to the sanity check */
+                               total = 2;
+                       if (add < del) {
+                               add = scale_linear(add, width, max_change);
+                               del = total - add;
+                       } else {
+                               del = scale_linear(del, width, max_change);
+                               add = total - del;
+                       }
                }
                fprintf(options->file, "%s", line_prefix);
                show_name(options->file, prefix, name, len);
@@ -1466,9 +1537,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                extra_shown = 1;
        }
        fprintf(options->file, "%s", line_prefix);
-       fprintf(options->file,
-              " %d files changed, %d insertions(+), %d deletions(-)\n",
-              total_files, adds, dels);
+       print_stat_summary(options->file, total_files, adds, dels);
 }
 
 static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
@@ -1498,8 +1567,7 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option
                                options->output_prefix_data);
                fprintf(options->file, "%s", msg->buf);
        }
-       fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n",
-              total_files, adds, dels);
+       print_stat_summary(options->file, total_files, adds, dels);
 }
 
 static void show_numstat(struct diffstat_t *data, struct diff_options *options)
@@ -2142,7 +2210,7 @@ static void builtin_diff(const char *name_a,
                struct emit_callback ecbdata;
                const struct userdiff_funcname *pe;
 
-               if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) {
+               if (must_show_header) {
                        fprintf(o->file, "%s", header.buf);
                        strbuf_reset(&header);
                }
@@ -2169,6 +2237,8 @@ static void builtin_diff(const char *name_a,
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
+               if (DIFF_OPT_TST(o, FUNCCONTEXT))
+                       xecfg.flags |= XDL_EMIT_FUNCCONTEXT;
                if (pe)
                        xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
                if (!diffopts)
@@ -3536,6 +3606,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (opt_arg(arg, '\0', "inter-hunk-context",
                         &options->interhunkcontext))
                ;
+       else if (!strcmp(arg, "-W"))
+               DIFF_OPT_SET(options, FUNCCONTEXT);
+       else if (!strcmp(arg, "--function-context"))
+               DIFF_OPT_SET(options, FUNCCONTEXT);
+       else if (!strcmp(arg, "--no-function-context"))
+               DIFF_OPT_CLR(options, FUNCCONTEXT);
        else if ((argcount = parse_long_opt("output", av, &optarg))) {
                options->file = fopen(optarg, "w");
                if (!options->file)
diff --git a/diff.h b/diff.h
index 8c66b59517305546d3e2f66fca652284468e1da1..7af5f1e2a7af3fde64ac945f8b2a9ed88829895a 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -12,6 +12,8 @@ struct diff_queue_struct;
 struct strbuf;
 struct diff_filespec;
 struct userdiff_driver;
+struct sha1_array;
+struct commit;
 
 typedef void (*change_fn_t)(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
@@ -79,6 +81,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
 #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
 #define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27)
 #define DIFF_OPT_DIRSTAT_BY_LINE     (1 << 28)
+#define DIFF_OPT_FUNCCONTEXT         (1 << 29)
 
 #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
 #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)
@@ -194,9 +197,9 @@ struct combine_diff_path {
 extern void show_combined_diff(struct combine_diff_path *elem, int num_parent,
                              int dense, struct rev_info *);
 
-extern void diff_tree_combined(const unsigned char *sha1, const unsigned char parent[][20], int num_parent, int dense, struct rev_info *rev);
+extern void diff_tree_combined(const unsigned char *sha1, const struct sha1_array *parents, int dense, struct rev_info *rev);
 
-extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_info *);
+extern void diff_tree_combined_merge(const struct commit *commit, int dense, struct rev_info *rev);
 
 void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b);
 
@@ -321,4 +324,7 @@ extern struct userdiff_driver *get_textconv(struct diff_filespec *one);
 
 extern int parse_rename_score(const char **cp_p);
 
+extern int print_stat_summary(FILE *fp, int files,
+                             int insertions, int deletions);
+
 #endif /* DIFF_H */
index c3760cfefd5dd123481b869eac3081b6e7d93201..380a837b5b136ae88d374ae8a7326c87597107fc 100644 (file)
@@ -8,6 +8,46 @@
 #include "xdiff-interface.h"
 #include "kwset.h"
 
+typedef int (*pickaxe_fn)(struct diff_filepair *p, struct diff_options *o, regex_t *regexp, kwset_t kws);
+
+static void pickaxe(struct diff_queue_struct *q, struct diff_options *o,
+                   regex_t *regexp, kwset_t kws, pickaxe_fn fn)
+{
+       int i;
+       struct diff_queue_struct outq;
+
+       DIFF_QUEUE_CLEAR(&outq);
+
+       if (o->pickaxe_opts & DIFF_PICKAXE_ALL) {
+               /* Showing the whole changeset if needle exists */
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (fn(p, o, regexp, kws))
+                               return; /* do not munge the queue */
+               }
+
+               /*
+                * Otherwise we will clear the whole queue by copying
+                * the empty outq at the end of this function, but
+                * first clear the current entries in the queue.
+                */
+               for (i = 0; i < q->nr; i++)
+                       diff_free_filepair(q->queue[i]);
+       } else {
+               /* Showing only the filepairs that has the needle */
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (fn(p, o, regexp, kws))
+                               diff_q(&outq, p);
+                       else
+                               diff_free_filepair(p);
+               }
+       }
+
+       free(q->queue);
+       *q = outq;
+}
+
 struct diffgrep_cb {
        regex_t *regexp;
        int hit;
@@ -45,7 +85,8 @@ static void fill_one(struct diff_filespec *one,
        }
 }
 
-static int diff_grep(struct diff_filepair *p, regex_t *regexp, struct diff_options *o)
+static int diff_grep(struct diff_filepair *p, struct diff_options *o,
+                    regex_t *regexp, kwset_t kws)
 {
        regmatch_t regmatch;
        struct userdiff_driver *textconv_one = NULL;
@@ -95,12 +136,8 @@ static int diff_grep(struct diff_filepair *p, regex_t *regexp, struct diff_optio
 
 static void diffcore_pickaxe_grep(struct diff_options *o)
 {
-       struct diff_queue_struct *q = &diff_queued_diff;
-       int i, has_changes, err;
+       int err;
        regex_t regex;
-       struct diff_queue_struct outq;
-       outq.queue = NULL;
-       outq.nr = outq.alloc = 0;
 
        err = regcomp(&regex, o->pickaxe, REG_EXTENDED | REG_NEWLINE);
        if (err) {
@@ -110,51 +147,21 @@ static void diffcore_pickaxe_grep(struct diff_options *o)
                die("invalid log-grep regex: %s", errbuf);
        }
 
-       if (o->pickaxe_opts & DIFF_PICKAXE_ALL) {
-               /* Showing the whole changeset if needle exists */
-               for (i = has_changes = 0; !has_changes && i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       if (diff_grep(p, &regex, o))
-                               has_changes++;
-               }
-               if (has_changes)
-                       return; /* do not munge the queue */
-
-               /*
-                * Otherwise we will clear the whole queue by copying
-                * the empty outq at the end of this function, but
-                * first clear the current entries in the queue.
-                */
-               for (i = 0; i < q->nr; i++)
-                       diff_free_filepair(q->queue[i]);
-       } else {
-               /* Showing only the filepairs that has the needle */
-               for (i = 0; i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       if (diff_grep(p, &regex, o))
-                               diff_q(&outq, p);
-                       else
-                               diff_free_filepair(p);
-               }
-       }
+       pickaxe(&diff_queued_diff, o, &regex, NULL, diff_grep);
 
        regfree(&regex);
-
-       free(q->queue);
-       *q = outq;
        return;
 }
 
-static unsigned int contains(struct diff_filespec *one,
-                            const char *needle, unsigned long len,
+static unsigned int contains(struct diff_filespec *one, struct diff_options *o,
                             regex_t *regexp, kwset_t kws)
 {
        unsigned int cnt;
        unsigned long sz;
        const char *data;
-       if (diff_populate_filespec(one, 0))
+       if (!o->pickaxe[0])
                return 0;
-       if (!len)
+       if (diff_populate_filespec(one, 0))
                return 0;
 
        sz = one->size;
@@ -176,14 +183,15 @@ static unsigned int contains(struct diff_filespec *one,
 
        } else { /* Classic exact string match */
                while (sz) {
-                       size_t offset = kwsexec(kws, data, sz, NULL);
+                       struct kwsmatch kwsm;
+                       size_t offset = kwsexec(kws, data, sz, &kwsm);
                        const char *found;
                        if (offset == -1)
                                break;
                        else
                                found = data + offset;
-                       sz -= found - data + len;
-                       data = found + len;
+                       sz -= found - data + kwsm.size[0];
+                       data = found + kwsm.size[0];
                        cnt++;
                }
        }
@@ -191,17 +199,31 @@ static unsigned int contains(struct diff_filespec *one,
        return cnt;
 }
 
+static int has_changes(struct diff_filepair *p, struct diff_options *o,
+                      regex_t *regexp, kwset_t kws)
+{
+       if (!DIFF_FILE_VALID(p->one)) {
+               if (!DIFF_FILE_VALID(p->two))
+                       return 0; /* ignore unmerged */
+               /* created */
+               return contains(p->two, o, regexp, kws) != 0;
+       }
+       if (!DIFF_FILE_VALID(p->two))
+               return contains(p->one, o, regexp, kws) != 0;
+       if (!diff_unmodified_pair(p)) {
+               return contains(p->one, o, regexp, kws) !=
+                      contains(p->two, o, regexp, kws);
+       }
+       return 0;
+}
+
 static void diffcore_pickaxe_count(struct diff_options *o)
 {
        const char *needle = o->pickaxe;
        int opts = o->pickaxe_opts;
-       struct diff_queue_struct *q = &diff_queued_diff;
        unsigned long len = strlen(needle);
-       int i, has_changes;
        regex_t regex, *regexp = NULL;
        kwset_t kws = NULL;
-       struct diff_queue_struct outq;
-       DIFF_QUEUE_CLEAR(&outq);
 
        if (opts & DIFF_PICKAXE_REGEX) {
                int err;
@@ -220,72 +242,12 @@ static void diffcore_pickaxe_count(struct diff_options *o)
                kwsprep(kws);
        }
 
-       if (opts & DIFF_PICKAXE_ALL) {
-               /* Showing the whole changeset if needle exists */
-               for (i = has_changes = 0; !has_changes && i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       if (!DIFF_FILE_VALID(p->one)) {
-                               if (!DIFF_FILE_VALID(p->two))
-                                       continue; /* ignore unmerged */
-                               /* created */
-                               if (contains(p->two, needle, len, regexp, kws))
-                                       has_changes++;
-                       }
-                       else if (!DIFF_FILE_VALID(p->two)) {
-                               if (contains(p->one, needle, len, regexp, kws))
-                                       has_changes++;
-                       }
-                       else if (!diff_unmodified_pair(p) &&
-                                contains(p->one, needle, len, regexp, kws) !=
-                                contains(p->two, needle, len, regexp, kws))
-                               has_changes++;
-               }
-               if (has_changes)
-                       return; /* not munge the queue */
-
-               /* otherwise we will clear the whole queue
-                * by copying the empty outq at the end of this
-                * function, but first clear the current entries
-                * in the queue.
-                */
-               for (i = 0; i < q->nr; i++)
-                       diff_free_filepair(q->queue[i]);
-       }
-       else
-               /* Showing only the filepairs that has the needle */
-               for (i = 0; i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       has_changes = 0;
-                       if (!DIFF_FILE_VALID(p->one)) {
-                               if (!DIFF_FILE_VALID(p->two))
-                                       ; /* ignore unmerged */
-                               /* created */
-                               else if (contains(p->two, needle, len, regexp,
-                                                 kws))
-                                       has_changes = 1;
-                       }
-                       else if (!DIFF_FILE_VALID(p->two)) {
-                               if (contains(p->one, needle, len, regexp, kws))
-                                       has_changes = 1;
-                       }
-                       else if (!diff_unmodified_pair(p) &&
-                                contains(p->one, needle, len, regexp, kws) !=
-                                contains(p->two, needle, len, regexp, kws))
-                               has_changes = 1;
-
-                       if (has_changes)
-                               diff_q(&outq, p);
-                       else
-                               diff_free_filepair(p);
-               }
+       pickaxe(&diff_queued_diff, o, regexp, kws, has_changes);
 
        if (opts & DIFF_PICKAXE_REGEX)
                regfree(&regex);
        else
                kwsfree(kws);
-
-       free(q->queue);
-       *q = outq;
        return;
 }
 
diff --git a/dir.c b/dir.c
index 08281d2ef74ea7790913e71f08d00299a1825765..0a78d00b545ac4f302ea89b6393773669907599e 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -34,49 +34,54 @@ int fnmatch_icase(const char *pattern, const char *string, int flags)
        return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
 }
 
-static int common_prefix(const char **pathspec)
+static size_t common_prefix_len(const char **pathspec)
 {
-       const char *path, *slash, *next;
-       int prefix;
+       const char *n, *first;
+       size_t max = 0;
 
        if (!pathspec)
-               return 0;
+               return max;
+
+       first = *pathspec;
+       while ((n = *pathspec++)) {
+               size_t i, len = 0;
+               for (i = 0; first == n || i < max; i++) {
+                       char c = n[i];
+                       if (!c || c != first[i] || is_glob_special(c))
+                               break;
+                       if (c == '/')
+                               len = i + 1;
+               }
+               if (first == n || len < max) {
+                       max = len;
+                       if (!max)
+                               break;
+               }
+       }
+       return max;
+}
 
-       path = *pathspec;
-       slash = strrchr(path, '/');
-       if (!slash)
-               return 0;
+/*
+ * Returns a copy of the longest leading path common among all
+ * pathspecs.
+ */
+char *common_prefix(const char **pathspec)
+{
+       unsigned long len = common_prefix_len(pathspec);
 
-       /*
-        * The first 'prefix' characters of 'path' are common leading
-        * path components among the pathspecs we have seen so far,
-        * including the trailing slash.
-        */
-       prefix = slash - path + 1;
-       while ((next = *++pathspec) != NULL) {
-               int len, last_matching_slash = -1;
-               for (len = 0; len < prefix && next[len] == path[len]; len++)
-                       if (next[len] == '/')
-                               last_matching_slash = len;
-               if (len == prefix)
-                       continue;
-               if (last_matching_slash < 0)
-                       return 0;
-               prefix = last_matching_slash + 1;
-       }
-       return prefix;
+       return len ? xmemdupz(*pathspec, len) : NULL;
 }
 
 int fill_directory(struct dir_struct *dir, const char **pathspec)
 {
        const char *path;
-       int len;
+       size_t len;
 
        /*
         * Calculate common prefix for the pathspec, and
         * use that to optimize the directory walk
         */
-       len = common_prefix(pathspec);
+       len = common_prefix_len(pathspec);
        path = "";
 
        if (len)
@@ -84,6 +89,8 @@ int fill_directory(struct dir_struct *dir, const char **pathspec)
 
        /* Read the directory and prune it */
        read_directory(dir, path, len, pathspec);
+       if (*path)
+               free((char *)path);
        return len;
 }
 
@@ -961,34 +968,34 @@ static int read_directory_recursive(struct dir_struct *dir,
 {
        DIR *fdir = opendir(*base ? base : ".");
        int contents = 0;
+       struct dirent *de;
+       char path[PATH_MAX + 1];
 
-       if (fdir) {
-               struct dirent *de;
-               char path[PATH_MAX + 1];
-               memcpy(path, base, baselen);
-
-               while ((de = readdir(fdir)) != NULL) {
-                       int len;
-                       switch (treat_path(dir, de, path, sizeof(path),
-                                          baselen, simplify, &len)) {
-                       case path_recurse:
-                               contents += read_directory_recursive
-                                       (dir, path, len, 0, simplify);
-                               continue;
-                       case path_ignored:
-                               continue;
-                       case path_handled:
-                               break;
-                       }
-                       contents++;
-                       if (check_only)
-                               goto exit_early;
-                       else
-                               dir_add_name(dir, path, len);
+       if (!fdir)
+               return 0;
+
+       memcpy(path, base, baselen);
+
+       while ((de = readdir(fdir)) != NULL) {
+               int len;
+               switch (treat_path(dir, de, path, sizeof(path),
+                                  baselen, simplify, &len)) {
+               case path_recurse:
+                       contents += read_directory_recursive(dir, path, len, 0, simplify);
+                       continue;
+               case path_ignored:
+                       continue;
+               case path_handled:
+                       break;
                }
-exit_early:
-               closedir(fdir);
+               contents++;
+               if (check_only)
+                       goto exit_early;
+               else
+                       dir_add_name(dir, path, len);
        }
+exit_early:
+       closedir(fdir);
 
        return contents;
 }
diff --git a/dir.h b/dir.h
index 433b5b4cd4c51e9b7557d3056570ed46b7ceba92..dd6947e1d46098732ff1d8c3f23087c1e7163fe9 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -64,6 +64,7 @@ struct dir_struct {
 #define MATCHED_RECURSIVELY 1
 #define MATCHED_FNMATCH 2
 #define MATCHED_EXACTLY 3
+extern char *common_prefix(const char **pathspec);
 extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
 extern int match_pathspec_depth(const struct pathspec *pathspec,
                                const char *name, int namelen,
index d60b73f6a5a88632a2a36140bf719484adec9f39..c93b8f44df0171a0f923546813d00e8b8e837af1 100644 (file)
@@ -9,6 +9,7 @@
  */
 #include "cache.h"
 #include "refs.h"
+#include "fmt-merge-msg.h"
 
 char git_default_email[MAX_GITNAME];
 char git_default_name[MAX_GITNAME];
@@ -59,7 +60,9 @@ enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
 char *notes_ref_name;
 int grafts_replace_parents = 1;
 int core_apply_sparse_checkout;
+int merge_log_config = -1;
 struct startup_info *startup_info;
+unsigned long pack_size_limit_cfg;
 
 /* Parallel index stat data preload? */
 int core_preload_index = 0;
@@ -107,7 +110,7 @@ static char *expand_namespace(const char *raw_namespace)
                if (strcmp((*c)->buf, "/") != 0)
                        strbuf_addf(&buf, "refs/namespaces/%s", (*c)->buf);
        strbuf_list_free(components);
-       if (check_ref_format(buf.buf) != CHECK_REF_FORMAT_OK)
+       if (check_refname_format(buf.buf, 0))
                die("bad git namespace path \"%s\"", raw_namespace);
        strbuf_addch(&buf, '/');
        return strbuf_detach(&buf, NULL);
index 742e7da6b8b58dd0803d89c6ad6c59589274a31b..c1486cabbaa5f736f92994587fafd1f3afdf43af 100644 (file)
@@ -722,13 +722,8 @@ static struct branch *new_branch(const char *name)
 
        if (b)
                die("Invalid attempt to create duplicate branch: %s", name);
-       switch (check_ref_format(name)) {
-       case 0: break; /* its valid */
-       case CHECK_REF_FORMAT_ONELEVEL:
-               break; /* valid, but too few '/', allow anyway */
-       default:
+       if (check_refname_format(name, REFNAME_ALLOW_ONELEVEL))
                die("Branch name doesn't conform to GIT standards: %s", name);
-       }
 
        b = pool_calloc(1, sizeof(struct branch));
        b->name = pool_strdup(name);
@@ -860,15 +855,15 @@ static struct tree_content *dup_tree_content(struct tree_content *s)
 
 static void start_packfile(void)
 {
-       static char tmpfile[PATH_MAX];
+       static char tmp_file[PATH_MAX];
        struct packed_git *p;
        struct pack_header hdr;
        int pack_fd;
 
-       pack_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+       pack_fd = odb_mkstemp(tmp_file, sizeof(tmp_file),
                              "pack/tmp_pack_XXXXXX");
-       p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
-       strcpy(p->pack_name, tmpfile);
+       p = xcalloc(1, sizeof(*p) + strlen(tmp_file) + 2);
+       strcpy(p->pack_name, tmp_file);
        p->pack_fd = pack_fd;
        p->do_not_close = 1;
        pack_file = sha1fd(pack_fd, p->pack_name);
@@ -1148,17 +1143,11 @@ static int store_object(
        return 0;
 }
 
-static void truncate_pack(off_t to, git_SHA_CTX *ctx)
+static void truncate_pack(struct sha1file_checkpoint *checkpoint)
 {
-       if (ftruncate(pack_data->pack_fd, to)
-        || lseek(pack_data->pack_fd, to, SEEK_SET) != to)
+       if (sha1file_truncate(pack_file, checkpoint))
                die_errno("cannot truncate pack to skip duplicate");
-       pack_size = to;
-
-       /* yes this is a layering violation */
-       pack_file->total = to;
-       pack_file->offset = 0;
-       pack_file->ctx = *ctx;
+       pack_size = checkpoint->offset;
 }
 
 static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
@@ -1171,8 +1160,8 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
        unsigned long hdrlen;
        off_t offset;
        git_SHA_CTX c;
-       git_SHA_CTX pack_file_ctx;
        git_zstream s;
+       struct sha1file_checkpoint checkpoint;
        int status = Z_OK;
 
        /* Determine if we should auto-checkpoint. */
@@ -1180,11 +1169,8 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
                || (pack_size + 60 + len) < pack_size)
                cycle_packfile();
 
-       offset = pack_size;
-
-       /* preserve the pack_file SHA1 ctx in case we have to truncate later */
-       sha1flush(pack_file);
-       pack_file_ctx = pack_file->ctx;
+       sha1file_checkpoint(pack_file, &checkpoint);
+       offset = checkpoint.offset;
 
        hdrlen = snprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1;
        if (out_sz <= hdrlen)
@@ -1250,14 +1236,14 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
 
        if (e->idx.offset) {
                duplicate_count_by_type[OBJ_BLOB]++;
-               truncate_pack(offset, &pack_file_ctx);
+               truncate_pack(&checkpoint);
 
        } else if (find_sha1_pack(sha1, packed_git)) {
                e->type = OBJ_BLOB;
                e->pack_id = MAX_PACK_ID;
                e->idx.offset = 1; /* just not zero! */
                duplicate_count_by_type[OBJ_BLOB]++;
-               truncate_pack(offset, &pack_file_ctx);
+               truncate_pack(&checkpoint);
 
        } else {
                e->depth = 0;
@@ -2178,6 +2164,11 @@ static uintmax_t do_change_note_fanout(
 
                if (tmp_hex_sha1_len == 40 && !get_sha1_hex(hex_sha1, sha1)) {
                        /* This is a note entry */
+                       if (fanout == 0xff) {
+                               /* Counting mode, no rename */
+                               num_notes++;
+                               continue;
+                       }
                        construct_path_with_fanout(hex_sha1, fanout, realpath);
                        if (!strcmp(fullpath, realpath)) {
                                /* Note entry is in correct location */
@@ -2384,7 +2375,7 @@ static void file_change_cr(struct branch *b, int rename)
                leaf.tree);
 }
 
-static void note_change_n(struct branch *b, unsigned char old_fanout)
+static void note_change_n(struct branch *b, unsigned char *old_fanout)
 {
        const char *p = command_buf.buf + 2;
        static struct strbuf uq = STRBUF_INIT;
@@ -2395,6 +2386,23 @@ static void note_change_n(struct branch *b, unsigned char old_fanout)
        uint16_t inline_data = 0;
        unsigned char new_fanout;
 
+       /*
+        * When loading a branch, we don't traverse its tree to count the real
+        * number of notes (too expensive to do this for all non-note refs).
+        * This means that recently loaded notes refs might incorrectly have
+        * b->num_notes == 0, and consequently, old_fanout might be wrong.
+        *
+        * Fix this by traversing the tree and counting the number of notes
+        * when b->num_notes == 0. If the notes tree is truly empty, the
+        * calculation should not take long.
+        */
+       if (b->num_notes == 0 && *old_fanout == 0) {
+               /* Invoke change_note_fanout() in "counting mode". */
+               b->num_notes = change_note_fanout(&b->branch_tree, 0xff);
+               *old_fanout = convert_num_notes_to_fanout(b->num_notes);
+       }
+
+       /* Now parse the notemodify command. */
        /* <dataref> or 'inline' */
        if (*p == ':') {
                char *x;
@@ -2416,6 +2424,8 @@ static void note_change_n(struct branch *b, unsigned char old_fanout)
        /* <committish> */
        s = lookup_branch(p);
        if (s) {
+               if (is_null_sha1(s->sha1))
+                       die("Can't add a note on empty branch.");
                hashcpy(commit_sha1, s->sha1);
        } else if (*p == ':') {
                uintmax_t commit_mark = strtoumax(p + 1, NULL, 10);
@@ -2453,7 +2463,7 @@ static void note_change_n(struct branch *b, unsigned char old_fanout)
                            typename(type), command_buf.buf);
        }
 
-       construct_path_with_fanout(sha1_to_hex(commit_sha1), old_fanout, path);
+       construct_path_with_fanout(sha1_to_hex(commit_sha1), *old_fanout, path);
        if (tree_content_remove(&b->branch_tree, path, NULL))
                b->num_notes--;
 
@@ -2640,7 +2650,7 @@ static void parse_new_commit(void)
                else if (!prefixcmp(command_buf.buf, "C "))
                        file_change_cr(b, 0);
                else if (!prefixcmp(command_buf.buf, "N "))
-                       note_change_n(b, prev_fanout);
+                       note_change_n(b, &prev_fanout);
                else if (!strcmp("deleteall", command_buf.buf))
                        file_change_deleteall(b);
                else if (!prefixcmp(command_buf.buf, "ls "))
@@ -2702,7 +2712,7 @@ static void parse_new_tag(void)
        /* Obtain the new tag name from the rest of our command */
        sp = strchr(command_buf.buf, ' ') + 1;
        t = pool_alloc(sizeof(struct tag));
-       t->next_tag = NULL;
+       memset(t, 0, sizeof(struct tag));
        t->name = pool_strdup(sp);
        if (last_tag)
                last_tag->next_tag = t;
@@ -2717,6 +2727,8 @@ static void parse_new_tag(void)
        from = strchr(command_buf.buf, ' ') + 1;
        s = lookup_branch(from);
        if (s) {
+               if (is_null_sha1(s->sha1))
+                       die("Can't tag an empty branch.");
                hashcpy(sha1, s->sha1);
                type = OBJ_COMMIT;
        } else if (*from == ':') {
@@ -3293,6 +3305,8 @@ int main(int argc, const char **argv)
 
        git_extract_argv0_path(argv[0]);
 
+       git_setup_gettext();
+
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage(fast_import_usage);
 
diff --git a/fmt-merge-msg.h b/fmt-merge-msg.h
new file mode 100644 (file)
index 0000000..b28d3a6
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef FMT_MERGE_MSG_H
+#define FMT_MERGE_MSG_H
+
+extern int merge_log_config;
+extern int fmt_merge_msg_config(const char *key, const char *value, void *cb);
+
+#endif /* FMT_MERGE_MSG_H */
index ae5394a49672b6c7fa6c5c69766aa4e80c187c5f..f75bca7f56b7b27c135d92acba816c40211fdece 100644 (file)
--- a/gettext.c
+++ b/gettext.c
@@ -5,6 +5,18 @@
 #include "git-compat-util.h"
 #include "gettext.h"
 
+#ifndef NO_GETTEXT
+#      include <locale.h>
+#      include <libintl.h>
+#      ifdef HAVE_LIBCHARSET_H
+#              include <libcharset.h>
+#      else
+#              include <langinfo.h>
+#              define locale_charset() nl_langinfo(CODESET)
+#      endif
+#endif
+
+#ifdef GETTEXT_POISON
 int use_gettext_poison(void)
 {
        static int poison_requested = -1;
@@ -12,3 +24,108 @@ int use_gettext_poison(void)
                poison_requested = getenv("GIT_GETTEXT_POISON") ? 1 : 0;
        return poison_requested;
 }
+#endif
+
+#ifndef NO_GETTEXT
+static void init_gettext_charset(const char *domain)
+{
+       const char *charset;
+
+       /*
+          This trick arranges for messages to be emitted in the user's
+          requested encoding, but avoids setting LC_CTYPE from the
+          environment for the whole program.
+
+          This primarily done to avoid a bug in vsnprintf in the GNU C
+          Library [1]. which triggered a "your vsnprintf is broken" error
+          on Git's own repository when inspecting v0.99.6~1 under a UTF-8
+          locale.
+
+          That commit contains a ISO-8859-1 encoded author name, which
+          the locale aware vsnprintf(3) won't interpolate in the format
+          argument, due to mismatch between the data encoding and the
+          locale.
+
+          Even if it wasn't for that bug we wouldn't want to use LC_CTYPE at
+          this point, because it'd require auditing all the code that uses C
+          functions whose semantics are modified by LC_CTYPE.
+
+          But only setting LC_MESSAGES as we do creates a problem, since
+          we declare the encoding of our PO files[2] the gettext
+          implementation will try to recode it to the user's locale, but
+          without LC_CTYPE it'll emit something like this on 'git init'
+          under the Icelandic locale:
+
+              Bj? til t?ma Git lind ? /hlagh/.git/
+
+          Gettext knows about the encoding of our PO file, but we haven't
+          told it about the user's encoding, so all the non-US-ASCII
+          characters get encoded to question marks.
+
+          But we're in luck! We can set LC_CTYPE from the environment
+          only while we call nl_langinfo and
+          bind_textdomain_codeset. That suffices to tell gettext what
+          encoding it should emit in, so it'll now say:
+
+              Bjó til tóma Git lind í /hlagh/.git/
+
+          And the equivalent ISO-8859-1 string will be emitted under a
+          ISO-8859-1 locale.
+
+          With this change way we get the advantages of setting LC_CTYPE
+          (talk to the user in his language/encoding), without the major
+          drawbacks (changed semantics for C functions we rely on).
+
+          However foreign functions using other message catalogs that
+          aren't using our neat trick will still have a problem, e.g. if
+          we have to call perror(3):
+
+          #include <stdio.h>
+          #include <locale.h>
+          #include <errno.h>
+
+          int main(void)
+          {
+                  setlocale(LC_MESSAGES, "");
+                  setlocale(LC_CTYPE, "C");
+                  errno = ENODEV;
+                  perror("test");
+                  return 0;
+          }
+
+          Running that will give you a message with question marks:
+
+          $ LANGUAGE= LANG=de_DE.utf8 ./test
+          test: Kein passendes Ger?t gefunden
+
+          In the long term we should probably see about getting that
+          vsnprintf bug in glibc fixed, and audit our code so it won't
+          fall apart under a non-C locale.
+
+          Then we could simply set LC_CTYPE from the environment, which would
+          make things like the external perror(3) messages work.
+
+          See t/t0203-gettext-setlocale-sanity.sh's "gettext.c" tests for
+          regression tests.
+
+          1. http://sourceware.org/bugzilla/show_bug.cgi?id=6530
+          2. E.g. "Content-Type: text/plain; charset=UTF-8\n" in po/is.po
+       */
+       setlocale(LC_CTYPE, "");
+       charset = locale_charset();
+       bind_textdomain_codeset(domain, charset);
+       setlocale(LC_CTYPE, "C");
+}
+
+void git_setup_gettext(void)
+{
+       const char *podir = getenv("GIT_TEXTDOMAINDIR");
+
+       if (!podir)
+               podir = GIT_LOCALE_PATH;
+       bindtextdomain("git", podir);
+       setlocale(LC_MESSAGES, "");
+       init_gettext_charset("git");
+       textdomain("git");
+}
+#endif
index 24d91824e5a810cb3f2cbc4ca0514ec68725597d..57ba8bb02e39d59752a5b2fbf016bc6fe49d27c1 100644 (file)
--- a/gettext.h
+++ b/gettext.h
 #error "namespace conflict: '_' or 'Q_' is pre-defined?"
 #endif
 
+#ifndef NO_GETTEXT
+#      include <libintl.h>
+#else
+#      ifdef gettext
+#              undef gettext
+#      endif
+#      define gettext(s) (s)
+#      ifdef ngettext
+#              undef ngettext
+#      endif
+#      define ngettext(s, p, n) ((n == 1) ? (s) : (p))
+#endif
+
 #define FORMAT_PRESERVING(n) __attribute__((format_arg(n)))
 
+#ifndef NO_GETTEXT
+extern void git_setup_gettext(void);
+#else
+static inline void git_setup_gettext(void)
+{
+}
+#endif
+
 #ifdef GETTEXT_POISON
 extern int use_gettext_poison(void);
 #else
@@ -23,7 +44,7 @@ extern int use_gettext_poison(void);
 
 static inline FORMAT_PRESERVING(1) const char *_(const char *msgid)
 {
-       return use_gettext_poison() ? "# GETTEXT POISON #" : msgid;
+       return use_gettext_poison() ? "# GETTEXT POISON #" : gettext(msgid);
 }
 
 static inline FORMAT_PRESERVING(1) FORMAT_PRESERVING(2)
@@ -31,7 +52,7 @@ const char *Q_(const char *msgid, const char *plu, unsigned long n)
 {
        if (use_gettext_poison())
                return "# GETTEXT POISON #";
-       return n == 1 ? msgid : plu;
+       return ngettext(msgid, plu, n);
 }
 
 /* Mark msgid for translation but do not translate it. */
index 94f853fbd4228cfc298b3a47c6b4a2515f825ac5..cb833e217ae90ebfe2f5ed1710d4372906bdfe3e 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -127,15 +127,18 @@ fall_back_3way () {
     mkdir "$dotest/patch-merge-tmp-dir"
 
     # First see if the patch records the index info that we can use.
-    git apply --build-fake-ancestor "$dotest/patch-merge-tmp-index" \
-       "$dotest/patch" &&
+    cmd="git apply $git_apply_opt --build-fake-ancestor" &&
+    cmd="$cmd "'"$dotest/patch-merge-tmp-index" "$dotest/patch"' &&
+    eval "$cmd" &&
     GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
     git write-tree >"$dotest/patch-merge-base+" ||
     cannot_fallback "$(gettext "Repository lacks necessary blobs to fall back on 3-way merge.")"
 
     say Using index info to reconstruct a base tree...
-    if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
-       git apply --cached <"$dotest/patch"
+
+    cmd='GIT_INDEX_FILE="$dotest/patch-merge-tmp-index"'
+    cmd="$cmd git apply --cached $git_apply_opt"' <"$dotest/patch"'
+    if eval "$cmd"
     then
        mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
        mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
@@ -201,7 +204,7 @@ check_patch_format () {
                l1=
                while test -z "$l1"
                do
-                       read l1
+                       read l1 || break
                done
                read l2
                read l3
@@ -311,6 +314,40 @@ split_patches () {
                this=
                msgnum=
                ;;
+       hg)
+               this=0
+               for hg in "$@"
+               do
+                       this=$(( $this + 1 ))
+                       msgnum=$(printf "%0${prec}d" $this)
+                       # hg stores changeset metadata in #-commented lines preceding
+                       # the commit message and diff(s). The only metadata we care about
+                       # are the User and Date (Node ID and Parent are hashes which are
+                       # only relevant to the hg repository and thus not useful to us)
+                       # Since we cannot guarantee that the commit message is in
+                       # git-friendly format, we put no Subject: line and just consume
+                       # all of the message as the body
+                       perl -M'POSIX qw(strftime)' -ne 'BEGIN { $subject = 0 }
+                               if ($subject) { print ; }
+                               elsif (/^\# User /) { s/\# User/From:/ ; print ; }
+                               elsif (/^\# Date /) {
+                                       my ($hashsign, $str, $time, $tz) = split ;
+                                       $tz = sprintf "%+05d", (0-$tz)/36;
+                                       print "Date: " .
+                                             strftime("%a, %d %b %Y %H:%M:%S ",
+                                                      localtime($time))
+                                             . "$tz\n";
+                               } elsif (/^\# /) { next ; }
+                               else {
+                                       print "\n", $_ ;
+                                       $subject = 1;
+                               }
+                       ' <"$hg" >"$dotest/$msgnum" || clean_abort
+               done
+               echo "$this" >"$dotest/last"
+               this=
+               msgnum=
+               ;;
        *)
                if test -n "$patch_format"
                then
index 5ef8ff76f6fd262c3109d25d706ebd3db35c73fa..426ae43be9cd7aa4230888ff0aa28ed2fd165ce0 100644 (file)
 #else
 #include <poll.h>
 #endif
-#ifndef __MINGW32__
+#if defined(__MINGW32__)
+/* pull in Windows compatibility stuff */
+#include "compat/mingw.h"
+#elif defined(_MSC_VER)
+#include "compat/msvc.h"
+#else
 #include <sys/wait.h>
 #include <sys/resource.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <netdb.h>
 #include <pwd.h>
+#include <sys/un.h>
 #ifndef NO_INTTYPES_H
 #include <inttypes.h>
 #else
 #include <grp.h>
 #define _ALL_SOURCE 1
 #endif
-#else  /* __MINGW32__ */
-/* pull in Windows compatibility stuff */
-#include "compat/mingw.h"
-#endif /* __MINGW32__ */
-#ifdef _MSC_VER
-#include "compat/msvc.h"
 #endif
 
 #ifndef NO_LIBGEN_H
@@ -219,7 +219,7 @@ extern char *gitbasename(char *);
 #define find_last_dir_sep(path) strrchr(path, '/')
 #endif
 
-#if __HP_cc >= 61000
+#if defined(__HP_cc) && (__HP_cc >= 61000)
 #define NORETURN __attribute__((noreturn))
 #define NORETURN_PTR
 #elif defined(__GNUC__) && !defined(NO_NORETURN)
@@ -351,6 +351,8 @@ extern size_t gitstrlcpy(char *, const char *, size_t);
 #ifdef NO_STRTOUMAX
 #define strtoumax gitstrtoumax
 extern uintmax_t gitstrtoumax(const char *, char **, int);
+#define strtoimax gitstrtoimax
+extern intmax_t gitstrtoimax(const char *, char **, int);
 #endif
 
 #ifdef NO_STRTOK_R
@@ -461,6 +463,8 @@ static inline int has_extension(const char *filename, const char *ext)
 #undef isdigit
 #undef isalpha
 #undef isalnum
+#undef islower
+#undef isupper
 #undef tolower
 #undef toupper
 extern unsigned char sane_ctype[256];
@@ -476,6 +480,8 @@ extern unsigned char sane_ctype[256];
 #define isdigit(x) sane_istest(x,GIT_DIGIT)
 #define isalpha(x) sane_istest(x,GIT_ALPHA)
 #define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
+#define islower(x) sane_iscase(x, 1)
+#define isupper(x) sane_iscase(x, 0)
 #define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL)
 #define is_regex_special(x) sane_istest(x,GIT_GLOB_SPECIAL | GIT_REGEX_SPECIAL)
 #define tolower(x) sane_case((unsigned char)(x), 0x20)
@@ -489,6 +495,17 @@ static inline int sane_case(int x, int high)
        return x;
 }
 
+static inline int sane_iscase(int x, int is_lower)
+{
+       if (!sane_istest(x, GIT_ALPHA))
+               return 0;
+
+       if (is_lower)
+               return (x & 0x20) != 0;
+       else
+               return (x & 0x20) == 0;
+}
+
 static inline int strtoul_ui(char const *s, int base, unsigned int *result)
 {
        unsigned long ul;
index 39a426e067c76e01d3f2a0d63243ec494436da46..e6bf25232c4ec27fc98f78e076678ae4d0a524c1 100755 (executable)
@@ -30,6 +30,13 @@ if ($opt_w || $opt_W) {
                chomp($gd);
                $ENV{GIT_DIR} = $gd;
        }
+
+       # On MSYS, convert a Windows-style path to an MSYS-style path
+       # so that rel2abs() below works correctly.
+       if ($^O eq 'msys') {
+               $ENV{GIT_DIR} =~ s#^([[:alpha:]]):/#/$1/#;
+       }
+
        # Make sure GIT_DIR is absolute
        $ENV{GIT_DIR} = File::Spec->rel2abs($ENV{GIT_DIR});
 }
index 1b8bff2cac163a6588df397168f57214c30b4784..b8eddabc9477ea3ecc6311d88443109041a55c3c 100755 (executable)
@@ -109,14 +109,14 @@ my $usage =
     "    --strict-paths      : Don't allow recursing into subdirectories\n".
     "    --export-all        : Don't check for gitcvs.enabled in config\n".
     "    --version, -V       : Print version information and exit\n".
-    "    --help, -h, -H      : Print usage information and exit\n".
+    "    -h, -H              : Print usage information and exit\n".
     "\n".
     "<directory> ... is a list of allowed directories. If no directories\n".
     "are given, all are allowed. This is an additional restriction, gitcvs\n".
     "access still needs to be enabled by the gitcvs.enabled config option.\n".
     "Alternately, one directory may be specified in GIT_CVSSERVER_ROOT.\n";
 
-my @opts = ( 'help|h|H', 'version|V',
+my @opts = ( 'h|H', 'version|V',
             'base-path=s', 'strict-paths', 'export-all' );
 GetOptions( $state, @opts )
     or die $usage;
index 8452890be974d30942f0acaa3f19282c1b8b25b2..e6558d101062929241cc30cd0272556c3164310b 100755 (executable)
@@ -43,12 +43,15 @@ launch_merge_tool () {
                printf "\nViewing: '$MERGED'\n"
                if use_ext_cmd
                then
-                       printf "Hit return to launch '%s': " \
+                       printf "Launch '%s' [Y/n]: " \
                                "$GIT_DIFFTOOL_EXTCMD"
                else
-                       printf "Hit return to launch '%s': " "$merge_tool"
+                       printf "Launch '%s' [Y/n]: " "$merge_tool"
+               fi
+               if read ans && test "$ans" = n
+               then
+                       return
                fi
-               read ans
        fi
 
        if use_ext_cmd
index ced1615e216018bc20303cd91eaea05c69cb6b11..09b65f1770c09824df5543d208d1df0e9a60d831 100755 (executable)
@@ -97,7 +97,7 @@ sub generate_command
                        $prompt = 'yes';
                        next;
                }
-               if ($arg eq '-h' || $arg eq '--help') {
+               if ($arg eq '-h') {
                        usage();
                }
                push @command, $arg;
index f96112d47f743289bf3893981bc89dcaa9caae19..33d07c06bd90833ce56bc64c13bdc08c1997c3fb 100644 (file)
@@ -1,3 +1,4 @@
+*           whitespace=indent-with-non-tab,trailing-space,space-before-tab,tabwidth=4
 *           encoding=US-ASCII
 git-gui.sh  encoding=UTF-8
 /po/*.po    encoding=UTF-8
index 1fb4d9b4b7393d2c803457365127692fb093338f..65709437ff06a2b371fe70fd846922c73ebc23b0 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=0.13.GITGUI
+DEF_VER=0.16.GITGUI
 
 LF='
 '
index fd6a43d0a29986d38094df2818b66beba0f49234..ba4e5c1330c84f54a6809991eec22aa2e8e25ddd 100755 (executable)
@@ -299,7 +299,9 @@ proc is_config_true {name} {
        global repo_config
        if {[catch {set v $repo_config($name)}]} {
                return 0
-       } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
+       }
+       set v [string tolower $v]
+       if {$v eq {} || $v eq {true} || $v eq {1} || $v eq {yes} || $v eq {on}} {
                return 1
        } else {
                return 0
@@ -310,7 +312,9 @@ proc is_config_false {name} {
        global repo_config
        if {[catch {set v $repo_config($name)}]} {
                return 0
-       } elseif {$v eq {false} || $v eq {0} || $v eq {no}} {
+       }
+       set v [string tolower $v]
+       if {$v eq {false} || $v eq {0} || $v eq {no} || $v eq {off}} {
                return 1
        } else {
                return 0
@@ -460,6 +464,35 @@ proc _which {what args} {
        return {}
 }
 
+# Test a file for a hashbang to identify executable scripts on Windows.
+proc is_shellscript {filename} {
+       if {![file exists $filename]} {return 0}
+       set f [open $filename r]
+       fconfigure $f -encoding binary
+       set magic [read $f 2]
+       close $f
+       return [expr {$magic eq "#!"}]
+}
+
+# Run a command connected via pipes on stdout.
+# This is for use with textconv filters and uses sh -c "..." to allow it to
+# contain a command with arguments. On windows we must check for shell
+# scripts specifically otherwise just call the filter command.
+proc open_cmd_pipe {cmd path} {
+       global env
+       if {![file executable [shellpath]]} {
+               set exe [auto_execok [lindex $cmd 0]]
+               if {[is_shellscript [lindex $exe 0]]} {
+                       set run [linsert [auto_execok sh] end -c "$cmd \"\$0\"" $path]
+               } else {
+                       set run [concat $exe [lrange $cmd 1 end] $path]
+               }
+       } else {
+               set run [list [shellpath] -c "$cmd \"\$0\"" $path]
+       }
+       return [open |$run r]
+}
+
 proc _lappend_nice {cmd_var} {
        global _nice
        upvar $cmd_var cmd
@@ -725,7 +758,10 @@ if {[is_Windows]} {
                gitlogo put gray26  -to  5 15 11 16
                gitlogo redither
 
-               wm iconphoto . -default gitlogo
+               image create photo gitlogo32 -width 32 -height 32
+               gitlogo32 copy gitlogo -zoom 2 2
+
+               wm iconphoto . -default gitlogo gitlogo32
        }
 }
 
@@ -846,6 +882,7 @@ set default_config(gui.fastcopyblame) false
 set default_config(gui.copyblamethreshold) 40
 set default_config(gui.blamehistoryctx) 7
 set default_config(gui.diffcontext) 5
+set default_config(gui.diffopts) {}
 set default_config(gui.commitmsgwidth) 75
 set default_config(gui.newbranchtemplate) {}
 set default_config(gui.spellingdictionary) {}
@@ -854,10 +891,12 @@ set default_config(gui.fontdiff) [font configure font_diff]
 # TODO: this option should be added to the git-config documentation
 set default_config(gui.maxfilesdisplayed) 5000
 set default_config(gui.usettk) 1
+set default_config(gui.warndetachedcommit) 1
 set font_descs {
        {fontui   font_ui   {mc "Main Font"}}
        {fontdiff font_diff {mc "Diff/Console Font"}}
 }
+set default_config(gui.stageuntracked) ask
 
 ######################################################################
 ##
@@ -1059,6 +1098,10 @@ git-version proc _parse_config {arr_name args} {
                                } else {
                                        set arr($name) $value
                                }
+                       } elseif {[regexp {^([^\n]+)$} $line line name]} {
+                               # no value given, but interpreting them as
+                               # boolean will be handled as true
+                               set arr($name) {}
                        }
                }
        }
@@ -1074,6 +1117,10 @@ git-version proc _parse_config {arr_name args} {
                                        } else {
                                                set arr($name) $value
                                        }
+                               } elseif {[regexp {^([^=]+)$} $line line name]} {
+                                       # no value given, but interpreting them as
+                                       # boolean will be handled as true
+                                       set arr($name) {}
                                }
                        }
                        close $fd_rc
@@ -1526,7 +1573,7 @@ proc run_prepare_commit_msg_hook {} {
 
        # prepare-commit-msg requires PREPARE_COMMIT_MSG exist.  From git-gui
        # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
-       # empty file but existant file.
+       # empty file but existent file.
 
        set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a]
 
@@ -2473,6 +2520,7 @@ proc toggle_or_diff {w x y} {
                                [concat $after [list ui_ready]]
                }
        } else {
+               set selected_paths($path) 1
                show_diff $path $w $lno
        }
 }
@@ -3361,6 +3409,7 @@ foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 gr
        $ui_diff tag configure clri3$n -background $c
 }
 $ui_diff tag configure clr1 -font font_diffbold
+$ui_diff tag configure clr4 -underline 1
 
 $ui_diff tag conf d_info -foreground blue -font font_diffbold
 
@@ -3877,7 +3926,7 @@ after 1 {
                $ui_comm configure -state disabled -background gray
        }
 }
-if {[is_enabled multicommit]} {
+if {[is_enabled multicommit] && ![is_config_false gui.gcwarning]} {
        after 1000 hint_gc
 }
 if {[is_enabled retcode]} {
index 61e358f960ca949cac5664c67442b82d0afca65f..324f7744c495dc2eb9a1d4acde09352d7948efe8 100644 (file)
@@ -22,6 +22,7 @@ field w_asim     ; # text column: annotations (simple computation)
 field w_file     ; # text column: actual file data
 field w_cviewer  ; # pane showing commit message
 field finder     ; # find mini-dialog frame
+field gotoline   ; # line goto mini-dialog frame
 field status     ; # status mega-widget instance
 field old_height ; # last known height of $w.file_pane
 
@@ -218,7 +219,8 @@ constructor new {i_commit i_path i_jump} {
        eval grid $w_columns $w.file_pane.out.sby -sticky nsew
        grid conf \
                $w.file_pane.out.sbx \
-               -column [expr {[llength $w_columns] - 1}] \
+               -column 0 \
+               -columnspan [expr {[llength $w_columns] + 1}] \
                -sticky we
        grid columnconfigure \
                $w.file_pane.out \
@@ -228,7 +230,14 @@ constructor new {i_commit i_path i_jump} {
 
        set finder [::searchbar::new \
                $w.file_pane.out.ff $w_file \
-               -column [expr {[llength $w_columns] - 1}] \
+               -column 0 \
+               -columnspan [expr {[llength $w_columns] + 1}] \
+               ]
+
+       set gotoline [::linebar::new \
+               $w.file_pane.out.lf $w_file \
+               -column 0 \
+               -columnspan [expr {[llength $w_columns] + 1}] \
                ]
 
        set w_cviewer $w.file_pane.cm.t
@@ -274,7 +283,11 @@ constructor new {i_commit i_path i_jump} {
        $w.ctxm add command \
                -label [mc "Find Text..."] \
                -accelerator F7 \
-               -command [list searchbar::show $finder]
+               -command [cb _show_finder]
+       $w.ctxm add command \
+               -label [mc "Goto Line..."] \
+               -accelerator "Ctrl-G" \
+               -command [cb _show_linebar]
        menu $w.ctxm.enc
        build_encoding_menu $w.ctxm.enc [cb _setencoding]
        $w.ctxm add cascade \
@@ -341,10 +354,13 @@ constructor new {i_commit i_path i_jump} {
        bind $w_cviewer <Tab>       "[list focus $w_file];break"
        bind $w_cviewer <Button-1>   [list focus $w_cviewer]
        bind $w_file    <Visibility> [cb _focus_search $w_file]
-       bind $top       <F7>         [list searchbar::show $finder]
+       bind $top       <F7>         [cb _show_finder]
+       bind $top       <Key-slash>  [cb _show_finder]
+       bind $top    <Control-Key-s> [cb _show_finder]
        bind $top       <Escape>     [list searchbar::hide $finder]
        bind $top       <F3>         [list searchbar::find_next $finder]
        bind $top       <Shift-F3>   [list searchbar::find_prev $finder]
+       bind $top    <Control-Key-g> [cb _show_linebar]
        catch { bind $top <Shift-Key-XF86_Switch_VT_3> [list searchbar::find_prev $finder] }
 
        grid configure $w.header -sticky ew
@@ -460,14 +476,7 @@ method _load {jump} {
        }
        if {$commit eq {}} {
                if {$do_textconv ne 0} {
-                       # Run textconv with sh -c "..." to allow it to
-                       # contain command + arguments. On windows, just
-                       # call the filter command.
-                       if {![file executable [shellpath]]} {
-                               set fd [open |[linsert $textconv end $path] r]
-                       } else {
-                               set fd [open |[list [shellpath] -c "$textconv \"\$0\"" $path] r]
-                       }
+                       set fd [open_cmd_pipe $textconv $path]
                } else {
                        set fd [open $path r]
                }
@@ -559,7 +568,11 @@ method _read_file {fd jump} {
        foreach i $w_columns {$i conf -state disabled}
 
        if {[eof $fd]} {
-               close $fd
+               fconfigure $fd -blocking 1; # enable error reporting on close
+               if {[catch {close $fd} err]} {
+                       tk_messageBox -icon error -title [mc Error] \
+                               -message $err
+               }
 
                # If we don't force Tk to update the widgets *right now*
                # none of our jump commands will cause a change in the UI.
@@ -1049,7 +1062,7 @@ method _gitkcommit {} {
                set radius [get_config gui.blamehistoryctx]
                set cmdline [list --select-commit=$cmit]
 
-                if {$radius > 0} {
+               if {$radius > 0} {
                        set author_time {}
                        set committer_time {}
 
@@ -1157,7 +1170,7 @@ method _read_diff_load_commit {fd cparent new_path tline} {
        }
 
        if {[eof $fd]} {
-               close $fd;
+               close $fd
                set current_fd {}
 
                _load_new_commit $this  \
@@ -1188,6 +1201,7 @@ method _open_tooltip {cur_w} {
                _hide_tooltip $this
 
                set tooltip_wm [toplevel $cur_w.tooltip -borderwidth 1]
+               catch {wm attributes $tooltip_wm -type tooltip}
                wm overrideredirect $tooltip_wm 1
                wm transient $tooltip_wm [winfo toplevel $cur_w]
                set tooltip_t $tooltip_wm.label
@@ -1298,9 +1312,9 @@ method _position_tooltip {} {
        set pos_y [expr {[winfo pointery .] + 10}]
 
        set g "${req_w}x${req_h}"
-       if {$pos_x >= 0} {append g +}
+       if {[tk windowingsystem] eq "win32" || $pos_x >= 0} {append g +}
        append g $pos_x
-       if {$pos_y >= 0} {append g +}
+       if {[tk windowingsystem] eq "win32" || $pos_y >= 0} {append g +}
        append g $pos_y
 
        wm geometry $tooltip_wm $g
@@ -1336,4 +1350,14 @@ method _resize {new_height} {
        set old_height $new_height
 }
 
+method _show_finder {} {
+       linebar::hide $gotoline
+       searchbar::show $finder
+}
+
+method _show_linebar {} {
+       searchbar::hide $finder
+       linebar::show $gotoline
+}
+
 }
index a8c622351167be937b9f02df149a1a6ec2a16e98..0328338fda22c90c674630e0351982ff1bb2fbc4 100644 (file)
@@ -26,8 +26,14 @@ constructor new {commit {path {}}} {
        wm withdraw $top
        wm title $top [append "[appname] ([reponame]): " [mc "File Browser"]]
 
+       if {$path ne {}} {
+               if {[string index $path end] ne {/}} {
+                       append path /
+               }
+       }
+
        set browser_commit $commit
-       set browser_path $browser_commit:$path
+       set browser_path "$browser_commit:[escape_path $path]"
 
        ${NS}::label $w.path \
                -textvariable @browser_path \
index c12d5e1698a1fcdcfa39bb69b475633b2f6c34c9..6dae7937d589c174132e9f8b9bd77133e189590f 100644 (file)
@@ -497,6 +497,7 @@ method _open_tooltip {} {
 
        if {$tooltip_wm eq {}} {
                set tooltip_wm [toplevel $w_list.tooltip -borderwidth 1]
+               catch {wm attributes $tooltip_wm -type tooltip}
                wm overrideredirect $tooltip_wm 1
                wm transient $tooltip_wm [winfo toplevel $w_list]
                set tooltip_t $tooltip_wm.label
@@ -610,9 +611,9 @@ method _position_tooltip {} {
        set pos_y [expr {[winfo pointery .] + 10}]
 
        set g "${req_w}x${req_h}"
-       if {$pos_x >= 0} {append g +}
+       if {[tk windowingsystem] eq "win32" || $pos_x >= 0} {append g +}
        append g $pos_x
-       if {$pos_y >= 0} {append g +}
+       if {[tk windowingsystem] eq "win32" || $pos_y >= 0} {append g +}
        append g $pos_y
 
        wm geometry $tooltip_wm $g
index c27b71476ac35dbad02b727f28aba5d7c0777e75..f08506f3834a1ec821390190b920146d83078997 100644 (file)
@@ -138,6 +138,7 @@ proc make_dialog {t w args} {
        upvar $t top $w pfx this this
        global use_ttk
        uplevel [linsert $args 0 make_toplevel $t $w]
+       catch {wm attributes $top -type dialog}
        pave_toplevel $pfx
 }
 
index 5ce46877bfb24701187f5ff5e94ce4aaf8b666b2..0d81432af53ba873d89421fa4767243818ab1f0a 100644 (file)
@@ -260,8 +260,25 @@ proc commit_prehook_wait {fd_ph curHEAD msg_p} {
 }
 
 proc commit_commitmsg {curHEAD msg_p} {
+       global is_detached repo_config
        global pch_error
 
+       if {$is_detached
+           && ![file exists [gitdir rebase-merge head-name]]
+           &&  [is_config_true gui.warndetachedcommit]} {
+               set msg [mc "You are about to commit on a detached head.\
+This is a potentially dangerous thing to do because if you switch\
+to another branch you will loose your changes and it can be difficult\
+to retrieve them later from the reflog. You should probably cancel this\
+commit and create a new branch to continue.\n\
+\n\
+Do you really want to proceed with your Commit?"]
+               if {[ask_popup $msg] ne yes} {
+                       unlock_index
+                       return
+               }
+       }
+
        # -- Run the commit-msg hook.
        #
        set fd_ph [githook_read commit-msg $msg_p]
index cf8a95ec346a9a8afa60a3812d59a8caa4bb4c3f..ec4405567a9b86a9bfd8bf50815b3c9c14e2df40 100644 (file)
@@ -309,6 +309,7 @@ proc start_show_diff {cont_info {add_opts {}}} {
 
        lappend cmd -p
        lappend cmd --color
+       set cmd [concat $cmd $repo_config(gui.diffopts)]
        if {$repo_config(gui.diffcontext) >= 1} {
                lappend cmd "-U$repo_config(gui.diffcontext)"
        }
@@ -502,9 +503,9 @@ proc read_diff {fd conflict_size cont_info} {
 
                foreach {posbegin colbegin posend colend} $markup {
                        set prefix clr
-                       foreach style [split $colbegin ";"] {
+                       foreach style [lsort -integer [split $colbegin ";"]] {
                                if {$style eq "7"} {append prefix i; continue}
-                               if {$style < 30 || $style > 47} {continue}
+                               if {$style != 4 && ($style < 30 || $style > 47)} {continue}
                                set a "$mark linestart + $posbegin chars"
                                set b "$mark linestart + $posend chars"
                                catch {$ui_diff tag add $prefix$style $a $b}
index 5d7bbf23eddeb2ff146487b28a79feee1d296289..8efbbdde21123dd65412cd5fe6dbb506966b0af9 100644 (file)
@@ -356,12 +356,33 @@ proc do_add_all {} {
        global file_states
 
        set paths [list]
+       set untracked_paths [list]
        foreach path [array names file_states] {
                switch -glob -- [lindex $file_states($path) 0] {
                U? {continue}
                ?M -
                ?T -
                ?D {lappend paths $path}
+               ?O {lappend untracked_paths $path}
+               }
+       }
+       if {[llength $untracked_paths]} {
+               set reply 0
+               switch -- [get_config gui.stageuntracked] {
+               no {
+                       set reply 0
+               }
+               yes {
+                       set reply 1
+               }
+               ask -
+               default {
+                       set reply [ask_popup [mc "Stage %d untracked files?" \
+                                                                         [llength $untracked_paths]]]
+               }
+               }
+               if {$reply} {
+                       set paths [concat $paths $untracked_paths]
                }
        }
        add_helper {Adding all changed files} $paths
diff --git a/git-gui/lib/line.tcl b/git-gui/lib/line.tcl
new file mode 100644 (file)
index 0000000..a026de9
--- /dev/null
@@ -0,0 +1,81 @@
+# goto line number
+# based on code from gitk, Copyright (C) Paul Mackerras
+
+class linebar {
+
+field w
+field ctext
+
+field linenum   {}
+
+constructor new {i_w i_text args} {
+       global use_ttk NS
+       set w      $i_w
+       set ctext  $i_text
+
+       ${NS}::frame  $w
+       ${NS}::label  $w.l       -text [mc "Goto Line:"]
+       tentry  $w.ent \
+               -textvariable ${__this}::linenum \
+               -background lightgreen \
+               -validate key \
+               -validatecommand [cb _validate %P]
+       ${NS}::button $w.bn      -text [mc Go] -command [cb _goto]
+
+       pack   $w.l   -side left
+       pack   $w.bn  -side right
+       pack   $w.ent -side left -expand 1 -fill x
+
+       eval grid conf $w -sticky we $args
+       grid remove $w
+
+       trace add variable linenum write [cb _goto_cb]
+       bind $w.ent <Return> [cb _goto]
+       bind $w.ent <Escape> [cb hide]
+
+       bind $w <Destroy> [list delete_this $this]
+       return $this
+}
+
+method show {} {
+       if {![visible $this]} {
+               grid $w
+       }
+       focus -force $w.ent
+}
+
+method hide {} {
+       if {[visible $this]} {
+               $w.ent delete 0 end
+               focus $ctext
+               grid remove $w
+       }
+}
+
+method visible {} {
+       return [winfo ismapped $w]
+}
+
+method editor {} {
+       return $w.ent
+}
+
+method _validate {P} {
+       # only accept numbers as input
+       string is integer $P
+}
+
+method _goto_cb {name ix op} {
+       after idle [cb _goto 1]
+}
+
+method _goto {{nohide {0}}} {
+       if {$linenum ne {}} {
+               $ctext see $linenum.0
+               if {!$nohide} {
+                       hide $this
+               }
+       }
+}
+
+}
index 3807c8d28324a277204db9191e99ddb856041c22..0cf1da1d7ebe75fe91322d9b404dbf4bd8a3b00f 100644 (file)
@@ -153,9 +153,12 @@ proc do_options {} {
                {i-20..200 gui.copyblamethreshold {mc "Minimum Letters To Blame Copy On"}}
                {i-0..300 gui.blamehistoryctx {mc "Blame History Context Radius (days)"}}
                {i-1..99 gui.diffcontext {mc "Number of Diff Context Lines"}}
+               {t gui.diffopts {mc "Additional Diff Parameters"}}
                {i-0..99 gui.commitmsgwidth {mc "Commit Message Text Width"}}
                {t gui.newbranchtemplate {mc "New Branch Name Template"}}
                {c gui.encoding {mc "Default File Contents Encoding"}}
+               {b gui.warndetachedcommit {mc "Warn before committing to a detached head"}}
+               {s gui.stageuntracked {mc "Staging of untracked files"} {list "yes" "no" "ask"}}
                } {
                set type [lindex $option 0]
                set name [lindex $option 1]
@@ -208,6 +211,23 @@ proc do_options {} {
                                }
                                pack $w.$f.$optid -side top -anchor w -fill x
                        }
+                       s {
+                               set opts [eval [lindex $option 3]]
+                               ${NS}::frame $w.$f.$optid
+                               ${NS}::label $w.$f.$optid.l -text "$text:"
+                               if {$use_ttk} {
+                                       ttk::combobox $w.$f.$optid.v \
+                                               -textvariable ${f}_config_new($name) \
+                                               -values $opts -state readonly
+                               } else {
+                                       eval tk_optionMenu $w.$f.$optid.v \
+                                               ${f}_config_new($name) \
+                                               $opts
+                               }
+                               pack $w.$f.$optid.l -side left -anchor w -fill x
+                               pack $w.$f.$optid.v -side right -anchor e -padx 5
+                               pack $w.$f.$optid -side top -anchor w -fill x
+                       }
                        }
                }
        }
index 7fdbf87bcdcf3e6d5a928dd4ac8a323509ea7953..ef1e55521d7cea10e280f720ad700a4cd4b71d65 100644 (file)
@@ -7,9 +7,16 @@ field w
 field ctext
 
 field searchstring   {}
-field casesensitive  1
+field regexpsearch
+field default_regexpsearch
+field casesensitive
+field default_casesensitive
+field smartcase
 field searchdirn     -forwards
 
+field history
+field history_index
+
 field smarktop
 field smarkbot
 
@@ -18,15 +25,37 @@ constructor new {i_w i_text args} {
        set w      $i_w
        set ctext  $i_text
 
+       set default_regexpsearch [is_config_true gui.search.regexp]
+       switch -- [get_config gui.search.case] {
+       no {
+               set default_casesensitive 0
+               set smartcase 0
+       }
+       smart {
+               set default_casesensitive 0
+               set smartcase 1
+       }
+       yes -
+       default {
+               set default_casesensitive 1
+               set smartcase 0
+       }
+       }
+
+       set history [list]
+
        ${NS}::frame  $w
        ${NS}::label  $w.l       -text [mc Find:]
-       entry  $w.ent -textvariable ${__this}::searchstring -background lightgreen
+       tentry  $w.ent -textvariable ${__this}::searchstring -background lightgreen
        ${NS}::button $w.bn      -text [mc Next] -command [cb find_next]
        ${NS}::button $w.bp      -text [mc Prev] -command [cb find_prev]
-       ${NS}::checkbutton $w.cs -text [mc Case-Sensitive] \
+       ${NS}::checkbutton $w.re -text [mc RegExp] \
+               -variable ${__this}::regexpsearch -command [cb _incrsearch]
+       ${NS}::checkbutton $w.cs -text [mc Case] \
                -variable ${__this}::casesensitive -command [cb _incrsearch]
        pack   $w.l   -side left
        pack   $w.cs  -side right
+       pack   $w.re  -side right
        pack   $w.bp  -side right
        pack   $w.bn  -side right
        pack   $w.ent -side left -expand 1 -fill x
@@ -35,6 +64,10 @@ constructor new {i_w i_text args} {
        grid remove $w
 
        trace add variable searchstring write [cb _incrsearch_cb]
+       bind $w.ent <Return> [cb find_next]
+       bind $w.ent <Shift-Return> [cb find_prev]
+       bind $w.ent <Key-Up>   [cb _prev_search]
+       bind $w.ent <Key-Down> [cb _next_search]
        
        bind $w <Destroy> [list delete_this $this]
        return $this
@@ -43,6 +76,10 @@ constructor new {i_w i_text args} {
 method show {} {
        if {![visible $this]} {
                grid $w
+               $w.ent delete 0 end
+               set regexpsearch  $default_regexpsearch
+               set casesensitive $default_casesensitive
+               set history_index [llength $history]
        }
        focus -force $w.ent
 }
@@ -51,6 +88,7 @@ method hide {} {
        if {[visible $this]} {
                focus $ctext
                grid remove $w
+               _save_search $this
        }
 }
 
@@ -96,6 +134,9 @@ method _do_search {start {mlenvar {}} {dir {}} {endbound {}}} {
                upvar $mlenvar mlen
                lappend cmd -count mlen
        }
+       if {$regexpsearch} {
+               lappend cmd -regexp
+       }
        if {!$casesensitive} {
                lappend cmd -nocase
        }
@@ -103,14 +144,16 @@ method _do_search {start {mlenvar {}} {dir {}} {endbound {}}} {
                set dir $searchdirn
        }
        lappend cmd $dir -- $searchstring
-       if {$endbound ne {}} {
-               set here [eval $cmd [list $start] [list $endbound]]
-       } else {
-               set here [eval $cmd [list $start]]
-               if {$here eq {}} {
-                       set here [eval $cmd [_get_wrap_anchor $this $dir]]
+       if {[catch {
+               if {$endbound ne {}} {
+                       set here [eval $cmd [list $start] [list $endbound]]
+               } else {
+                       set here [eval $cmd [list $start]]
+                       if {$here eq {}} {
+                               set here [eval $cmd [_get_wrap_anchor $this $dir]]
+                       }
                }
-       }
+       } err]} { set here {} }
        return $here
 }
 
@@ -124,19 +167,76 @@ method _incrsearch {} {
                $ctext mark set anchor [_get_new_anchor $this]
        }
        if {$searchstring ne {}} {
+               if {$smartcase && [regexp {[[:upper:]]} $searchstring]} {
+                       set casesensitive 1
+               }
                set here [_do_search $this anchor mlen]
                if {$here ne {}} {
                        $ctext see $here
                        $ctext tag remove sel 1.0 end
                        $ctext tag add sel $here "$here + $mlen c"
-                       $w.ent configure -background lightgreen
+                       #$w.ent configure -background lightgreen
+                       $w.ent state !pressed
                        _set_marks $this 1
                } else {
-                       $w.ent configure -background lightpink
+                       #$w.ent configure -background lightpink
+                       $w.ent state pressed
                }
+       } elseif {$smartcase} {
+               # clearing the field resets the smart case detection
+               set casesensitive 0
+       }
+}
+
+method _save_search {} {
+       if {$searchstring eq {}} {
+               return
+       }
+       if {[llength $history] > 0} {
+               foreach {s_regexp s_case s_expr} [lindex $history end] break
+       } else {
+               set s_regexp $regexpsearch
+               set s_case   $casesensitive
+               set s_expr   ""
+       }
+       if {$searchstring eq $s_expr} {
+               # update modes
+               set history [lreplace $history end end \
+                               [list $regexpsearch $casesensitive $searchstring]]
+       } else {
+               lappend history [list $regexpsearch $casesensitive $searchstring]
+       }
+       set history_index [llength $history]
+}
+
+method _prev_search {} {
+       if {$history_index > 0} {
+               incr history_index -1
+               foreach {s_regexp s_case s_expr} [lindex $history $history_index] break
+               $w.ent delete 0 end
+               $w.ent insert 0 $s_expr
+               set regexpsearch $s_regexp
+               set casesensitive $s_case
        }
 }
 
+method _next_search {} {
+       if {$history_index < [llength $history]} {
+               incr history_index
+       }
+       if {$history_index < [llength $history]} {
+               foreach {s_regexp s_case s_expr} [lindex $history $history_index] break
+       } else {
+               set s_regexp $default_regexpsearch
+               set s_case   $default_casesensitive
+               set s_expr   ""
+       }
+       $w.ent delete 0 end
+       $w.ent insert 0 $s_expr
+       set regexpsearch $s_regexp
+       set casesensitive $s_case
+}
+
 method find_prev {} {
        find_next $this -backwards
 }
@@ -147,6 +247,7 @@ method find_next {{dir -forwards}} {
        set searchdirn $dir
        $ctext mark unset anchor
        if {$searchstring ne {}} {
+               _save_search $this
                set start [_get_new_anchor $this]
                if {$dir eq "-forwards"} {
                        set start "$start + 1c"
@@ -196,4 +297,4 @@ method scrolled {} {
        }
 }
 
-}
\ No newline at end of file
+}
index 5f75bc96b3504bdd67ae3500cfdd30f7d4fcb32a..aa6457bbb5f1b0d64d6e04f27394912483250117 100644 (file)
@@ -117,7 +117,7 @@ proc read_sshkey_output {fd w} {
        } else {
                set finfo [find_ssh_key]
                if {$finfo eq {}} {
-                       set sshkey_title [mc "Generation succeded, but no keys found."]
+                       set sshkey_title [mc "Generation succeeded, but no keys found."]
                        $w.contents insert end $sshkey_output
                } else {
                        set sshkey_title [mc "Your key is in: %s" [lindex $finfo 0]]
index 1da458673b557fe84ac8e430e97b7ad53c69a07a..8b88d3678b7ddd802260f47bcfeff8a895d93f3f 100644 (file)
@@ -23,10 +23,59 @@ proc InitTheme {} {
        ttk::style configure Gold.TFrame -background gold -relief flat
        # listboxes should have a theme border so embed in ttk::frame
        ttk::style layout SListbox.TFrame {
-        SListbox.Frame.Entry.field -sticky news -border true -children {
-            SListbox.Frame.padding -sticky news
-        }
-    }
+               SListbox.Frame.Entry.field -sticky news -border true -children {
+                       SListbox.Frame.padding -sticky news
+               }
+       }
+
+       # Handle either current Tk or older versions of 8.5
+       if {[catch {set theme [ttk::style theme use]}]} {
+               set theme  $::ttk::currentTheme
+       }
+
+       if {[lsearch -exact {default alt classic clam} $theme] != -1} {
+               # Simple override of standard ttk::entry to change the field
+               # packground according to a state flag. We should use 'user1'
+               # but not all versions of 8.5 support that so make use of 'pressed'
+               # which is not normally in use for entry widgets.
+               ttk::style layout Edged.Entry [ttk::style layout TEntry]
+               ttk::style map Edged.Entry {*}[ttk::style map TEntry]
+               ttk::style configure Edged.Entry {*}[ttk::style configure TEntry] \
+                       -fieldbackground lightgreen
+               ttk::style map Edged.Entry -fieldbackground {
+                       {pressed !disabled} lightpink
+               }
+       } else {
+               # For fancier themes, in particular the Windows ones, the field
+               # element may not support changing the background color. So instead
+               # override the fill using the default fill element. If we overrode
+               # the vista theme field element we would loose the themed border
+               # of the widget.
+               catch {
+                       ttk::style element create color.fill from default
+               }
+
+               ttk::style layout Edged.Entry {
+                       Edged.Entry.field -sticky nswe -border 0 -children {
+                               Edged.Entry.border -sticky nswe -border 1 -children {
+                                       Edged.Entry.padding -sticky nswe -children {
+                                               Edged.Entry.color.fill -sticky nswe -children {
+                                                       Edged.Entry.textarea -sticky nswe
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               ttk::style configure Edged.Entry {*}[ttk::style configure TEntry] \
+                       -background lightgreen -padding 0 -borderwidth 0
+               ttk::style map Edged.Entry {*}[ttk::style map TEntry] \
+                       -background {{pressed !disabled} lightpink}
+       }
+
+       if {[lsearch [bind . <<ThemeChanged>>] InitTheme] == -1} {
+               bind . <<ThemeChanged>> +[namespace code [list InitTheme]]
+       }
 }
 
 proc gold_frame {w args} {
@@ -74,6 +123,7 @@ proc paddedlabel {w args} {
 # place a themed frame over the surface.
 proc Dialog {w args} {
        eval [linsert $args 0 toplevel $w -class Dialog]
+       catch {wm attributes $w -type dialog}   
        pave_toplevel $w
        return $w
 }
@@ -143,6 +193,47 @@ proc tspinbox {w args} {
        }
 }
 
+proc tentry {w args} {
+       global use_ttk
+       if {$use_ttk} {
+               InitTheme
+               ttk::entry $w -style Edged.Entry
+       } else {
+               entry $w
+       }
+
+       rename $w _$w
+       interp alias {} $w {} tentry_widgetproc $w
+       eval [linsert $args 0 tentry_widgetproc $w configure]
+       return $w
+}
+proc tentry_widgetproc {w cmd args} {
+       global use_ttk
+       switch -- $cmd {
+               state {
+                       if {$use_ttk} {
+                               return [uplevel 1 [list _$w $cmd] $args]
+                       } else {
+                               if {[lsearch -exact $args pressed] != -1} {
+                                       _$w configure -background lightpink
+                               } else {
+                                       _$w configure -background lightgreen
+                               }
+                       }
+               }
+               configure {
+                       if {$use_ttk} {
+                               if {[set n [lsearch -exact $args -background]] != -1} {
+                                       set args [lreplace $args $n [incr n]]
+                                       if {[llength $args] == 0} {return}
+                               }
+                       }
+                       return [uplevel 1 [list _$w $cmd] $args]
+               }
+               default { return [uplevel 1 [list _$w $cmd] $args] }
+       }
+}
+
 # Tk 8.6 provides a standard font selection dialog. This uses the native
 # dialogs on Windows and MacOSX or a standard Tk dialog on X11.
 proc tchoosefont {w title familyvar sizevar} {
index 95e6e5553ea86482d0fe9be77b07e805e01e3393..6ec94113db7d8c75d78e5300469bb8154e37c940 100644 (file)
@@ -87,8 +87,14 @@ proc tools_exec {fullname} {
                        return
                }
        } elseif {[is_config_true "guitool.$fullname.confirm"]} {
-               if {[ask_popup [mc "Are you sure you want to run %s?" $fullname]] ne {yes}} {
-                       return
+               if {[is_config_true "guitool.$fullname.needsfile"]} {
+                       if {[ask_popup [mc "Are you sure you want to run %1\$s on file \"%2\$s\"?" $fullname $current_diff_path]] ne {yes}} {
+                               return
+                       }
+               } else {
+                       if {[ask_popup [mc "Are you sure you want to run %s?" $fullname]] ne {yes}} {
+                               return
+                       }
                }
        }
 
index 7fad9b7d91a6d6a0671ca7bac714dcc4fd022403..e5d211edea05cbfb856e81a9d683a5fb0f4f4b20 100644 (file)
@@ -124,6 +124,7 @@ proc do_push_anywhere {} {
 
        set w .push_setup
        toplevel $w
+       catch {wm attributes $w -type dialog}
        wm withdraw $w
        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
        pave_toplevel $w
index 595bbf5dee97e34eeab46b742225fce2f95ab052..0f5837d48e7040e9d2b07513666b608353736645 100644 (file)
@@ -18,28 +18,23 @@ specialized so-called "po file editors" (e.g. emacs po-mode, KBabel,
 poedit, GTranslator --- any of them would work well).  Please install
 them.
 
-You would then need to clone the git-gui internationalization project
-repository, so that you can work on it:
+You would then need to clone the git-gui project repository and create
+a feature branch to begin working:
 
-       $ git clone mob@repo.or.cz:/srv/git/git-gui/git-gui-i18n.git/
-       $ cd git-gui-i18n
-       $ git checkout --track -b mob origin/mob
-       $ git config remote.origin.push mob
+       $ git clone git://repo.or.cz/git-gui.git
+       $ cd git-gui.git
+       $ git checkout -b my-translation
 
-The "git checkout" command creates a 'mob' branch from upstream's
-corresponding branch and makes it your current branch.  You will be
-working on this branch.
-
-The "git config" command records in your repository configuration file
-that you would push "mob" branch to the upstream when you say "git
-push".
+The "git checkout" command creates a new branch to keep your work
+isolated and to make it simple to post your patch series when
+completed.  You will be working on this branch.
 
 
 2. Starting a new language.
 
-In the git-gui-i18n directory is a po/ subdirectory.  It has a
-handful files whose names end with ".po".  Is there a file that has
-messages in your language?
+In the git-gui directory is a po/ subdirectory.  It has a handful of
+files whose names end with ".po".  Is there a file that has messages
+in your language?
 
 If you do not know what your language should be named, you need to find
 it.  This currently follows ISO 639-1 two letter codes:
@@ -149,15 +144,18 @@ There is a trick to test your translation without first installing:
        $ make
        $ LANG=af ./git-gui.sh
 
-When you are satisfied with your translation, commit your changes, and
-push it back to the 'mob' branch:
+When you are satisfied with your translation, commit your changes then submit
+your patch series to the maintainer and the Git mailing list:
 
        $ edit po/af.po
        ... be sure to update Last-Translator: and
        ... PO-Revision-Date: lines.
        $ git add po/af.po
-       $ git commit -m 'Started Afrikaans translation.'
-       $ git push
+       $ git commit -s -m 'git-gui: added Afrikaans translation.'
+       $ git send-email --to 'git@vger.kernel.org' \
+          --cc 'Pat Thoyts <patthoyts@users.sourceforge.net>' \
+          --subject 'git-gui: Afrikaans translation' \
+          master..
 
 
 3. Updating your translation.
@@ -169,6 +167,7 @@ itself was updated and there are new messages that need translation.
 
 In any case, make sure you are up-to-date before starting your work:
 
+       $ git checkout master
        $ git pull
 
 In the former case, you will edit po/af.po (again, replace "af" with
index 8bd3c5d75feea3dfe310adbfab705d8045aa8367..24cc4e3675e05e9bd3bf7ea17335a67a3aa5166b 100644 (file)
@@ -1714,7 +1714,7 @@ msgstr ""
 
 #: lib/index.tcl:30
 msgid "Continue"
-msgstr "Forstätt"
+msgstr "Fortsätt"
 
 #: lib/index.tcl:33
 msgid "Unlock Index"
index 085e213a126e5864befc8e3436832ae3c587a624..a9f23f7fcdff610f2b206bc121db7c9217bbf0c7 100755 (executable)
@@ -181,10 +181,14 @@ stage_submodule () {
 }
 
 checkout_staged_file () {
-    tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^    ]*\)    ')
+    tmpfile=$(expr \
+           "$(git checkout-index --temp --stage="$1" "$2" 2>/dev/null)" \
+           : '\([^     ]*\)    ')
 
     if test $? -eq 0 -a -n "$tmpfile" ; then
        mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
+    else
+       >"$3"
     fi
 }
 
@@ -224,9 +228,9 @@ merge_file () {
     mv -- "$MERGED" "$BACKUP"
     cp -- "$BACKUP" "$MERGED"
 
-    base_present   && checkout_staged_file 1 "$MERGED" "$BASE"
-    local_present  && checkout_staged_file 2 "$MERGED" "$LOCAL"
-    remote_present && checkout_staged_file 3 "$MERGED" "$REMOTE"
+    checkout_staged_file 1 "$MERGED" "$BASE"
+    checkout_staged_file 2 "$MERGED" "$LOCAL"
+    checkout_staged_file 3 "$MERGED" "$REMOTE"
 
     if test -z "$local_mode" -o -z "$remote_mode"; then
        echo "Deleted merge conflict for '$MERGED':"
index 902fc4adb4b95d27c6ba6c63f40108702dd93f11..d8b64d7a67a19f1821a26c3ec82c0953db717be6 100755 (executable)
@@ -44,6 +44,10 @@ merge_args=
 curr_branch=$(git symbolic-ref -q HEAD)
 curr_branch_short="${curr_branch#refs/heads/}"
 rebase=$(git config --bool branch.$curr_branch_short.rebase)
+if test -z "$rebase"
+then
+       rebase=$(git config --bool pull.rebase)
+fi
 dry_run=
 while :
 do
@@ -120,7 +124,7 @@ do
        --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
                dry_run=--dry-run
                ;;
-       -h|--h|--he|--hel|--help|--help-|--help-a|--help-al|--help-all)
+       -h|--help-all)
                usage
                ;;
        *)
index 94f36c254c53366ba53d256c0bd50a1de07cdb85..5812222eb9afa2b2903040d7cf32ab0fb33c3508 100644 (file)
@@ -143,6 +143,21 @@ die_with_patch () {
        die "$2"
 }
 
+exit_with_patch () {
+       echo "$1" > "$state_dir"/stopped-sha
+       make_patch $1
+       git rev-parse --verify HEAD > "$amend"
+       warn "You can amend the commit now, with"
+       warn
+       warn "  git commit --amend"
+       warn
+       warn "Once you are satisfied with your changes, run"
+       warn
+       warn "  git rebase --continue"
+       warn
+       exit $2
+}
+
 die_abort () {
        rm -rf "$state_dir"
        die "$1"
@@ -161,6 +176,19 @@ do_with_author () {
        )
 }
 
+git_sequence_editor () {
+       if test -z "$GIT_SEQUENCE_EDITOR"
+       then
+               GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
+               if [ -z "$GIT_SEQUENCE_EDITOR" ]
+               then
+                       GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
+               fi
+       fi
+
+       eval "$GIT_SEQUENCE_EDITOR" '"$@"'
+}
+
 pick_one () {
        ff=--ff
        case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
@@ -395,7 +423,13 @@ do_next () {
                mark_action_done
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $sha1... $rest"
-               git commit --amend --no-post-rewrite
+               git commit --amend --no-post-rewrite || {
+                       warn "Could not amend commit after successfully picking $sha1... $rest"
+                       warn "This is most likely due to an empty commit message, or the pre-commit hook"
+                       warn "failed. If the pre-commit hook failed, you may need to resolve the issue before"
+                       warn "you are able to reword the commit."
+                       exit_with_patch $sha1 1
+               }
                record_in_rewritten $sha1
                ;;
        edit|e)
@@ -404,19 +438,8 @@ do_next () {
                mark_action_done
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $sha1... $rest"
-               echo "$sha1" > "$state_dir"/stopped-sha
-               make_patch $sha1
-               git rev-parse --verify HEAD > "$amend"
                warn "Stopped at $sha1... $rest"
-               warn "You can amend the commit now, with"
-               warn
-               warn "  git commit --amend"
-               warn
-               warn "Once you are satisfied with your changes, run"
-               warn
-               warn "  git rebase --continue"
-               warn
-               exit 0
+               exit_with_patch $sha1 0
                ;;
        squash|s|fixup|f)
                case "$command" in
@@ -832,7 +855,7 @@ has_action "$todo" ||
        die_abort "Nothing to do"
 
 cp "$todo" "$todo".backup
-git_editor "$todo" ||
+git_sequence_editor "$todo" ||
        die_abort "Could not execute editor"
 
 has_action "$todo" ||
index 26afc75cc7d0fbf3de69325043130836c32454a0..dc599077f0e55472ae814ed4e15f5b3a1b729caf 100644 (file)
@@ -90,10 +90,13 @@ call_merge () {
 
 finish_rb_merge () {
        move_to_original_branch
-       git notes copy --for-rewrite=rebase < "$state_dir"/rewritten
-       if test -x "$GIT_DIR"/hooks/post-rewrite &&
-               test -s "$state_dir"/rewritten; then
-               "$GIT_DIR"/hooks/post-rewrite rebase < "$state_dir"/rewritten
+       if test -s "$state_dir"/rewritten
+       then
+               git notes copy --for-rewrite=rebase <"$state_dir"/rewritten
+               if test -x "$GIT_DIR"/hooks/post-rewrite
+               then
+                       "$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten
+               fi
        fi
        rm -r "$state_dir"
        say All done.
index fc080cc5e45d02e618e7224c052de1be676f4360..e6438e24c7c787b55428a48eca9461d772db7ee6 100755 (executable)
@@ -35,44 +35,103 @@ do
        shift
 done
 
-base=$1
-url=$2
-head=${3-HEAD}
+base=$1 url=$2 head=${3-HEAD} status=0 branch_name=
 
-[ "$base" ] || usage
-[ "$url" ] || usage
+headref=$(git symbolic-ref -q "$head")
+if git show-ref -q --verify "$headref"
+then
+       branch_name=${headref#refs/heads/}
+       if test "z$branch_name" = "z$headref" ||
+               ! git config "branch.$branch_name.description" >/dev/null
+       then
+               branch_name=
+       fi
+fi
+
+tag_name=$(git describe --exact "$head^0" 2>/dev/null)
 
-baserev=`git rev-parse --verify "$base"^0` &&
-headrev=`git rev-parse --verify "$head"^0` || exit
+test -n "$base" && test -n "$url" || usage
+baserev=$(git rev-parse --verify "$base"^0) &&
+headrev=$(git rev-parse --verify "$head"^0) || exit
 
-merge_base=`git merge-base $baserev $headrev` ||
+merge_base=$(git merge-base $baserev $headrev) ||
 die "fatal: No commits in common between $base and $head"
 
-branch=$(git ls-remote "$url" \
-       | sed -n -e "/^$headrev refs.heads./{
-               s/^.*   refs.heads.//
-               p
-               q
-       }")
+# $head is the token given from the command line. If a ref with that
+# name exists at the remote and their values match, we should use it.
+# Otherwise find a ref that matches $headrev.
+find_matching_ref='
+       sub abbr {
+               my $ref = shift;
+               if ($ref =~ s|^refs/heads/|| || $ref =~ s|^refs/tags/|tags/|) {
+                       return $ref;
+               } else {
+                       return $ref;
+               }
+       }
+
+       my ($exact, $found);
+       while (<STDIN>) {
+               my ($sha1, $ref, $deref) = /^(\S+)\s+(\S+?)(\^\{\})?$/;
+               next unless ($sha1 eq $ARGV[1]);
+               $found = abbr($ref);
+               if ($ref =~ m|/\Q$ARGV[0]\E$|) {
+                       $exact = $found;
+                       last;
+               }
+       }
+       if ($exact) {
+               print "$exact\n";
+       } elsif ($found) {
+               print "$found\n";
+       }
+'
+
+ref=$(git ls-remote "$url" | perl -e "$find_matching_ref" "$head" "$headrev")
+
 url=$(git ls-remote --get-url "$url")
-if [ -z "$branch" ]; then
-       echo "warn: No branch of $url is at:" >&2
-       git log --max-count=1 --pretty='tformat:warn:   %h: %s' $headrev >&2
-       echo "warn: Are you sure you pushed $head there?" >&2
-       echo >&2
-       echo >&2
-       branch=..BRANCH.NOT.VERIFIED..
-       status=1
-fi
 
 git show -s --format='The following changes since commit %H:
 
   %s (%ci)
 
-are available in the git repository at:' $baserev &&
-echo "  $url $branch" &&
-echo &&
+are available in the git repository at:
+' $merge_base &&
+echo "  $url${ref+ $ref}" &&
+git show -s --format='
+for you to fetch changes up to %H:
+
+  %s (%ci)
+
+----------------------------------------------------------------' $headrev &&
+
+if test -n "$branch_name"
+then
+       echo "(from the branch description for $branch_name local branch)"
+       echo
+       git config "branch.$branch_name.description"
+fi &&
+
+if test -n "$tag_name"
+then
+       git cat-file tag "$tag_name" |
+       sed -n -e '1,/^$/d' -e '/^-----BEGIN PGP /q' -e p
+       echo
+fi &&
+
+if test -n "$branch_name" || test -n "$tag_name"
+then
+       echo "----------------------------------------------------------------"
+fi &&
 
 git shortlog ^$baserev $headrev &&
-git diff -M --stat --summary $patch $merge_base..$headrev || exit
+git diff -M --stat --summary $patch $merge_base..$headrev || status=1
+
+if test -z "$ref"
+then
+       echo "warn: No branch of $url is at:" >&2
+       git show -s --format='warn:   %h: %s' $headrev >&2
+       echo "warn: Are you sure you pushed '$head' there?" >&2
+       status=1
+fi
 exit $status
index 5790744ae3fd89f83a229f964a5603df94e05c83..ef30c557c7dee549e891fe7605902ba58d0566a3 100755 (executable)
@@ -210,6 +210,7 @@ my %config_bool_settings = (
     "signedoffbycc" => [\$signed_off_by_cc, undef],
     "signedoffcc" => [\$signed_off_by_cc, undef],      # Deprecated
     "validate" => [\$validate, 1],
+    "multiedit" => [\$multiedit, undef]
 );
 
 my %config_settings = (
@@ -225,15 +226,17 @@ my %config_settings = (
     "cccmd" => \$cc_cmd,
     "aliasfiletype" => \$aliasfiletype,
     "bcc" => \@bcclist,
-    "aliasesfile" => \@alias_files,
     "suppresscc" => \@suppress_cc,
     "envelopesender" => \$envelope_sender,
-    "multiedit" => \$multiedit,
     "confirm"   => \$confirm,
     "from" => \$sender,
     "assume8bitencoding" => \$auto_8bit_encoding,
 );
 
+my %config_path_settings = (
+    "aliasesfile" => \@alias_files,
+);
+
 # Help users prepare for 1.7.0
 sub chain_reply_to {
        if (defined $chain_reply_to &&
@@ -275,7 +278,9 @@ $SIG{INT}  = \&signal_handler;
 # Begin by accumulating all the variables (defined above), that we will end up
 # needing, first, from the command line:
 
-my $rc = GetOptions("sender|from=s" => \$sender,
+my $help;
+my $rc = GetOptions("h" => \$help,
+                   "sender|from=s" => \$sender,
                     "in-reply-to=s" => \$initial_reply_to,
                    "subject=s" => \$initial_subject,
                    "to=s" => \@initial_to,
@@ -313,6 +318,7 @@ my $rc = GetOptions("sender|from=s" => \$sender,
                    "force" => \$force,
         );
 
+usage() if $help;
 unless ($rc) {
     usage();
 }
@@ -330,6 +336,19 @@ sub read_config {
                $$target = Git::config_bool(@repo, "$prefix.$setting") unless (defined $$target);
        }
 
+       foreach my $setting (keys %config_path_settings) {
+               my $target = $config_path_settings{$setting};
+               if (ref($target) eq "ARRAY") {
+                       unless (@$target) {
+                               my @values = Git::config_path(@repo, "$prefix.$setting");
+                               @$target = @values if (@values && defined $values[0]);
+                       }
+               }
+               else {
+                       $$target = Git::config_path(@repo, "$prefix.$setting") unless (defined $$target);
+               }
+       }
+
        foreach my $setting (keys %config_settings) {
                my $target = $config_settings{$setting};
                next if $setting eq "to" and defined $no_to;
@@ -1095,6 +1114,12 @@ X-Mailer: git-send-email $gitversion
                }
 
                if (defined $smtp_authuser) {
+                       # Workaround AUTH PLAIN/LOGIN interaction defect
+                       # with Authen::SASL::Cyrus
+                       eval {
+                               require Authen::SASL;
+                               Authen::SASL->import(qw(Perl));
+                       };
 
                        if (!defined $smtp_authpass) {
 
index e672366f0c3db3b547233af146cd1bba275c0042..b4575fb3a109a2973d519c9f03187ca8fe516679 100644 (file)
@@ -2,47 +2,91 @@
 #
 # Copyright (c) 2010 Ævar Arnfjörð Bjarmason
 #
-# This is a skeleton no-op implementation of gettext for Git. It'll be
-# replaced by something that uses gettext.sh in a future patch series.
+# This is Git's interface to gettext.sh. See po/README for usage
+# instructions.
+
+# Export the TEXTDOMAIN* data that we need for Git
+TEXTDOMAIN=git
+export TEXTDOMAIN
+if test -z "$GIT_TEXTDOMAINDIR"
+then
+       TEXTDOMAINDIR="@@LOCALEDIR@@"
+else
+       TEXTDOMAINDIR="$GIT_TEXTDOMAINDIR"
+fi
+export TEXTDOMAINDIR
 
 if test -z "$GIT_GETTEXT_POISON"
 then
-       gettext () {
-               printf "%s" "$1"
-       }
+       if test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && type gettext.sh >/dev/null 2>&1
+       then
+               # This is GNU libintl's gettext.sh, we don't need to do anything
+               # else than setting up the environment and loading gettext.sh
+               GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu
+               export GIT_INTERNAL_GETTEXT_SH_SCHEME
 
-       gettextln() {
-               printf "%s\n" "$1"
-       }
+               # Try to use libintl's gettext.sh, or fall back to English if we
+               # can't.
+               . gettext.sh
 
-       eval_gettext () {
-               printf "%s" "$1" | (
-                       export PATH $(git sh-i18n--envsubst --variables "$1");
-                       git sh-i18n--envsubst "$1"
-               )
-       }
+       elif test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && test "$(gettext -h 2>&1)" = "-h"
+       then
+               # We don't have gettext.sh, but there's a gettext binary in our
+               # path. This is probably Solaris or something like it which has a
+               # gettext implementation that isn't GNU libintl.
+               GIT_INTERNAL_GETTEXT_SH_SCHEME=solaris
+               export GIT_INTERNAL_GETTEXT_SH_SCHEME
 
-       eval_gettextln () {
-               printf "%s\n" "$1" | (
-                       export PATH $(git sh-i18n--envsubst --variables "$1");
-                       git sh-i18n--envsubst "$1"
-               )
-       }
+               # Solaris has a gettext(1) but no eval_gettext(1)
+               eval_gettext () {
+                       gettext "$1" | (
+                               export PATH $(git sh-i18n--envsubst --variables "$1");
+                               git sh-i18n--envsubst "$1"
+                       )
+               }
+
+       else
+               # Since gettext.sh isn't available we'll have to define our own
+               # dummy pass-through functions.
+
+               # Tell our tests that we don't have the real gettext.sh
+               GIT_INTERNAL_GETTEXT_SH_SCHEME=fallthrough
+               export GIT_INTERNAL_GETTEXT_SH_SCHEME
+
+               gettext () {
+                       printf "%s" "$1"
+               }
+
+               eval_gettext () {
+                       printf "%s" "$1" | (
+                               export PATH $(git sh-i18n--envsubst --variables "$1");
+                               git sh-i18n--envsubst "$1"
+                       )
+               }
+       fi
 else
+       # Emit garbage under GETTEXT_POISON=YesPlease. Unlike the C tests
+       # this relies on an environment variable
+
+       GIT_INTERNAL_GETTEXT_SH_SCHEME=poison
+       export GIT_INTERNAL_GETTEXT_SH_SCHEME
+
        gettext () {
                printf "%s" "# GETTEXT POISON #"
        }
 
-       gettextln () {
-               printf "%s\n" "# GETTEXT POISON #"
-       }
-
        eval_gettext () {
                printf "%s" "# GETTEXT POISON #"
        }
-
-       eval_gettextln () {
-               printf "%s\n" "# GETTEXT POISON #"
-       }
 fi
 
+# Git-specific wrapper functions
+gettextln () {
+       gettext "$1"
+       echo
+}
+
+eval_gettextln () {
+       eval_gettext "$1"
+       echo
+}
index 8e427dab31c27bd78d9eac1ad15bc1f5dcc044c4..5d8e4e6c89f0471567a7aa1f07b054d8ac502e15 100644 (file)
@@ -90,7 +90,7 @@ $LONG_USAGE"
        fi
 
        case "$1" in
-               -h|--h|--he|--hel|--help)
+               -h)
                echo "$LONG_USAGE"
                exit
        esac
@@ -200,7 +200,7 @@ get_author_ident_from_commit () {
                s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
 
                g
-               s/^author [^<]* <[^>]*> \(.*\)$/\1/
+               s/^author [^<]* <[^>]*> \(.*\)$/@\1/
                s/.*/GIT_AUTHOR_DATE='\''&'\''/p
 
                q
index 31dec0abf13ee7d6ded313b24253cd12254f1685..fe4ab28b2e10f85b20ff7892663cbb141fad4723 100755 (executable)
@@ -115,7 +115,8 @@ create_stash () {
                        git read-tree --index-output="$TMPindex" -m $i_tree &&
                        GIT_INDEX_FILE="$TMPindex" &&
                        export GIT_INDEX_FILE &&
-                       git diff --name-only -z HEAD | git update-index -z --add --remove --stdin &&
+                       git diff --name-only -z HEAD -- >"$TMP-stagenames" &&
+                       git update-index -z --add --remove --stdin <"$TMP-stagenames" &&
                        git write-tree &&
                        rm -f "$TMPindex"
                ) ) ||
@@ -134,7 +135,7 @@ create_stash () {
                w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
                die "$(gettext "Cannot save the current worktree state")"
 
-               git diff-tree -p HEAD $w_tree "$TMP-patch" &&
+               git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
                test -s "$TMP-patch" ||
                die "$(gettext "No changes selected")"
 
@@ -211,7 +212,7 @@ save_stash () {
 
        if test -n "$patch_mode" && test -n "$untracked"
        then
-           die "Can't use --patch and ---include-untracked or --all at the same time"
+           die "Can't use --patch and --include-untracked or --all at the same time"
        fi
 
        stash_msg="$*"
@@ -240,7 +241,7 @@ save_stash () {
                test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
                if test -n "$untracked"
                then
-                       git clean --force --quiet $CLEAN_X_OPTION
+                       git clean --force --quiet -d $CLEAN_X_OPTION
                fi
 
                if test "$keep_index" = "t" && test -n $i_tree
@@ -491,7 +492,7 @@ drop_stash () {
                die "$(eval_gettext "\${REV}: Could not drop stash entry")"
 
        # clear_stash if we just dropped the last stash entry
-       git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
+       git rev-parse --verify "$ref_stash@{0}" >/dev/null 2>&1 || clear_stash
 }
 
 apply_to_branch () {
index 814d0d914eaf994462e61559b0692c958f97513b..9bb2e13e929c824a75c648966aaaa61672b9c445 100755 (executable)
@@ -104,9 +104,9 @@ module_name()
        re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
        name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
                sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
-       test -z "$name" &&
-       die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$path'")"
-       echo "$name"
+       test -z "$name" &&
+       die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$path'")"
+       echo "$name"
 }
 
 #
@@ -128,13 +128,50 @@ module_clone()
                quiet=-q
        fi
 
-       if test -n "$reference"
+       gitdir=
+       gitdir_base=
+       name=$(module_name "$path" 2>/dev/null)
+       test -n "$name" || name="$path"
+       base_path=$(dirname "$path")
+
+       gitdir=$(git rev-parse --git-dir)
+       gitdir_base="$gitdir/modules/$base_path"
+       gitdir="$gitdir/modules/$path"
+
+       case $gitdir in
+       /*)
+               a="$(cd_to_toplevel && pwd)/"
+               b=$gitdir
+               while [ "$b" ] && [ "${a%%/*}" = "${b%%/*}" ]
+               do
+                       a=${a#*/} b=${b#*/};
+               done
+
+               rel="$a$name"
+               rel=`echo $rel | sed -e 's|[^/]*|..|g'`
+               rel_gitdir="$rel/$b"
+               ;;
+       *)
+               rel=`echo $name | sed -e 's|[^/]*|..|g'`
+               rel_gitdir="$rel/$gitdir"
+               ;;
+       esac
+
+       if test -d "$gitdir"
        then
-               git-clone $quiet "$reference" -n "$url" "$path"
+               mkdir -p "$path"
+               echo "gitdir: $rel_gitdir" >"$path/.git"
+               rm -f "$gitdir/index"
        else
-               git-clone $quiet -n "$url" "$path"
-       fi ||
-       die "$(eval_gettext "Clone of '\$url' into submodule path '\$path' failed")"
+               mkdir -p "$gitdir_base"
+               if test -n "$reference"
+               then
+                       git-clone $quiet "$reference" -n "$url" "$path" --separate-git-dir "$gitdir"
+               else
+                       git-clone $quiet -n "$url" "$path" --separate-git-dir "$gitdir"
+               fi ||
+               die "$(eval_gettext "Clone of '\$url' into submodule path '\$path' failed")"
+       fi
 }
 
 #
@@ -426,6 +463,9 @@ cmd_update()
                --recursive)
                        recursive=1
                        ;;
+               --checkout)
+                       update="checkout"
+                       ;;
                --)
                        shift
                        break
@@ -458,7 +498,19 @@ cmd_update()
                fi
                name=$(module_name "$path") || exit
                url=$(git config submodule."$name".url)
-               update_module=$(git config submodule."$name".update)
+               if ! test -z "$update"
+               then
+                       update_module=$update
+               else
+                       update_module=$(git config submodule."$name".update)
+               fi
+
+               if test "$update_module" = "none"
+               then
+                       echo "Skipping submodule '$path'"
+                       continue
+               fi
+
                if test -z "$url"
                then
                        # Only mention uninitialized submodules when its
@@ -480,11 +532,6 @@ Maybe you want to use 'update --init'?")"
                        die "$(eval_gettext "Unable to find current revision in submodule path '\$path'")"
                fi
 
-               if ! test -z "$update"
-               then
-                       update_module=$update
-               fi
-
                if test "$subsha1" != "$sha1"
                then
                        subforce=$force
index 351e743a902568c8b6f68ffaf4030f087ba8568d..eeb83d375931df81bd87ca285c8d0741c14f33a2 100755 (executable)
@@ -22,14 +22,13 @@ $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn';
 $Git::SVN::Ra::_log_window_size = 100;
 $Git::SVN::_minimize_url = 'unset';
 
-if (! exists $ENV{SVN_SSH}) {
-       if (exists $ENV{GIT_SSH}) {
-               $ENV{SVN_SSH} = $ENV{GIT_SSH};
-               if ($^O eq 'msys') {
-                       $ENV{SVN_SSH} =~ s/\\/\\\\/g;
-                       $ENV{SVN_SSH} =~ s/(.*)/"$1"/;
-               }
-       }
+if (! exists $ENV{SVN_SSH} && exists $ENV{GIT_SSH}) {
+       $ENV{SVN_SSH} = $ENV{GIT_SSH};
+}
+
+if (exists $ENV{SVN_SSH} && $^O eq 'msys') {
+       $ENV{SVN_SSH} =~ s/\\/\\\\/g;
+       $ENV{SVN_SSH} =~ s/(.*)/"$1"/;
 }
 
 $Git::SVN::Log::TZ = $ENV{TZ};
@@ -87,14 +86,15 @@ my ($_stdin, $_help, $_edit,
        $_version, $_fetch_all, $_no_rebase, $_fetch_parent,
        $_merge, $_strategy, $_dry_run, $_local,
        $_prefix, $_no_checkout, $_url, $_verbose,
-       $_git_format, $_commit_url, $_tag, $_merge_info);
+       $_git_format, $_commit_url, $_tag, $_merge_info, $_interactive);
 $Git::SVN::_follow_parent = 1;
 $SVN::Git::Fetcher::_placeholder_filename = ".gitignore";
 $_q ||= 0;
 my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
                     'config-dir=s' => \$Git::SVN::Ra::config_dir,
                     'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
-                    'ignore-paths=s' => \$SVN::Git::Fetcher::_ignore_regex );
+                    'ignore-paths=s' => \$SVN::Git::Fetcher::_ignore_regex,
+                    'ignore-refs=s' => \$Git::SVN::Ra::_ignore_refs_regex );
 my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
                'authors-file|A=s' => \$_authors,
                'authors-prog=s' => \$_authors_prog,
@@ -163,6 +163,7 @@ my %cmd = (
                          'revision|r=i' => \$_revision,
                          'no-rebase' => \$_no_rebase,
                          'mergeinfo=s' => \$_merge_info,
+                         'interactive|i' => \$_interactive,
                        %cmt_opts, %fc_opts } ],
        branch => [ \&cmd_branch,
                    'Create a branch in the SVN repository',
@@ -256,6 +257,27 @@ my %cmd = (
                {} ],
 );
 
+use Term::ReadLine;
+package FakeTerm;
+sub new {
+       my ($class, $reason) = @_;
+       return bless \$reason, shift;
+}
+sub readline {
+       my $self = shift;
+       die "Cannot use readline on FakeTerm: $$self";
+}
+package main;
+
+my $term = eval {
+       $ENV{"GIT_SVN_NOTTY"}
+               ? new Term::ReadLine 'git-svn', \*STDIN, \*STDOUT
+               : new Term::ReadLine 'git-svn';
+};
+if ($@) {
+       $term = new FakeTerm "$@: going non-interactive";
+}
+
 my $cmd;
 for (my $i = 0; $i < @ARGV; $i++) {
        if (defined $cmd{$ARGV[$i]}) {
@@ -299,7 +321,7 @@ read_git_config(\%opts);
 if ($cmd && ($cmd eq 'log' || $cmd eq 'blame')) {
        Getopt::Long::Configure('pass_through');
 }
-my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version,
+my $rv = GetOptions(%opts, 'h|H' => \$_help, 'version|V' => \$_version,
                     'minimize-connections' => \$Git::SVN::Migration::_minimize,
                     'id|i=s' => \$Git::SVN::default_ref_id,
                     'svn-remote|remote|R=s' => sub {
@@ -366,6 +388,36 @@ sub version {
        exit 0;
 }
 
+sub ask {
+       my ($prompt, %arg) = @_;
+       my $valid_re = $arg{valid_re};
+       my $default = $arg{default};
+       my $resp;
+       my $i = 0;
+
+       if ( !( defined($term->IN)
+            && defined( fileno($term->IN) )
+            && defined( $term->OUT )
+            && defined( fileno($term->OUT) ) ) ){
+               return defined($default) ? $default : undef;
+       }
+
+       while ($i++ < 10) {
+               $resp = $term->readline($prompt);
+               if (!defined $resp) { # EOF
+                       print "\n";
+                       return defined $default ? $default : undef;
+               }
+               if ($resp eq '' and defined $default) {
+                       return $default;
+               }
+               if (!defined $valid_re or $resp =~ /$valid_re/) {
+                       return $resp;
+               }
+       }
+       return undef;
+}
+
 sub do_git_init_db {
        unless (-d $ENV{GIT_DIR}) {
                my @init_db = ('init');
@@ -388,9 +440,12 @@ sub do_git_init_db {
                command_noisy('config', "$pfx.$i", $icv{$i});
                $set = $i;
        }
-       my $ignore_regex = \$SVN::Git::Fetcher::_ignore_regex;
-       command_noisy('config', "$pfx.ignore-paths", $$ignore_regex)
-               if defined $$ignore_regex;
+       my $ignore_paths_regex = \$SVN::Git::Fetcher::_ignore_regex;
+       command_noisy('config', "$pfx.ignore-paths", $$ignore_paths_regex)
+               if defined $$ignore_paths_regex;
+       my $ignore_refs_regex = \$Git::SVN::Ra::_ignore_refs_regex;
+       command_noisy('config', "$pfx.ignore-refs", $$ignore_refs_regex)
+               if defined $$ignore_refs_regex;
 
        if (defined $SVN::Git::Fetcher::_preserve_empty_dirs) {
                my $fname = \$SVN::Git::Fetcher::_placeholder_filename;
@@ -629,7 +684,7 @@ sub populate_merge_info {
                                fatal "merge commit $d has ancestor $parent, but that change "
                      ."does not have git-svn metadata!";
                        }
-                       unless ($branchurl =~ /^$rooturl(.*)/) {
+                       unless ($branchurl =~ /^\Q$rooturl\E(.*)/) {
                                fatal "commit $parent git-svn metadata changed mid-run!";
                        }
                        my $branchpath = $1;
@@ -746,6 +801,27 @@ sub cmd_dcommit {
                     "If these changes depend on each other, re-running ",
                     "without --no-rebase may be required."
        }
+
+       if (defined $_interactive){
+               my $ask_default = "y";
+               foreach my $d (@$linear_refs){
+                       my ($fh, $ctx) = command_output_pipe(qw(show --summary), "$d");
+                       while (<$fh>){
+                               print $_;
+                       }
+                       command_close_pipe($fh, $ctx);
+                       $_ = ask("Commit this patch to SVN? ([y]es (default)|[n]o|[q]uit|[a]ll): ",
+                                valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i,
+                                default => $ask_default);
+                       die "Commit this patch reply required" unless defined $_;
+                       if (/^[nq]/i) {
+                               exit(0);
+                       } elsif (/^a/i) {
+                               last;
+                       }
+               }
+       }
+
        my $expect_url = $url;
 
        my $push_merge_info = eval {
@@ -791,7 +867,7 @@ sub cmd_dcommit {
                                                         ."has uuid $uuid!";
                                        }
 
-                                       unless ($branchurl =~ /^$rooturl(.*)/) {
+                                       unless ($branchurl =~ /^\Q$rooturl\E(.*)/) {
                                                # This branch is very strange indeed.
                                                fatal "merge parent $parent for $d is on branch "
                                                         ."$branchurl, which is not under the "
@@ -2119,6 +2195,8 @@ sub read_all_remotes {
                        $r->{$1}->{url} = $2;
                } elsif (m!^(.+)\.pushurl=\s*(.*)\s*$!) {
                        $r->{$1}->{pushurl} = $2;
+               } elsif (m!^(.+)\.ignore-refs=\s*(.*)\s*$!) {
+                       $r->{$1}->{ignore_refs_regex} = $2;
                } elsif (m!^(.+)\.(branches|tags)=$svn_refspec$!) {
                        my ($remote, $t, $local_ref, $remote_ref) =
                                                             ($1, $2, $3, $4);
@@ -2155,6 +2233,16 @@ sub read_all_remotes {
                }
        } keys %$r;
 
+       foreach my $remote (keys %$r) {
+               foreach ( grep { defined $_ }
+                         map { $r->{$remote}->{$_} } qw(branches tags) ) {
+                       foreach my $rs ( @$_ ) {
+                               $rs->{ignore_refs_regex} =
+                                   $r->{$remote}->{ignore_refs_regex};
+                       }
+               }
+       }
+
        $r;
 }
 
@@ -5301,7 +5389,7 @@ sub apply_diff {
                                       $self->{mergeinfo});
        }
        $self->rmdirs if $_rmdir;
-       if (@$mods == 0) {
+       if (@$mods == 0 && !defined($self->{mergeinfo})) {
                $self->abort_edit;
        } else {
                $self->close_edit;
@@ -5310,7 +5398,7 @@ sub apply_diff {
 }
 
 package Git::SVN::Ra;
-use vars qw/@ISA $config_dir $_log_window_size/;
+use vars qw/@ISA $config_dir $_ignore_refs_regex $_log_window_size/;
 use strict;
 use warnings;
 my ($ra_invalid, $can_do_switch, %ignored_err, $RA);
@@ -5768,6 +5856,17 @@ sub get_dir_globbed {
        @finalents;
 }
 
+# return value: 0 -- don't ignore, 1 -- ignore
+sub is_ref_ignored {
+       my ($g, $p) = @_;
+       my $refname = $g->{ref}->full_path($p);
+       return 1 if defined($g->{ignore_refs_regex}) &&
+                   $refname =~ m!$g->{ignore_refs_regex}!;
+       return 0 unless defined($_ignore_refs_regex);
+       return 1 if $refname =~ m!$_ignore_refs_regex!o;
+       return 0;
+}
+
 sub match_globs {
        my ($self, $exists, $paths, $globs, $r) = @_;
 
@@ -5804,6 +5903,7 @@ sub match_globs {
                        next unless /$g->{path}->{regex}/;
                        my $p = $1;
                        my $pathname = $g->{path}->full_path($p);
+                       next if is_ref_ignored($g, $p);
                        next if $exists->{$pathname};
                        next if ($self->check_path($pathname, $r) !=
                                 $SVN::Node::dir);
index e9de241dd004a9f5d6d6d8e6a300a1fa5949a46d..1e827264b4cab04a801804b8d066f9d2cbb85edc 100755 (executable)
@@ -156,7 +156,7 @@ firefox|iceweasel|seamonkey|iceape)
        ;;
 google-chrome|chrome|chromium|chromium-browser)
        # No need to specify newTab. It's default in chromium
-       eval "$browser_path" "$@" &
+       "$browser_path" "$@" &
        ;;
 konqueror)
        case "$(basename "$browser_path")" in
@@ -164,10 +164,10 @@ konqueror)
                # It's simpler to use kfmclient to open a new tab in konqueror.
                browser_path="$(echo "$browser_path" | sed -e 's/konqueror$/kfmclient/')"
                type "$browser_path" > /dev/null 2>&1 || die "No '$browser_path' found."
-               eval "$browser_path" newTab "$@"
+               "$browser_path" newTab "$@" &
                ;;
        kfmclient)
-               eval "$browser_path" newTab "$@"
+               "$browser_path" newTab "$@" &
                ;;
        *)
                "$browser_path" "$@" &
@@ -175,7 +175,7 @@ konqueror)
        esac
        ;;
 w3m|elinks|links|lynx|open)
-       eval "$browser_path" "$@"
+       "$browser_path" "$@"
        ;;
 start)
        exec "$browser_path" '"web-browse"' "$@"
@@ -185,7 +185,7 @@ opera|dillo)
        ;;
 *)
        if test -n "$browser_cmd"; then
-               ( eval $browser_cmd "$@" )
+               ( eval "$browser_cmd \"\$@\"" )
        fi
        ;;
 esac
diff --git a/git.c b/git.c
index 8e34903a65c8775b19b993d3e9ddf47c23d5254e..380561663061f011189de883a865fbd59a922190 100644 (file)
--- a/git.c
+++ b/git.c
@@ -434,6 +434,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "update-ref", cmd_update_ref, RUN_SETUP },
                { "update-server-info", cmd_update_server_info, RUN_SETUP },
                { "upload-archive", cmd_upload_archive },
+               { "upload-archive--writer", cmd_upload_archive_writer },
                { "var", cmd_var, RUN_SETUP_GENTLY },
                { "verify-pack", cmd_verify_pack },
                { "verify-tag", cmd_verify_tag, RUN_SETUP },
@@ -494,7 +495,7 @@ 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, RUN_SILENT_EXEC_FAILURE);
+       status = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE | RUN_CLEAN_ON_EXIT);
        if (status >= 0 || errno != ENOENT)
                exit(status);
 
@@ -537,6 +538,8 @@ int main(int argc, const char **argv)
        if (!cmd)
                cmd = "git-help";
 
+       git_setup_gettext();
+
        /*
         * "git-xxxx" is the same as "git xxxx", but we obviously:
         *
index 91c8462b7dae171247c40db7f7b5b36d2c0633a1..b93df109c8f34ec25bd837aa7f1301b9f492efb0 100644 (file)
@@ -134,6 +134,7 @@ find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
 %else
 rm -rf $RPM_BUILD_ROOT%{_mandir}
 %endif
+rm -rf $RPM_BUILD_ROOT%{_datadir}/locale
 
 mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d
 install -m 644 -T contrib/completion/git-completion.bash $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/git
@@ -199,7 +200,11 @@ rm -rf $RPM_BUILD_ROOT
 
 %files -n gitweb
 %defattr(-,root,root)
+%doc gitweb/README gitweb/INSTALL Documentation/*gitweb*.txt
 %{_datadir}/gitweb
+%{!?_without_docs: %{_mandir}/man1/*gitweb*.1*}
+%{!?_without_docs: %{_mandir}/man5/*gitweb*.5*}
+%{!?_without_docs: %doc Documentation/*gitweb*.html }
 
 %files -n perl-Git -f perl-files
 %defattr(-,root,root)
@@ -208,6 +213,9 @@ rm -rf $RPM_BUILD_ROOT
 # No files for you!
 
 %changelog
+* Sun Sep 18 2011 Jakub Narebski <jnareb@gmail.com>
+- Add gitweb manpages to 'gitweb' subpackage
+
 * Wed Jun 30 2010 Junio C Hamano <gitster@pobox.com>
 - Add 'gitweb' subpackage.
 
index a383e6c08d5752df1ff42fa25019273dccdaebc8..007a1bfdf37d231470f69d9d0cffa46e80127f34 100644 (file)
@@ -54,7 +54,7 @@ def valid_git_ref (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.
+    # check_refname_format() in the git library, when such is available.
     if ref_name.endswith('/') or \
        ref_name.startswith('.') or \
        ref_name.count('/.') or \
index 4cde0c493b8ad425c09c63173c692a0ffa4ed632..64ef3c401367c96236d81de3d88957a6459867cd 100755 (executable)
@@ -2,20 +2,16 @@
 # Tcl ignores the next line -*- tcl -*- \
 exec wish "$0" -- "$@"
 
-# Copyright © 2005-2009 Paul Mackerras.  All rights reserved.
+# Copyright © 2005-2011 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)]} {
-       return $env(GIT_DIR)
-    } else {
-       return [exec git rev-parse --git-dir]
-    }
+proc hasworktree {} {
+    return [expr {[exec git rev-parse --is-bare-repository] == "false" &&
+                 [exec git rev-parse --is-inside-git-dir] == "false"}]
 }
 
 # A simple scheduler for compute-intensive stuff.
@@ -468,11 +464,11 @@ proc updatecommits {} {
     global viewactive viewcomplete tclencoding
     global startmsecs showneartags showlocalchanges
     global mainheadid viewmainheadid viewmainheadid_orig pending_select
-    global isworktree
+    global hasworktree
     global varcid vposids vnegids vflags vrevs
     global show_notes
 
-    set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
+    set hasworktree [hasworktree]
     rereadrefs
     set view $curview
     if {$mainheadid ne $viewmainheadid_orig($view)} {
@@ -659,7 +655,7 @@ proc newvarc {view id} {
        if {![info exists commitinfo($id)]} {
            parsecommit $id $commitdata($id) 1
        }
-       set cdate [lindex $commitinfo($id) 4]
+       set cdate [lindex [lindex $commitinfo($id) 4] 0]
        if {![string is integer -strict $cdate]} {
            set cdate 0
        }
@@ -1621,7 +1617,7 @@ proc readcommit {id} {
 }
 
 proc parsecommit {id contents listed} {
-    global commitinfo cdate
+    global commitinfo
 
     set inhdr 1
     set comment {}
@@ -1641,10 +1637,10 @@ proc parsecommit {id contents listed} {
        set line [split $line " "]
        set tag [lindex $line 0]
        if {$tag == "author"} {
-           set audate [lindex $line end-1]
+           set audate [lrange $line end-1 end]
            set auname [join [lrange $line 1 end-2] " "]
        } elseif {$tag == "committer"} {
-           set comdate [lindex $line end-1]
+           set comdate [lrange $line end-1 end]
            set comname [join [lrange $line 1 end-2] " "]
        }
     }
@@ -1671,11 +1667,9 @@ proc parsecommit {id contents listed} {
        }
        set comment $newcomment
     }
-    if {$comdate != {}} {
-       set cdate($id) $comdate
-    }
+    set hasnote [string first "\nNotes:\n" $contents]
     set commitinfo($id) [list $headline $auname $audate \
-                            $comname $comdate $comment]
+                            $comname $comdate $comment $hasnote]
 }
 
 proc getcommit {id} {
@@ -2437,9 +2431,9 @@ proc makewindow {} {
     bindkey n "selnextline 1"
     bindkey z "goback"
     bindkey x "goforw"
-    bindkey i "selnextline -1"
-    bindkey k "selnextline 1"
-    bindkey j "goback"
+    bindkey k "selnextline -1"
+    bindkey j "selnextline 1"
+    bindkey h "goback"
     bindkey l "goforw"
     bindkey b prevfile
     bindkey d "$ctext yview scroll 18 units"
@@ -2815,7 +2809,7 @@ proc about {} {
     message $w.m -text [mc "
 Gitk - a commit viewer for git
 
-Copyright \u00a9 2005-2010 Paul Mackerras
+Copyright \u00a9 2005-2011 Paul Mackerras
 
 Use and redistribute under the terms of the GNU General Public License"] \
            -justify center -aspect 400 -border 2 -bg white -relief groove
@@ -2850,9 +2844,9 @@ proc keys {} {
 [mc "<%s-W>            Close window" $M1T]
 [mc "<Home>            Move to first commit"]
 [mc "<End>             Move to last commit"]
-[mc "<Up>, p, i        Move up one commit"]
-[mc "<Down>, n, k      Move down one commit"]
-[mc "<Left>, z, j      Go back in history list"]
+[mc "<Up>, p, k        Move up one commit"]
+[mc "<Down>, n, j      Move down one commit"]
+[mc "<Left>, z, h      Go back in history list"]
 [mc "<Right>, x, l     Go forward in history list"]
 [mc "<PageUp>  Move up one page in commit list"]
 [mc "<PageDown>        Move down one page in commit list"]
@@ -3333,8 +3327,7 @@ proc gitknewtmpdir {} {
     global diffnum gitktmpdir gitdir
 
     if {![info exists gitktmpdir]} {
-       set gitktmpdir [file join [file dirname $gitdir] \
-                           [format ".gitk-tmp.%s" [pid]]]
+       set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]]
        if {[catch {file mkdir $gitktmpdir} err]} {
            error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
            unset gitktmpdir
@@ -3366,10 +3359,10 @@ proc save_file_from_commit {filename output what} {
 
 proc external_diff_get_one_file {diffid filename diffdir} {
     global nullid nullid2 nullfile
-    global gitdir
+    global worktree
 
     if {$diffid == $nullid} {
-        set difffile [file join [file dirname $gitdir] $filename]
+        set difffile [file join $worktree $filename]
        if {[file exists $difffile]} {
            return $difffile
        }
@@ -3559,7 +3552,7 @@ proc make_relative {f} {
 }
 
 proc external_blame {parent_idx {line {}}} {
-    global flist_menu_file gitdir
+    global flist_menu_file cdup
     global nullid nullid2
     global parentlist selectedline currentid
 
@@ -3578,7 +3571,7 @@ proc external_blame {parent_idx {line {}}} {
     if {$line ne {} && $line > 1} {
        lappend cmdline "--line=$line"
     }
-    set f [file join [file dirname $gitdir] $flist_menu_file]
+    set f [file join $cdup $flist_menu_file]
     # Unfortunately it seems git gui blame doesn't like
     # being given an absolute path...
     set f [make_relative $f]
@@ -3591,7 +3584,7 @@ proc external_blame {parent_idx {line {}}} {
 proc show_line_source {} {
     global cmitmode currentid parents curview blamestuff blameinst
     global diff_menu_line diff_menu_filebase flist_menu_file
-    global nullid nullid2 gitdir
+    global nullid nullid2 gitdir cdup
 
     set from_index {}
     if {$cmitmode eq "tree"} {
@@ -3644,7 +3637,7 @@ proc show_line_source {} {
     } else {
        lappend blameargs $id
     }
-    lappend blameargs -- [file join [file dirname $gitdir] $flist_menu_file]
+    lappend blameargs -- [file join $cdup $flist_menu_file]
     if {[catch {
        set f [open $blameargs r]
     } err]} {
@@ -4529,12 +4522,22 @@ proc makepatterns {l} {
 
 proc do_file_hl {serial} {
     global highlight_files filehighlight highlight_paths gdttype fhl_list
+    global cdup findtype
 
     if {$gdttype eq [mc "touching paths:"]} {
+       # If "exact" match then convert backslashes to forward slashes.
+       # Most useful to support Windows-flavoured file paths.
+       if {$findtype eq [mc "Exact"]} {
+           set highlight_files [string map {"\\" "/"} $highlight_files]
+       }
        if {[catch {set paths [shellsplit $highlight_files]}]} return
        set highlight_paths [makepatterns $paths]
        highlight_filelist
-       set gdtargs [concat -- $paths]
+       set relative_paths {}
+       foreach path $paths {
+           lappend relative_paths [file join $cdup $path]
+       }
+       set gdtargs [concat -- $relative_paths]
     } elseif {$gdttype eq [mc "adding/removing string:"]} {
        set gdtargs [list "-S$highlight_files"]
     } else {
@@ -5031,9 +5034,9 @@ proc dohidelocalchanges {} {
 # spawn off a process to do git diff-index --cached HEAD
 proc dodiffindex {} {
     global lserial showlocalchanges vfilelimit curview
-    global isworktree
+    global hasworktree
 
-    if {!$showlocalchanges || !$isworktree} return
+    if {!$showlocalchanges || !$hasworktree} return
     incr lserial
     set cmd "|git diff-index --cached HEAD"
     if {$vfilelimit($curview) ne {}} {
@@ -5899,6 +5902,9 @@ proc drawcmittext {id row col} {
        || [info exists idotherrefs($id)]} {
        set xt [drawtags $id $x $xt $y]
     }
+    if {[lindex $commitinfo($id) 6] > 0} {
+       set xt [drawnotesign $xt $y]
+    }
     set headline [lindex $commitinfo($id) 0]
     set name [lindex $commitinfo($id) 1]
     set date [lindex $commitinfo($id) 2]
@@ -6345,6 +6351,17 @@ proc drawtags {id x xt y1} {
     return $xt
 }
 
+proc drawnotesign {xt y} {
+    global linespc canv fgcolor
+
+    set orad [expr {$linespc / 3}]
+    set t [$canv create rectangle [expr {$xt - $orad}] [expr {$y - $orad}] \
+              [expr {$xt + $orad - 1}] [expr {$y + $orad - 1}] \
+              -fill yellow -outline $fgcolor -width 1 -tags circle]
+    set xt [expr {$xt + $orad * 3}]
+    return $xt
+}
+
 proc xcoord {i level ln} {
     global canvx0 xspc1 xspc2
 
@@ -9043,6 +9060,7 @@ proc exec_citool {tool_args {baseid {}}} {
 proc cherrypick {} {
     global rowmenuid curview
     global mainhead mainheadid
+    global gitdir
 
     set oldhead [exec git rev-parse HEAD]
     set dheads [descheads $rowmenuid]
@@ -9071,7 +9089,7 @@ proc cherrypick {} {
                        conflict.\nDo you wish to run git citool to\
                        resolve it?"]]} {
                # Force citool to read MERGE_MSG
-               file delete [file join [gitdir] "GITGUI_MSG"]
+               file delete [file join $gitdir "GITGUI_MSG"]
                exec_citool {} $rowmenuid
            }
        } else {
@@ -9437,6 +9455,7 @@ proc refill_reflist {} {
 proc getallcommits {} {
     global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
     global idheads idtags idotherrefs allparents tagobjid
+    global gitdir
 
     if {![info exists allcommits]} {
        set nextarc 0
@@ -9444,7 +9463,7 @@ proc getallcommits {} {
        set seeds {}
        set allcwait 0
        set cachedarcs 0
-       set allccache [file join [gitdir] "gitk.cache"]
+       set allccache [file join $gitdir "gitk.cache"]
        if {![catch {
            set f [open $allccache r]
            set allcwait 1
@@ -11024,7 +11043,7 @@ proc prefsok {} {
 proc formatdate {d} {
     global datetimeformat
     if {$d ne {}} {
-       set d [clock format $d -format $datetimeformat]
+       set d [clock format [lindex $d 0] -format $datetimeformat]
     }
     return $d
 }
@@ -11505,14 +11524,10 @@ setui $uicolor
 setoptions
 
 # check that we can find a .git directory somewhere...
-if {[catch {set gitdir [gitdir]}]} {
+if {[catch {set gitdir [exec git rev-parse --git-dir]}]} {
     show_error {} . [mc "Cannot find a git repository here."]
     exit 1
 }
-if {![file isdirectory $gitdir]} {
-    show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
-    exit 1
-}
 
 set selecthead {}
 set selectheadid {}
@@ -11628,7 +11643,12 @@ set stopped 0
 set stuffsaved 0
 set patchnum 0
 set lserial 0
-set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
+set hasworktree [hasworktree]
+set cdup {}
+if {[expr {[exec git rev-parse --is-inside-work-tree] == "true"}]} {
+    set cdup [exec git rev-parse --show-cdup]
+}
+set worktree [exec git rev-parse --show-toplevel]
 setcoords
 makewindow
 catch {
index f5efe7454ca42decbe7b113388bc6233e2311c1e..6d4540679731bdfd33af61635c59b798b19a8888 100644 (file)
@@ -130,6 +130,8 @@ You can specify the following configuration variables when building GIT:
    Points to an .html file which is included on the gitweb project
    overview page ('projects_list' view), if it exists.  Relative to
    gitweb.cgi script.  [Default: indextext.html]
+ * GITWEB_SITE_HTML_HEAD_STRING
+   html snippet to include in the <head> section of each page. [No default]
  * GITWEB_SITE_HEADER
    Filename of html text to include at top of each page.  Relative to
    gitweb.cgi script.  [No default]
@@ -229,7 +231,7 @@ Gitweb config file
 ------------------
 
 See also "Runtime gitweb configuration" section in README file
-for gitweb (in gitweb/README).
+for gitweb (in gitweb/README), and gitweb.conf(5) manpage.
 
 - You can configure gitweb further using the per-instance gitweb configuration file;
   by default this is a file named gitweb_config.perl in the same place as
@@ -287,97 +289,19 @@ adding the following lines to your $GITWEB_CONFIG:
 Gitweb repositories
 -------------------
 
-- By default all git repositories under projectroot are visible and
-  available to gitweb. The list of projects is generated by default by
-  scanning the projectroot directory for git repositories (for object
-  databases to be more exact).
-
-  You can provide a pre-generated list of [visible] repositories,
-  together with information about their owners (the project ownership
-  defaults to the owner of the repository directory otherwise), by setting
-  the GITWEB_LIST build configuration variable (or the $projects_list
-  variable in the gitweb config file) to point to a plain file.
-
-  Each line of the projects list file should consist of the url-encoded path
-  to the project repository database (relative to projectroot), followed
-  by the url-encoded project owner on the same line (separated by a space).
-  Spaces in both project path and project owner have to be encoded as either
-  '%20' or '+'.
-
-  Other characters that have to be url-encoded, i.e. replaced by '%'
-  followed by two-digit character number in octal, are: other whitespace
-  characters (because they are field separator in a record), plus sign '+'
-  (because it can be used as replacement for spaces), and percent sign '%'
-  (which is used for encoding / escaping).
-
-  You can generate the projects list index file using the project_index
-  action (the 'TXT' link on projects list page) directly from gitweb.
-
-- By default, even if a project is not visible on projects list page, you
-  can view it nevertheless by hand-crafting a gitweb URL. You can set the
-  GITWEB_STRICT_EXPORT build configuration variable (or the $strict_export
-  variable in the gitweb config file) to only allow viewing of
-  repositories also shown on the overview page.
-
-- Alternatively, you can configure gitweb to only list and allow
-  viewing of the explicitly exported repositories, via the
-  GITWEB_EXPORT_OK build configuration variable (or the $export_ok
-  variable in gitweb config file). If it evaluates to true, gitweb
-  shows repositories only if this file exists in its object database
-  (if directory has the magic file named $export_ok).
-
-- Finally, it is possible to specify an arbitrary perl subroutine that
-  will be called for each project to determine if it can be exported.
-  The subroutine receives an absolute path to the project as its only
-  parameter.
-
-  For example, if you use mod_perl to run the script, and have dumb
-  http protocol authentication configured for your repositories, you
-  can use the following hook to allow access only if the user is
-  authorized to read the files:
-
-    $export_auth_hook = sub {
-        use Apache2::SubRequest ();
-        use Apache2::Const -compile => qw(HTTP_OK);
-        my $path = "$_[0]/HEAD";
-        my $r    = Apache2::RequestUtil->request;
-        my $sub  = $r->lookup_file($path);
-        return $sub->filename eq $path
-            && $sub->status == Apache2::Const::HTTP_OK;
-    };
-
-
-Generating projects list using gitweb
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We assume that GITWEB_CONFIG has its default Makefile value, namely
-gitweb_config.perl. Put the following in gitweb_make_index.perl file:
-
-       $GITWEB_CONFIG = "gitweb_config.perl";
-       do $GITWEB_CONFIG if -e $GITWEB_CONFIG;
-
-       $projects_list = $projectroot;
-
-Then create the following script to get list of project in the format
-suitable for GITWEB_LIST build configuration variable (or
-$projects_list variable in gitweb config):
-
-       #!/bin/sh
-
-       export GITWEB_CONFIG="gitweb_make_index.perl"
-       export GATEWAY_INTERFACE="CGI/1.1"
-       export HTTP_ACCEPT="*/*"
-       export REQUEST_METHOD="GET"
-       export QUERY_STRING="a=project_index"
-
-       perl -- /var/www/cgi-bin/gitweb.cgi
+By default gitweb shows all git repositories under single common repository
+root on a local filesystem; see description of GITWEB_PROJECTROOT build-time
+configuration variable above (and also of GITWEB_LIST).
+
+More advanced usage, like limiting access or visibility of repositories and
+managing multiple roots are described on gitweb manpage.
 
 
 Example web server configuration
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-See also "Webserver configuration" section in README file for gitweb
-(in gitweb/README).
+See also "Webserver configuration" and "Advanced web server setup" sections
+in gitweb(1) manpage.
 
 
 - Apache2, gitweb installed as CGI script,
index 1c85b5fda8bc994e0ecd249e11e8f2331098bea9..cd194d057f9231ed77f57f05b22a226cd1210f6d 100644 (file)
@@ -34,6 +34,7 @@ GITWEB_CSS = static/gitweb.css
 GITWEB_LOGO = static/git-logo.png
 GITWEB_FAVICON = static/git-favicon.png
 GITWEB_JS = static/gitweb.js
+GITWEB_SITE_HTML_HEAD_STRING =
 GITWEB_SITE_HEADER =
 GITWEB_SITE_FOOTER =
 HIGHLIGHT_BIN = highlight
@@ -144,6 +145,7 @@ GITWEB_REPLACE = \
        -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_HTML_HEAD_STRING++|$(GITWEB_SITE_HTML_HEAD_STRING)|g' \
        -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
        -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
        -e 's|++HIGHLIGHT_BIN++|$(HIGHLIGHT_BIN)|g'
@@ -185,7 +187,9 @@ install: all
 ### Cleaning rules
 
 clean:
-       $(RM) gitweb.cgi static/gitweb.min.js static/gitweb.min.css GITWEB-BUILD-OPTIONS
+       $(RM) gitweb.cgi static/gitweb.js \
+               static/gitweb.min.js static/gitweb.min.css \
+               GITWEB-BUILD-OPTIONS
 
 .PHONY: all clean install test test-installed .FORCE-GIT-VERSION-FILE FORCE
 
index a9988200d6b67066b837e7298e0e1529057a4bca..6da4778b73e3ef1d098fce7655bf4ca5bb2443e5 100644 (file)
@@ -7,9 +7,18 @@ The one working on:
 From the git version 1.4.0 gitweb is bundled with git.
 
 
+Build time gitweb configuration
+-------------------------------
+There are many configuration variables which affect building gitweb (among
+others creating gitweb.cgi out of gitweb.perl by replacing placeholders such
+as `++GIT_BINDIR++` by their build-time values).
+
+Building and installing gitweb is described in gitweb's INSTALL file
+(in 'gitweb/INSTALL').
+
+
 Runtime gitweb configuration
 ----------------------------
-
 Gitweb obtains configuration data from the following sources in the
 following order:
 
@@ -41,400 +50,22 @@ Ultimate description on how to reconfigure the default features setting
 in your `GITWEB_CONFIG` or per-project in `project.git/config` can be found
 as comments inside 'gitweb.cgi'.
 
-See also the "Gitweb config file" (with an example of config file), and
-the "Gitweb repositories" sections in INSTALL file for gitweb.
-
-
-The gitweb config file is a fragment of perl code. You can set variables
-using "our $variable = value"; text from "#" character until the end
-of a line is ignored. See perlsyn(1) man page for details.
-
-Below is the list of variables which you might want to set in gitweb config.
-See the top of 'gitweb.cgi' for the full list of variables and their
-descriptions.
-
-Gitweb config file variables
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can set, among others, the following variables in gitweb config files
-(with the exception of $projectroot and $projects_list this list does
-not include variables usually directly set during build):
- * $GIT
-   Core git executable to use.  By default set to "$GIT_BINDIR/git", which
-   in turn is by default set to "$(bindir)/git".  If you use git from binary
-   package, set this to "/usr/bin/git".  This can just be "git" if your
-   webserver has a sensible PATH.  If you have multiple git versions
-   installed it can be used to choose which one to use.
- * $version
-   Gitweb version, set automatically when creating gitweb.cgi from
-   gitweb.perl. You might want to modify it if you are running modified
-   gitweb.
- * $projectroot
-   Absolute filesystem path which will be prepended to project path;
-   the path to repository is $projectroot/$project.  Set to
-   $GITWEB_PROJECTROOT during installation.  This variable have to be
-   set correctly for gitweb to find repositories.
- * $projects_list
-   Source of projects list, either directory to scan, or text file
-   with list of repositories (in the "<URI-encoded repository path> SP
-   <URI-encoded repository owner>" line format; actually there can be
-   any sequence of whitespace in place of space (SP)).  Set to
-   $GITWEB_LIST during installation.  If empty, $projectroot is used
-   to scan for repositories.
- * $my_url, $my_uri
-   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.  See
-   $per_request_config if you need to set them still.
- * $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 "/".
-   See $per_request_config if you need to set it anyway.
- * $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).
- * @stylesheets
-   List of URIs of stylesheets (relative to base URI of a page). You
-   might specify more than one stylesheet, for example use gitweb.css
-   as base, with site specific modifications in separate stylesheet
-   to make it easier to upgrade gitweb. You can add 'site' stylesheet
-   for example by using
-      push @stylesheets, "gitweb-site.css";
-   in the gitweb config file.
- * $logo_url, $logo_label
-   URI and label (title) of GIT logo link (or your site logo, if you choose
-   to use different logo image). By default they point to git homepage;
-   in the past they pointed to git documentation at www.kernel.org.
- * $projects_list_description_width
-   The width (in characters) of the projects list "Description" column.
-   Longer descriptions will be cut (trying to cut at word boundary);
-   full description is available as 'title' attribute (usually shown on
-   mouseover).  By default set to 25, which might be too small if you
-   use long project descriptions.
- * $projects_list_group_categories
-   Enables the grouping of projects by category on the project list page.
-   The category of a project is determined by the $GIT_DIR/category
-   file or the 'gitweb.category' variable in its repository configuration.
-   Disabled by default.
- * $project_list_default_category
-   Default category for projects for which none is specified.  If set
-   to the empty string, such projects will remain uncategorized and
-   listed at the top, above categorized projects.
- * @git_base_url_list
-   List of git base URLs used for URL to where fetch project from, shown
-   in project summary page.  Full URL is "$git_base_url/$project".
-   You can setup multiple base URLs (for example one for  git:// protocol
-   access, and one for http:// "dumb" protocol access).  Note that per
-   repository configuration in 'cloneurl' file, or as values of gitweb.url
-   project config.
- * $default_blob_plain_mimetype
-   Default mimetype for blob_plain (raw) view, if mimetype checking
-   doesn't result in some other type; by default 'text/plain'.
- * $default_text_plain_charset
-   Default charset for text files. If not set, web server configuration
-   would be used.
- * $mimetypes_file
-   File to use for (filename extension based) guessing of MIME types before
-   trying /etc/mime.types. Path, if relative, is taken currently as
-   relative to the current git repository.
- * $fallback_encoding
-   Gitweb assumes this charset if line contains non-UTF-8 characters.
-   Fallback decoding is used without error checking, so it can be even
-   'utf-8'. Value must be valid encoding; see Encoding::Supported(3pm) man
-   page for a list.   By default 'latin1', aka. 'iso-8859-1'.
- * @diff_opts
-   Rename detection options for git-diff and git-diff-tree. By default
-   ('-M'); set it to ('-C') or ('-C', '-C') to also detect copies, or
-   set it to () if you don't want to have renames detection.
- * $prevent_xss
-   If true, some gitweb features are disabled to prevent content in
-   repositories from launching cross-site scripting (XSS) attacks.  Set this
-   to true if you don't trust the content of your repositories. The default
-   is false.
- * $maxload
-   Used to set the maximum load that we will still respond to gitweb queries.
-   If server load exceed this value then return "503 Service Unavailable" error.
-   Server load is taken to be 0 if gitweb cannot determine its value.  Set it to
-   undefined value to turn it off.  The default is 300.
- * $highlight_bin
-   Path to the highlight executable to use (must be the one from
-   http://www.andre-simon.de due to assumptions about parameters and output).
-   Useful if highlight is not installed on your webserver's PATH.
-   [Default: highlight]
- * $per_request_config
-   If set to code reference, it would be run once per each request.  You can
-   set parts of configuration that change per session, e.g. by setting it to
-     sub { $ENV{GL_USER} = $cgi->remote_user || "gitweb"; }
-   Otherwise it is treated as boolean value: if true gitweb would process
-   config file once per request, if false it would process config file only
-   once.  Note: $my_url, $my_uri, and $base_url are overwritten with
-   their default values before every request, so if you want to change
-   them, be sure to set this variable to true or a code reference effecting
-   the desired changes.  The default is true.
-
-Projects list file format
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Instead of having gitweb find repositories by scanning filesystem starting
-from $projectroot (or $projects_list, if it points to directory), you can
-provide list of projects by setting $projects_list to a text file with list
-of projects (and some additional info).  This file uses the following
-format:
-
-One record (for project / repository) per line, whitespace separated fields;
-does not support (at least for now) lines continuation (newline escaping).
-Leading and trailing whitespace are ignored, any run of whitespace can be
-used as field separator (rules for Perl's "split(' ', $line)").  Keyed by
-the first field, which is project name, i.e. path to repository GIT_DIR
-relative to $projectroot.  Fields use modified URI encoding, defined in
-RFC 3986, section 2.1 (Percent-Encoding), or rather "Query string encoding"
-(see http://en.wikipedia.org/wiki/Query_string#URL_encoding), the difference
-being that SP (' ') can be encoded as '+' (and therefore '+' has to be also
-percent-encoded).  Reserved characters are: '%' (used for encoding), '+'
-(can be used to encode SPACE), all whitespace characters as defined in Perl,
-including SP, TAB and LF, (used to separate fields in a record).
-
-Currently list of fields is
- * <repository path>  - path to repository GIT_DIR, relative to $projectroot
- * <repository owner> - displayed as repository owner, preferably full name,
-                        or email, or both
-
-You can additionally use $projects_list file to limit which repositories
-are visible, and together with $strict_export to limit access to
-repositories (see "Gitweb repositories" section in gitweb/INSTALL).
-
-
-Per-repository gitweb configuration
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can also configure individual repositories shown in gitweb by creating
-file in the GIT_DIR of git repository, or by setting some repo configuration
-variable (in GIT_DIR/config).
-
-You can use the following files in repository:
- * README.html
-   A .html file (HTML fragment) which is included on the gitweb project
-   summary page inside <div> block element. You can use it for longer
-   description of a project, to provide links (for example to project's
-   homepage), etc. This is recognized only if XSS prevention is off
-   ($prevent_xss is false); a way to include a readme safely when XSS
-   prevention is on may be worked out in the future.
- * description (or gitweb.description)
-   Short (shortened by default to 25 characters in the projects list page)
-   single line description of a project (of a repository). Plain text file;
-   HTML will be escaped. By default set to
-     Unnamed repository; edit this file to name it for gitweb.
-   from the template during repository creation. You can use the
-   gitweb.description repo configuration variable, but the file takes
-   precedence.
- * category (or gitweb.category)
-   Singe line category of a project, used to group projects if
-   $projects_list_group_categories is enabled. By default (file and
-   configuration variable absent), uncategorized projects are put in
-   the $project_list_default_category category. You can use the
-   gitweb.category repo configuration variable, but the file takes
-   precedence.
- * cloneurl (or multiple-valued gitweb.url)
-   File with repository URL (used for clone and fetch), one per line.
-   Displayed in the project summary page. You can use multiple-valued
-   gitweb.url repository configuration variable for that, but the file
-   takes precedence.
- * gitweb.owner
-   You can use the gitweb.owner repository configuration variable to set
-   repository's owner. It is displayed in the project list and summary
-   page. If it's not set, filesystem directory's owner is used
-   (via GECOS field / real name field from getpwiud(3)).
- * various gitweb.* config variables (in config)
-   Read description of %feature hash for detailed list, and some
-   descriptions.
-
-
-Webserver configuration
------------------------
-
-If you want to have one URL for both gitweb and your http://
-repositories, you can configure apache like this:
-
-<VirtualHost *:80>
-    ServerName         git.example.org
-    DocumentRoot       /pub/git
-    SetEnv                     GITWEB_CONFIG   /etc/gitweb.conf
-
-    # turning on mod rewrite
-    RewriteEngine on
-
-    # make the front page an internal rewrite to the gitweb script
-    RewriteRule ^/$  /cgi-bin/gitweb.cgi
-
-    # make access for "dumb clients" work
-    RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI}  [L,PT]
-</VirtualHost>
-
-The above configuration expects your public repositories to live under
-/pub/git and will serve them as http://git.domain.org/dir-under-pub-git,
-both as cloneable GIT URL and as browseable gitweb interface.
-If you then start your git-daemon with --base-path=/pub/git --export-all
-then you can even use the git:// URL with exactly the same path.
-
-Setting the environment variable GITWEB_CONFIG will tell gitweb to use
-the named file (i.e. in this example /etc/gitweb.conf) as a
-configuration for gitweb.  Perl variables defined in here will
-override the defaults given at the head of the gitweb.perl (or
-gitweb.cgi).  Look at the comments in that file for information on
-which variables and what they mean.
-
-If you use the rewrite rules from the example you'll likely also need
-something like the following in your gitweb.conf (or gitweb_config.perl) file:
-
-  @stylesheets = ("/some/absolute/path/gitweb.css");
-  $my_uri = "/";
-  $home_link = "/";
-
-
-Webserver configuration with multiple projects' root
-----------------------------------------------------
-
-If you want to use gitweb with several project roots you can edit your apache
-virtual host and gitweb.conf configuration files like this :
-
-virtual host configuration :
-
-<VirtualHost *:80>
-    ServerName                 git.example.org
-    DocumentRoot               /pub/git
-    SetEnv                             GITWEB_CONFIG   /etc/gitweb.conf
-
-    # turning on mod rewrite
-    RewriteEngine on
-
-    # make the front page an internal rewrite to the gitweb script
-    RewriteRule ^/$            /cgi-bin/gitweb.cgi [QSA,L,PT]
-
-    # look for a public_git folder in unix users' home
-    # http://git.example.org/~<user>/
-    RewriteRule ^/\~([^\/]+)(/|/gitweb.cgi)?$  /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
-
-    # http://git.example.org/+<user>/
-    #RewriteRule ^/\+([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
-
-    # http://git.example.org/user/<user>/
-    #RewriteRule ^/user/([^\/]+)/(gitweb.cgi)?$        /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
-
-    # defined list of project roots
-    RewriteRule ^/scm(/|/gitweb.cgi)?$         /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/pub/scm/,L,PT]
-    RewriteRule ^/var(/|/gitweb.cgi)?$         /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/var/git/,L,PT]
-
-    # make access for "dumb clients" work
-    RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI}  [L,PT]
-</VirtualHost>
-
-gitweb.conf configuration :
-
-$projectroot = $ENV{'GITWEB_PROJECTROOT'} || "/pub/git";
-
-These configurations enable two things. First, each unix user (<user>) of the
-server will be able to browse through gitweb git repositories found in
-~/public_git/ with the following url : http://git.example.org/~<user>/
-
-If you do not want this feature on your server just remove the second rewrite rule.
-
-If you already use mod_userdir in your virtual host or you don't want to use
-the '~' as first character just comment or remove the second rewrite rule and
-uncomment one of the following according to what you want.
-
-Second, repositories found in /pub/scm/ and /var/git/ will be accesible
-through http://git.example.org/scm/ and http://git.example.org/var/.
-You can add as many project roots as you want by adding rewrite rules like the
-third and the fourth.
-
-
-PATH_INFO usage
------------------------
-If you enable PATH_INFO usage in gitweb by putting
-
-   $feature{'pathinfo'}{'default'} = [1];
-
-in your gitweb.conf, it is possible to set up your server so that it
-consumes and produces URLs in the form
-
-http://git.example.com/project.git/shortlog/sometag
-
-by using a configuration such as the following, that assumes that
-/var/www/gitweb is the DocumentRoot of your webserver, and that it
-contains the gitweb.cgi script and complementary static files
-(stylesheet, favicon):
-
-<VirtualHost *:80>
-       ServerAlias git.example.com
-
-       DocumentRoot /var/www/gitweb
-
-       <Directory /var/www/gitweb>
-               Options ExecCGI
-               AddHandler cgi-script cgi
-
-               DirectoryIndex gitweb.cgi
-
-               RewriteEngine On
-               RewriteCond %{REQUEST_FILENAME} !-f
-               RewriteCond %{REQUEST_FILENAME} !-d
-               RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
-       </Directory>
-</VirtualHost>
-
-The rewrite rule guarantees that existing static files will be properly
-served, whereas any other URL will be passed to gitweb as PATH_INFO
-parameter.
-
-Notice that in this case you don't need special settings for
-@stylesheets, $my_uri and $home_link, but you lose "dumb client" access
-to your project .git dirs. A possible workaround for the latter is the
-following: in your project root dir (e.g. /pub/git) have the projects
-named without a .git extension (e.g. /pub/git/project instead of
-/pub/git/project.git) and configure Apache as follows:
-
-<VirtualHost *:80>
-       ServerAlias git.example.com
-
-       DocumentRoot /var/www/gitweb
-
-       AliasMatch ^(/.*?)(\.git)(/.*)?$ /pub/git$1$3
-       <Directory /var/www/gitweb>
-               Options ExecCGI
-               AddHandler cgi-script cgi
-
-               DirectoryIndex gitweb.cgi
-
-               RewriteEngine On
-               RewriteCond %{REQUEST_FILENAME} !-f
-               RewriteCond %{REQUEST_FILENAME} !-d
-               RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
-       </Directory>
-</VirtualHost>
-
-The additional AliasMatch makes it so that
-
-http://git.example.com/project.git
-
-will give raw access to the project's git dir (so that the project can
-be cloned), while
-
-http://git.example.com/project
-
-will provide human-friendly gitweb access.
-
-This solution is not 100% bulletproof, in the sense that if some project
-has a named ref (branch, tag) starting with 'git/', then paths such as
+See also gitweb.conf(5) manpage.
 
-http://git.example.com/project/command/abranch..git/abranch
 
-will fail with a 404 error.
+Web server configuration
+------------------------
+Gitweb can be run as CGI script, as legacy mod_perl application (using
+ModPerl::Registry), and as FastCGI script.  You can find some simple examples
+in "Example web server configuration" section in INSTALL file for gitweb (in
+gitweb/INSTALL).
 
+See "Webserver configuration" and "Advanced web server setup" sections in
+gitweb(1) manpage.
 
 
+AUTHORS
+-------
 Originally written by:
   Kay Sievers <kay.sievers@vrfy.org>
 
index a95226e089fa22d7041e4374382bb2709da10df1..b67972ec5d9e2ddd78ffccada03361ed0bc63a14 100755 (executable)
@@ -52,7 +52,7 @@ sub evaluate_uri {
        # as base URL.
        # Therefore, if we needed to strip PATH_INFO, then we know that we have
        # to build the base URL ourselves:
-       our $path_info = $ENV{"PATH_INFO"};
+       our $path_info = decode_utf8($ENV{"PATH_INFO"});
        if ($path_info) {
                if ($my_url =~ s,\Q$path_info\E$,, &&
                    $my_uri =~ s,\Q$path_info\E$,, &&
@@ -85,6 +85,8 @@ our $home_link_str = "++GITWEB_HOME_LINK_STR++";
 our $site_name = "++GITWEB_SITENAME++"
                  || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
 
+# html snippet to include in the <head> section of each page
+our $site_html_head_string = "++GITWEB_SITE_HTML_HEAD_STRING++";
 # filename of html text to include at top of each page
 our $site_header = "++GITWEB_SITE_HEADER++";
 # html text to include at home page
@@ -757,6 +759,7 @@ our @cgi_param_mapping = (
        extra_options => "opt",
        search_use_regexp => "sr",
        ctag => "by_tag",
+       diff_style => "ds",
        # this must be last entry (for manipulation from JavaScript)
        javascript => "js"
 );
@@ -813,9 +816,9 @@ sub evaluate_query_params {
 
        while (my ($name, $symbol) = each %cgi_param_mapping) {
                if ($symbol eq 'opt') {
-                       $input_params{$name} = [ $cgi->param($symbol) ];
+                       $input_params{$name} = [ map { decode_utf8($_) } $cgi->param($symbol) ];
                } else {
-                       $input_params{$name} = $cgi->param($symbol);
+                       $input_params{$name} = decode_utf8($cgi->param($symbol));
                }
        }
 }
@@ -1070,7 +1073,16 @@ sub evaluate_and_validate_params {
                if (length($searchtext) < 2) {
                        die_error(403, "At least two characters are required for search parameter");
                }
-               $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
+               if ($search_use_regexp) {
+                       $search_regexp = $searchtext;
+                       if (!eval { qr/$search_regexp/; 1; }) {
+                               (my $error = $@) =~ s/ at \S+ line \d+.*\n?//;
+                               die_error(400, "Invalid search regexp '$search_regexp'",
+                                         esc_html($error));
+                       }
+               } else {
+                       $search_regexp = quotemeta $searchtext;
+               }
        }
 }
 
@@ -1120,8 +1132,10 @@ sub dispatch {
        if (!defined $action) {
                if (defined $hash) {
                        $action = git_get_type($hash);
+                       $action or die_error(404, "Object does not exist");
                } elsif (defined $hash_base && defined $file_name) {
                        $action = git_get_type("$hash_base:$file_name");
+                       $action or die_error(404, "File or directory does not exist");
                } elsif (defined $project) {
                        $action = 'summary';
                } else {
@@ -1440,8 +1454,8 @@ sub validate_refname {
 sub to_utf8 {
        my $str = shift;
        return undef unless defined $str;
-       if (utf8::valid($str)) {
-               utf8::decode($str);
+
+       if (utf8::is_utf8($str) || utf8::decode($str)) {
                return $str;
        } else {
                return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
@@ -1693,6 +1707,7 @@ sub chop_and_escape_str {
        my ($str) = @_;
 
        my $chopped = chop_str(@_);
+       $str = to_utf8($str);
        if ($chopped eq $str) {
                return esc_html($chopped);
        } else {
@@ -2223,93 +2238,119 @@ sub format_diff_cc_simplified {
        return $result;
 }
 
-# format patch (diff) line (not to be used for diff headers)
-sub format_diff_line {
-       my $line = shift;
-       my ($from, $to) = @_;
-       my $diff_class = "";
-
-       chomp $line;
+sub diff_line_class {
+       my ($line, $from, $to) = @_;
 
+       # ordinary diff
+       my $num_sign = 1;
+       # combined diff
        if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
-               # combined diff
-               my $prefix = substr($line, 0, scalar @{$from->{'href'}});
-               if ($line =~ m/^\@{3}/) {
-                       $diff_class = " chunk_header";
-               } elsif ($line =~ m/^\\/) {
-                       $diff_class = " incomplete";
-               } elsif ($prefix =~ tr/+/+/) {
-                       $diff_class = " add";
-               } elsif ($prefix =~ tr/-/-/) {
-                       $diff_class = " rem";
-               }
-       } else {
-               # assume ordinary diff
-               my $char = substr($line, 0, 1);
-               if ($char eq '+') {
-                       $diff_class = " add";
-               } elsif ($char eq '-') {
-                       $diff_class = " rem";
-               } elsif ($char eq '@') {
-                       $diff_class = " chunk_header";
-               } elsif ($char eq "\\") {
-                       $diff_class = " incomplete";
-               }
+               $num_sign = scalar @{$from->{'href'}};
+       }
+
+       my @diff_line_classifier = (
+               { regexp => qr/^\@\@{$num_sign} /, class => "chunk_header"},
+               { regexp => qr/^\\/,               class => "incomplete"  },
+               { regexp => qr/^ {$num_sign}/,     class => "ctx" },
+               # classifier for context must come before classifier add/rem,
+               # or we would have to use more complicated regexp, for example
+               # qr/(?= {0,$m}\+)[+ ]{$num_sign}/, where $m = $num_sign - 1;
+               { regexp => qr/^[+ ]{$num_sign}/,   class => "add" },
+               { regexp => qr/^[- ]{$num_sign}/,   class => "rem" },
+       );
+       for my $clsfy (@diff_line_classifier) {
+               return $clsfy->{'class'}
+                       if ($line =~ $clsfy->{'regexp'});
        }
-       $line = untabify($line);
-       if ($from && $to && $line =~ m/^\@{2} /) {
-               my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
-                       $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
 
-               $from_lines = 0 unless defined $from_lines;
-               $to_lines   = 0 unless defined $to_lines;
+       # fallback
+       return "";
+}
 
-               if ($from->{'href'}) {
-                       $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
-                                            -class=>"list"}, $from_text);
-               }
-               if ($to->{'href'}) {
-                       $to_text   = $cgi->a({-href=>"$to->{'href'}#l$to_start",
-                                            -class=>"list"}, $to_text);
-               }
-               $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
-                       "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
-               return "<div class=\"diff$diff_class\">$line</div>\n";
-       } elsif ($from && $to && $line =~ m/^\@{3}/) {
-               my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
-               my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
+# assumes that $from and $to are defined and correctly filled,
+# and that $line holds a line of chunk header for unified diff
+sub format_unidiff_chunk_header {
+       my ($line, $from, $to) = @_;
 
-               @from_text = split(' ', $ranges);
-               for (my $i = 0; $i < @from_text; ++$i) {
-                       ($from_start[$i], $from_nlines[$i]) =
-                               (split(',', substr($from_text[$i], 1)), 0);
-               }
+       my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
+               $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
 
-               $to_text   = pop @from_text;
-               $to_start  = pop @from_start;
-               $to_nlines = pop @from_nlines;
+       $from_lines = 0 unless defined $from_lines;
+       $to_lines   = 0 unless defined $to_lines;
 
-               $line = "<span class=\"chunk_info\">$prefix ";
-               for (my $i = 0; $i < @from_text; ++$i) {
-                       if ($from->{'href'}[$i]) {
-                               $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
-                                                 -class=>"list"}, $from_text[$i]);
-                       } else {
-                               $line .= $from_text[$i];
-                       }
-                       $line .= " ";
-               }
-               if ($to->{'href'}) {
-                       $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
-                                         -class=>"list"}, $to_text);
+       if ($from->{'href'}) {
+               $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
+                                    -class=>"list"}, $from_text);
+       }
+       if ($to->{'href'}) {
+               $to_text   = $cgi->a({-href=>"$to->{'href'}#l$to_start",
+                                    -class=>"list"}, $to_text);
+       }
+       $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
+               "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
+       return $line;
+}
+
+# assumes that $from and $to are defined and correctly filled,
+# and that $line holds a line of chunk header for combined diff
+sub format_cc_diff_chunk_header {
+       my ($line, $from, $to) = @_;
+
+       my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
+       my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
+
+       @from_text = split(' ', $ranges);
+       for (my $i = 0; $i < @from_text; ++$i) {
+               ($from_start[$i], $from_nlines[$i]) =
+                       (split(',', substr($from_text[$i], 1)), 0);
+       }
+
+       $to_text   = pop @from_text;
+       $to_start  = pop @from_start;
+       $to_nlines = pop @from_nlines;
+
+       $line = "<span class=\"chunk_info\">$prefix ";
+       for (my $i = 0; $i < @from_text; ++$i) {
+               if ($from->{'href'}[$i]) {
+                       $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
+                                         -class=>"list"}, $from_text[$i]);
                } else {
-                       $line .= $to_text;
+                       $line .= $from_text[$i];
                }
-               $line .= " $prefix</span>" .
-                        "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
-               return "<div class=\"diff$diff_class\">$line</div>\n";
+               $line .= " ";
        }
-       return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
+       if ($to->{'href'}) {
+               $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
+                                 -class=>"list"}, $to_text);
+       } else {
+               $line .= $to_text;
+       }
+       $line .= " $prefix</span>" .
+                "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
+       return $line;
+}
+
+# process patch (diff) line (not to be used for diff headers),
+# returning class and HTML-formatted (but not wrapped) line
+sub process_diff_line {
+       my $line = shift;
+       my ($from, $to) = @_;
+
+       my $diff_class = diff_line_class($line, $from, $to);
+
+       chomp $line;
+       $line = untabify($line);
+
+       if ($from && $to && $line =~ m/^\@{2} /) {
+               $line = format_unidiff_chunk_header($line, $from, $to);
+               return $diff_class, $line;
+
+       } elsif ($from && $to && $line =~ m/^\@{3}/) {
+               $line = format_cc_diff_chunk_header($line, $from, $to);
+               return $diff_class, $line;
+
+       }
+       return $diff_class, esc_html($line, -nbsp=>1);
 }
 
 # Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
@@ -2361,7 +2402,7 @@ sub get_feed_info {
        return unless (defined $project);
        # some views should link to OPML, or to generic project feed,
        # or don't have specific feed yet (so they should use generic)
-       return if ($action =~ /^(?:tags|heads|forks|tag|search)$/x);
+       return if (!$action || $action =~ /^(?:tags|heads|forks|tag|search)$/x);
 
        my $branch;
        # branches refs uses 'refs/heads/' prefix (fullname) to differentiate
@@ -2735,7 +2776,7 @@ sub git_populate_project_tagcloud {
        }
 
        my $cloud;
-       my $matched = $cgi->param('by_tag');
+       my $matched = $input_params{'ctag'};
        if (eval { require HTML::TagCloud; 1; }) {
                $cloud = HTML::TagCloud->new;
                foreach my $ctag (sort keys %ctags_lc) {
@@ -2806,8 +2847,8 @@ sub git_get_projects_list {
                my $dir = $projects_list;
                # remove the trailing "/"
                $dir =~ s!/+$!!;
-               my $pfxlen = length("$projects_list");
-               my $pfxdepth = ($projects_list =~ tr!/!!);
+               my $pfxlen = length("$dir");
+               my $pfxdepth = ($dir =~ tr!/!!);
                # when filtering, search only given subdirectory
                if ($filter) {
                        $dir .= "/$filter";
@@ -2939,10 +2980,10 @@ sub filter_forks_from_projects_list {
 sub search_projects_list {
        my ($projlist, %opts) = @_;
        my $tagfilter  = $opts{'tagfilter'};
-       my $searchtext = $opts{'searchtext'};
+       my $search_re = $opts{'search_regexp'};
 
        return @$projlist
-               unless ($tagfilter || $searchtext);
+               unless ($tagfilter || $search_re);
 
        my @projects;
  PROJECT:
@@ -2954,10 +2995,10 @@ sub search_projects_list {
                                grep { lc($_) eq lc($tagfilter) } keys %{$pr->{'ctags'}};
                }
 
-               if ($searchtext) {
+               if ($search_re) {
                        next unless
-                               $pr->{'path'} =~ /$searchtext/ ||
-                               $pr->{'descr_long'} =~ /$searchtext/;
+                               $pr->{'path'} =~ /$search_re/ ||
+                               $pr->{'descr_long'} =~ /$search_re/;
                }
 
                push @projects, $pr;
@@ -3841,7 +3882,7 @@ sub print_search_form {
                               -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
              $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
              " search:\n",
-             $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
+             $cgi->textfield(-name => "s", -value => $searchtext, -override => 1) . "\n" .
              "<span title=\"Extended regular expression\">" .
              $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
                             -checked => $search_use_regexp) .
@@ -3879,6 +3920,11 @@ EOF
                print "<base href=\"".esc_url($base_url)."\" />\n";
        }
        print_header_links($status);
+
+       if (defined $site_html_head_string) {
+               print to_utf8($site_html_head_string);
+       }
+
        print "</head>\n" .
              "<body>\n";
 
@@ -4826,8 +4872,97 @@ sub git_difftree_body {
        print "</table>\n";
 }
 
+sub print_sidebyside_diff_chunk {
+       my @chunk = @_;
+       my (@ctx, @rem, @add);
+
+       return unless @chunk;
+
+       # incomplete last line might be among removed or added lines,
+       # or both, or among context lines: find which
+       for (my $i = 1; $i < @chunk; $i++) {
+               if ($chunk[$i][0] eq 'incomplete') {
+                       $chunk[$i][0] = $chunk[$i-1][0];
+               }
+       }
+
+       # guardian
+       push @chunk, ["", ""];
+
+       foreach my $line_info (@chunk) {
+               my ($class, $line) = @$line_info;
+
+               # print chunk headers
+               if ($class && $class eq 'chunk_header') {
+                       print $line;
+                       next;
+               }
+
+               ## print from accumulator when type of class of lines change
+               # empty contents block on start rem/add block, or end of chunk
+               if (@ctx && (!$class || $class eq 'rem' || $class eq 'add')) {
+                       print join '',
+                               '<div class="chunk_block ctx">',
+                                       '<div class="old">',
+                                       @ctx,
+                                       '</div>',
+                                       '<div class="new">',
+                                       @ctx,
+                                       '</div>',
+                               '</div>';
+                       @ctx = ();
+               }
+               # empty add/rem block on start context block, or end of chunk
+               if ((@rem || @add) && (!$class || $class eq 'ctx')) {
+                       if (!@add) {
+                               # pure removal
+                               print join '',
+                                       '<div class="chunk_block rem">',
+                                               '<div class="old">',
+                                               @rem,
+                                               '</div>',
+                                       '</div>';
+                       } elsif (!@rem) {
+                               # pure addition
+                               print join '',
+                                       '<div class="chunk_block add">',
+                                               '<div class="new">',
+                                               @add,
+                                               '</div>',
+                                       '</div>';
+                       } else {
+                               # assume that it is change
+                               print join '',
+                                       '<div class="chunk_block chg">',
+                                               '<div class="old">',
+                                               @rem,
+                                               '</div>',
+                                               '<div class="new">',
+                                               @add,
+                                               '</div>',
+                                       '</div>';
+                       }
+                       @rem = @add = ();
+               }
+
+               ## adding lines to accumulator
+               # guardian value
+               last unless $line;
+               # rem, add or change
+               if ($class eq 'rem') {
+                       push @rem, $line;
+               } elsif ($class eq 'add') {
+                       push @add, $line;
+               }
+               # context line
+               if ($class eq 'ctx') {
+                       push @ctx, $line;
+               }
+       }
+}
+
 sub git_patchset_body {
-       my ($fd, $difftree, $hash, @hash_parents) = @_;
+       my ($fd, $diff_style, $difftree, $hash, @hash_parents) = @_;
        my ($hash_parent) = $hash_parents[0];
 
        my $is_combined = (@hash_parents > 1);
@@ -4837,6 +4972,7 @@ sub git_patchset_body {
        my $diffinfo;
        my $to_name;
        my (%from, %to);
+       my @chunk; # for side-by-side diff
 
        print "<div class=\"patchset\">\n";
 
@@ -4943,10 +5079,29 @@ sub git_patchset_body {
 
                        next PATCH if ($patch_line =~ m/^diff /);
 
-                       print format_diff_line($patch_line, \%from, \%to);
+                       my ($class, $line) = process_diff_line($patch_line, \%from, \%to);
+                       my $diff_classes = "diff";
+                       $diff_classes .= " $class" if ($class);
+                       $line = "<div class=\"$diff_classes\">$line</div>\n";
+
+                       if ($diff_style eq 'sidebyside' && !$is_combined) {
+                               if ($class eq 'chunk_header') {
+                                       print_sidebyside_diff_chunk(@chunk);
+                                       @chunk = ( [ $class, $line ] );
+                               } else {
+                                       push @chunk, [ $class, $line ];
+                               }
+                       } else {
+                               # default 'inline' style and unknown styles
+                               print $line;
+                       }
                }
 
        } continue {
+               if (@chunk) {
+                       print_sidebyside_diff_chunk(@chunk);
+                       @chunk = ();
+               }
                print "</div>\n"; # class="patch"
        }
 
@@ -5136,9 +5291,9 @@ sub git_project_list_body {
 
        my $check_forks = gitweb_check_feature('forks');
        my $show_ctags  = gitweb_check_feature('ctags');
-       my $tagfilter = $show_ctags ? $cgi->param('by_tag') : undef;
+       my $tagfilter = $show_ctags ? $input_params{'ctag'} : undef;
        $check_forks = undef
-               if ($tagfilter || $searchtext);
+               if ($tagfilter || $search_regexp);
 
        # filtering out forks before filling info allows to do less work
        @projects = filter_forks_from_projects_list(\@projects)
@@ -5146,9 +5301,9 @@ sub git_project_list_body {
        @projects = fill_project_list_info(\@projects);
        # searching projects require filling to be run before it
        @projects = search_projects_list(\@projects,
-                                        'searchtext' => $searchtext,
+                                        'search_regexp' => $search_regexp,
                                         'tagfilter'  => $tagfilter)
-               if ($tagfilter || $searchtext);
+               if ($tagfilter || $search_regexp);
 
        $order ||= $default_projects_order;
        $from = 0 unless defined $from;
@@ -5424,7 +5579,7 @@ sub git_tags_body {
 
 sub git_heads_body {
        # uses global variable $project
-       my ($headlist, $head, $from, $to, $extra) = @_;
+       my ($headlist, $head_at, $from, $to, $extra) = @_;
        $from = 0 unless defined $from;
        $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
 
@@ -5433,7 +5588,7 @@ sub git_heads_body {
        for (my $i = $from; $i <= $to; $i++) {
                my $entry = $headlist->[$i];
                my %ref = %$entry;
-               my $curr = $ref{'id'} eq $head;
+               my $curr = defined $head_at && $ref{'id'} eq $head_at;
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
                } else {
@@ -5692,7 +5847,7 @@ sub git_search_files {
        my %co = @_;
 
        local $/ = "\n";
-       open my $fd, "-|", git_cmd(), 'grep', '-n',
+       open my $fd, "-|", git_cmd(), 'grep', '-n', '-z',
                $search_use_regexp ? ('-E', '-i') : '-F',
                $searchtext, $co{'tree'}
                        or die_error(500, "Open git-grep failed");
@@ -5706,6 +5861,7 @@ sub git_search_files {
        my $alternate = 1;
        my $matches = 0;
        my $lastfile = '';
+       my $file_href;
        while (my $line = <$fd>) {
                chomp $line;
                my ($file, $lno, $ltext, $binary);
@@ -5714,7 +5870,8 @@ sub git_search_files {
                        $file = $1;
                        $binary = 1;
                } else {
-                       (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
+                       ($file, $lno, $ltext) = split(/\0/, $line, 3);
+                       $file =~ s/^$co{'tree'}://;
                }
                if ($file ne $lastfile) {
                        $lastfile and print "</td></tr>\n";
@@ -5723,10 +5880,10 @@ sub git_search_files {
                        } else {
                                print "<tr class=\"light\">\n";
                        }
+                       $file_href = href(action=>"blob", hash_base=>$co{'id'},
+                                         file_name=>$file);
                        print "<td class=\"list\">".
-                               $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
-                                                      file_name=>"$file"),
-                                       -class => "list"}, esc_path($file));
+                               $cgi->a({-href => $file_href, -class => "list"}, esc_path($file));
                        print "</td><td>\n";
                        $lastfile = $file;
                }
@@ -5744,10 +5901,9 @@ sub git_search_files {
                                $ltext = esc_html($ltext, -nbsp=>1);
                        }
                        print "<div class=\"pre\">" .
-                               $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
-                                                      file_name=>"$file").'#l'.$lno,
-                                       -class => "linenr"}, sprintf('%4i', $lno))
-                               . ' ' .  $ltext . "</div>\n";
+                               $cgi->a({-href => $file_href.'#l'.$lno,
+                                       -class => "linenr"}, sprintf('%4i', $lno)) .
+                               ' ' .  $ltext . "</div>\n";
                }
        }
        if ($lastfile) {
@@ -5848,7 +6004,7 @@ sub git_project_list {
        }
        print $cgi->startform(-method => "get") .
              "<p class=\"projsearch\">Search:\n" .
-             $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
+             $cgi->textfield(-name => "s", -value => $searchtext, -override => 1) . "\n" .
              "</p>" .
              $cgi->end_form() . "\n";
        git_project_list_body(\@list, $order);
@@ -6051,7 +6207,7 @@ sub git_tag {
 
 sub git_blame_common {
        my $format = shift || 'porcelain';
-       if ($format eq 'porcelain' && $cgi->param('js')) {
+       if ($format eq 'porcelain' && $input_params{'javascript'}) {
                $format = 'incremental';
                $action = 'blame_incremental'; # for page title etc
        }
@@ -6100,7 +6256,9 @@ sub git_blame_common {
                        -type=>"text/plain", -charset => "utf-8",
                        -status=> "200 OK");
                local $| = 1; # output autoflush
-               print while <$fd>;
+               while (my $line = <$fd>) {
+                       print to_utf8($line);
+               }
                close $fd
                        or print "ERROR $!\n";
 
@@ -6942,6 +7100,7 @@ sub git_object {
 
 sub git_blobdiff {
        my $format = shift || 'html';
+       my $diff_style = $input_params{'diff_style'} || 'inline';
 
        my $fd;
        my @difftree;
@@ -7020,6 +7179,7 @@ sub git_blobdiff {
                my $formats_nav =
                        $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
                                "raw");
+               $formats_nav .= diff_style_nav($diff_style);
                git_header_html(undef, $expires);
                if (defined $hash_base && (my %co = parse_commit($hash_base))) {
                        git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
@@ -7051,7 +7211,8 @@ sub git_blobdiff {
        if ($format eq 'html') {
                print "<div class=\"page_body\">\n";
 
-               git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base);
+               git_patchset_body($fd, $diff_style,
+                                 [ \%diffinfo ], $hash_base, $hash_parent_base);
                close $fd;
 
                print "</div>\n"; # class="page_body"
@@ -7076,9 +7237,31 @@ sub git_blobdiff_plain {
        git_blobdiff('plain');
 }
 
+# assumes that it is added as later part of already existing navigation,
+# so it returns "| foo | bar" rather than just "foo | bar"
+sub diff_style_nav {
+       my ($diff_style, $is_combined) = @_;
+       $diff_style ||= 'inline';
+
+       return "" if ($is_combined);
+
+       my @styles = (inline => 'inline', 'sidebyside' => 'side by side');
+       my %styles = @styles;
+       @styles =
+               @styles[ map { $_ * 2 } 0..$#styles/2 ];
+
+       return join '',
+               map { " | ".$_ }
+               map {
+                       $_ eq $diff_style ? $styles{$_} :
+                       $cgi->a({-href => href(-replay=>1, diff_style => $_)}, $styles{$_})
+               } @styles;
+}
+
 sub git_commitdiff {
        my %params = @_;
        my $format = $params{-format} || 'html';
+       my $diff_style = $input_params{'diff_style'} || 'inline';
 
        my ($patch_max) = gitweb_get_feature('patches');
        if ($format eq 'patch') {
@@ -7104,6 +7287,7 @@ sub git_commitdiff {
                                $cgi->a({-href => href(action=>"patch", -replay=>1)},
                                        "patch");
                }
+               $formats_nav .= diff_style_nav($diff_style, @{$co{'parents'}} > 1);
 
                if (defined $hash_parent &&
                    $hash_parent ne '-c' && $hash_parent ne '--cc') {
@@ -7121,8 +7305,8 @@ sub git_commitdiff {
                                }
                        }
                        $formats_nav .= ': ' .
-                               $cgi->a({-href => href(action=>"commitdiff",
-                                                      hash=>$hash_parent)},
+                               $cgi->a({-href => href(-replay=>1,
+                                                      hash=>$hash_parent, hash_base=>undef)},
                                        esc_html($hash_parent_short)) .
                                ')';
                } elsif (!$co{'parent'}) {
@@ -7132,28 +7316,28 @@ sub git_commitdiff {
                        # single parent commit
                        $formats_nav .=
                                ' (parent: ' .
-                               $cgi->a({-href => href(action=>"commitdiff",
-                                                      hash=>$co{'parent'})},
+                               $cgi->a({-href => href(-replay=>1,
+                                                      hash=>$co{'parent'}, hash_base=>undef)},
                                        esc_html(substr($co{'parent'}, 0, 7))) .
                                ')';
                } else {
                        # merge commit
                        if ($hash_parent eq '--cc') {
                                $formats_nav .= ' | ' .
-                                       $cgi->a({-href => href(action=>"commitdiff",
+                                       $cgi->a({-href => href(-replay=>1,
                                                               hash=>$hash, hash_parent=>'-c')},
                                                'combined');
                        } else { # $hash_parent eq '-c'
                                $formats_nav .= ' | ' .
-                                       $cgi->a({-href => href(action=>"commitdiff",
+                                       $cgi->a({-href => href(-replay=>1,
                                                               hash=>$hash, hash_parent=>'--cc')},
                                                'compact');
                        }
                        $formats_nav .=
                                ' (merge: ' .
                                join(' ', map {
-                                       $cgi->a({-href => href(action=>"commitdiff",
-                                                              hash=>$_)},
+                                       $cgi->a({-href => href(-replay=>1,
+                                                              hash=>$_, hash_base=>undef)},
                                                esc_html(substr($_, 0, 7)));
                                } @{$co{'parents'}} ) .
                                ')';
@@ -7282,7 +7466,8 @@ sub git_commitdiff {
                                  $use_parents ? @{$co{'parents'}} : $hash_parent);
                print "<br/>\n";
 
-               git_patchset_body($fd, \@difftree, $hash,
+               git_patchset_body($fd, $diff_style,
+                                 \@difftree, $hash,
                                  $use_parents ? @{$co{'parents'}} : $hash_parent);
                close $fd;
                print "</div>\n"; # class="page_body"
@@ -7692,11 +7877,12 @@ sub git_opml {
                -charset => 'utf-8',
                -content_disposition => 'inline; filename="opml.xml"');
 
+       my $title = esc_html($site_name);
        print <<XML;
 <?xml version="1.0" encoding="utf-8"?>
 <opml version="1.0">
 <head>
-  <title>$site_name OPML Export</title>
+  <title>$title OPML Export</title>
 </head>
 <body>
 <outline text="git RSS feeds">
index 7d88509208417e4b1222629002ea339ecc32526e..c7827e8f1d2f9239bca42d80d2efbaa025300bbb 100644 (file)
@@ -475,6 +475,36 @@ div.diff.nodifferences {
        color: #600000;
 }
 
+/* side-by-side diff */
+div.chunk_block {
+       overflow: hidden;
+}
+
+div.chunk_block div.old {
+       float: left;
+       width: 50%;
+       overflow: hidden;
+}
+
+div.chunk_block div.new {
+       margin-left: 50%;
+       width: 50%;
+}
+
+div.chunk_block.rem div.old div.diff.rem {
+       background-color: #fff5f5;
+}
+div.chunk_block.add div.new div.diff.add {
+       background-color: #f8fff8;
+}
+div.chunk_block.chg div     div.diff {
+       background-color: #fffff0;
+}
+div.chunk_block.ctx div     div.diff.ctx {
+       color: #404040;
+}
+
+
 div.index_include {
        border: solid #d9d8d1;
        border-width: 0px 0px 1px;
diff --git a/gpg-interface.c b/gpg-interface.c
new file mode 100644 (file)
index 0000000..09ab64a
--- /dev/null
@@ -0,0 +1,140 @@
+#include "cache.h"
+#include "run-command.h"
+#include "strbuf.h"
+#include "gpg-interface.h"
+#include "sigchain.h"
+
+static char *configured_signing_key;
+static const char *gpg_program = "gpg";
+
+void set_signing_key(const char *key)
+{
+       free(configured_signing_key);
+       configured_signing_key = xstrdup(key);
+}
+
+int git_gpg_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "user.signingkey")) {
+               set_signing_key(value);
+       }
+       if (!strcmp(var, "gpg.program")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               gpg_program = xstrdup(value);
+       }
+       return 0;
+}
+
+const char *get_signing_key(void)
+{
+       if (configured_signing_key)
+               return configured_signing_key;
+       return git_committer_info(IDENT_ERROR_ON_NO_NAME|IDENT_NO_DATE);
+}
+
+/*
+ * Create a detached signature for the contents of "buffer" and append
+ * it after "signature"; "buffer" and "signature" can be the same
+ * strbuf instance, which would cause the detached signature appended
+ * at the end.
+ */
+int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key)
+{
+       struct child_process gpg;
+       const char *args[4];
+       ssize_t len;
+       size_t i, j, bottom;
+
+       memset(&gpg, 0, sizeof(gpg));
+       gpg.argv = args;
+       gpg.in = -1;
+       gpg.out = -1;
+       args[0] = gpg_program;
+       args[1] = "-bsau";
+       args[2] = signing_key;
+       args[3] = NULL;
+
+       if (start_command(&gpg))
+               return error(_("could not run gpg."));
+
+       /*
+        * When the username signingkey is bad, program could be terminated
+        * because gpg exits without reading and then write gets SIGPIPE.
+        */
+       sigchain_push(SIGPIPE, SIG_IGN);
+
+       if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
+               close(gpg.in);
+               close(gpg.out);
+               finish_command(&gpg);
+               return error(_("gpg did not accept the data"));
+       }
+       close(gpg.in);
+
+       bottom = signature->len;
+       len = strbuf_read(signature, gpg.out, 1024);
+       close(gpg.out);
+
+       sigchain_pop(SIGPIPE);
+
+       if (finish_command(&gpg) || !len || len < 0)
+               return error(_("gpg failed to sign the data"));
+
+       /* Strip CR from the line endings, in case we are on Windows. */
+       for (i = j = bottom; i < signature->len; i++)
+               if (signature->buf[i] != '\r') {
+                       if (i != j)
+                               signature->buf[j] = signature->buf[i];
+                       j++;
+               }
+       strbuf_setlen(signature, j);
+
+       return 0;
+}
+
+/*
+ * Run "gpg" to see if the payload matches the detached signature.
+ * gpg_output, when set, receives the diagnostic output from GPG.
+ */
+int verify_signed_buffer(const char *payload, size_t payload_size,
+                        const char *signature, size_t signature_size,
+                        struct strbuf *gpg_output)
+{
+       struct child_process gpg;
+       const char *args_gpg[] = {NULL, "--verify", "FILE", "-", NULL};
+       char path[PATH_MAX];
+       int fd, ret;
+
+       args_gpg[0] = gpg_program;
+       fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
+       if (fd < 0)
+               return error("could not create temporary file '%s': %s",
+                            path, strerror(errno));
+       if (write_in_full(fd, signature, signature_size) < 0)
+               return error("failed writing detached signature to '%s': %s",
+                            path, strerror(errno));
+       close(fd);
+
+       memset(&gpg, 0, sizeof(gpg));
+       gpg.argv = args_gpg;
+       gpg.in = -1;
+       if (gpg_output)
+               gpg.err = -1;
+       args_gpg[2] = path;
+       if (start_command(&gpg)) {
+               unlink(path);
+               return error("could not run gpg.");
+       }
+
+       write_in_full(gpg.in, payload, payload_size);
+       close(gpg.in);
+
+       if (gpg_output)
+               strbuf_read(gpg_output, gpg.err, 0);
+       ret = finish_command(&gpg);
+
+       unlink_or_warn(path);
+
+       return ret;
+}
diff --git a/gpg-interface.h b/gpg-interface.h
new file mode 100644 (file)
index 0000000..b9c3608
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef GPG_INTERFACE_H
+#define GPG_INTERFACE_H
+
+extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
+extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output);
+extern int git_gpg_config(const char *, const char *, void *);
+extern void set_signing_key(const char *);
+extern const char *get_signing_key(void);
+
+#endif
diff --git a/grep.c b/grep.c
index b29d09c7f6a5a7f9621fb5287eb07e72ece7f484..f492d267cc157d46f899c70d01ea707d737d27ae 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -79,7 +79,7 @@ static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt)
 {
        const char *error;
        int erroffset;
-       int options = 0;
+       int options = PCRE_MULTILINE;
 
        if (opt->ignore_case)
                options |= PCRE_CASELESS;
@@ -806,10 +806,51 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
        opt->output(opt, "\n", 1);
 }
 
-static int match_funcname(struct grep_opt *opt, char *bol, char *eol)
+#ifndef NO_PTHREADS
+int grep_use_locks;
+
+/*
+ * This lock protects access to the gitattributes machinery, which is
+ * not thread-safe.
+ */
+pthread_mutex_t grep_attr_mutex;
+
+static inline void grep_attr_lock(void)
+{
+       if (grep_use_locks)
+               pthread_mutex_lock(&grep_attr_mutex);
+}
+
+static inline void grep_attr_unlock(void)
+{
+       if (grep_use_locks)
+               pthread_mutex_unlock(&grep_attr_mutex);
+}
+
+/*
+ * Same as git_attr_mutex, but protecting the thread-unsafe object db access.
+ */
+pthread_mutex_t grep_read_mutex;
+
+#else
+#define grep_attr_lock()
+#define grep_attr_unlock()
+#endif
+
+static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bol, char *eol)
 {
        xdemitconf_t *xecfg = opt->priv;
-       if (xecfg && xecfg->find_func) {
+       if (xecfg && !xecfg->find_func) {
+               grep_source_load_driver(gs);
+               if (gs->driver->funcname.pattern) {
+                       const struct userdiff_funcname *pe = &gs->driver->funcname;
+                       xdiff_set_find_func(xecfg, pe->pattern, pe->cflags);
+               } else {
+                       xecfg = opt->priv = NULL;
+               }
+       }
+
+       if (xecfg) {
                char buf[1];
                return xecfg->find_func(bol, eol - bol, buf, 1,
                                        xecfg->find_func_priv) >= 0;
@@ -822,33 +863,33 @@ static int match_funcname(struct grep_opt *opt, char *bol, char *eol)
        return 0;
 }
 
-static void show_funcname_line(struct grep_opt *opt, const char *name,
-                              char *buf, char *bol, unsigned lno)
+static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
+                              char *bol, unsigned lno)
 {
-       while (bol > buf) {
+       while (bol > gs->buf) {
                char *eol = --bol;
 
-               while (bol > buf && bol[-1] != '\n')
+               while (bol > gs->buf && bol[-1] != '\n')
                        bol--;
                lno--;
 
                if (lno <= opt->last_shown)
                        break;
 
-               if (match_funcname(opt, bol, eol)) {
-                       show_line(opt, bol, eol, name, lno, '=');
+               if (match_funcname(opt, gs, bol, eol)) {
+                       show_line(opt, bol, eol, gs->name, lno, '=');
                        break;
                }
        }
 }
 
-static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
+static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
                             char *bol, char *end, unsigned lno)
 {
        unsigned cur = lno, from = 1, funcname_lno = 0;
        int funcname_needed = !!opt->funcname;
 
-       if (opt->funcbody && !match_funcname(opt, bol, end))
+       if (opt->funcbody && !match_funcname(opt, gs, bol, end))
                funcname_needed = 2;
 
        if (opt->pre_context < lno)
@@ -857,14 +898,14 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
                from = opt->last_shown + 1;
 
        /* Rewind. */
-       while (bol > buf &&
+       while (bol > gs->buf &&
               cur > (funcname_needed == 2 ? opt->last_shown + 1 : from)) {
                char *eol = --bol;
 
-               while (bol > buf && bol[-1] != '\n')
+               while (bol > gs->buf && bol[-1] != '\n')
                        bol--;
                cur--;
-               if (funcname_needed && match_funcname(opt, bol, eol)) {
+               if (funcname_needed && match_funcname(opt, gs, bol, eol)) {
                        funcname_lno = cur;
                        funcname_needed = 0;
                }
@@ -872,7 +913,7 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
 
        /* We need to look even further back to find a function signature. */
        if (opt->funcname && funcname_needed)
-               show_funcname_line(opt, name, buf, bol, cur);
+               show_funcname_line(opt, gs, bol, cur);
 
        /* Back forward. */
        while (cur < lno) {
@@ -880,7 +921,7 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
 
                while (*eol != '\n')
                        eol++;
-               show_line(opt, bol, eol, name, cur, sign);
+               show_line(opt, bol, eol, gs->name, cur, sign);
                bol = eol + 1;
                cur++;
        }
@@ -942,29 +983,15 @@ static int look_ahead(struct grep_opt *opt,
        return 0;
 }
 
-int grep_threads_ok(const struct grep_opt *opt)
-{
-       /* If this condition is true, then we may use the attribute
-        * machinery in grep_buffer_1. The attribute code is not
-        * thread safe, so we disable the use of threads.
-        */
-       if (opt->funcname && !opt->unmatch_name_only && !opt->status_only &&
-           !opt->name_only)
-               return 0;
-
-       return 1;
-}
-
 static void std_output(struct grep_opt *opt, const void *buf, size_t size)
 {
        fwrite(buf, size, 1, stdout);
 }
 
-static int grep_buffer_1(struct grep_opt *opt, const char *name,
-                        char *buf, unsigned long size, int collect_hits)
+static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int collect_hits)
 {
-       char *bol = buf;
-       unsigned long left = size;
+       char *bol;
+       unsigned long left;
        unsigned lno = 1;
        unsigned last_hit = 0;
        int binary_match_only = 0;
@@ -994,11 +1021,11 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
 
        switch (opt->binary) {
        case GREP_BINARY_DEFAULT:
-               if (buffer_is_binary(buf, size))
+               if (grep_source_is_binary(gs))
                        binary_match_only = 1;
                break;
        case GREP_BINARY_NOMATCH:
-               if (buffer_is_binary(buf, size))
+               if (grep_source_is_binary(gs))
                        return 0; /* Assume unmatch */
                break;
        case GREP_BINARY_TEXT:
@@ -1008,17 +1035,15 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
        }
 
        memset(&xecfg, 0, sizeof(xecfg));
-       if (opt->funcname && !opt->unmatch_name_only && !opt->status_only &&
-           !opt->name_only && !binary_match_only && !collect_hits) {
-               struct userdiff_driver *drv = userdiff_find_by_path(name);
-               if (drv && drv->funcname.pattern) {
-                       const struct userdiff_funcname *pe = &drv->funcname;
-                       xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
-                       opt->priv = &xecfg;
-               }
-       }
+       opt->priv = &xecfg;
+
        try_lookahead = should_lookahead(opt);
 
+       if (grep_source_load(gs) < 0)
+               return 0;
+
+       bol = gs->buf;
+       left = gs->size;
        while (left) {
                char *eol, ch;
                int hit;
@@ -1067,14 +1092,14 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                        if (opt->status_only)
                                return 1;
                        if (opt->name_only) {
-                               show_name(opt, name);
+                               show_name(opt, gs->name);
                                return 1;
                        }
                        if (opt->count)
                                goto next_line;
                        if (binary_match_only) {
                                opt->output(opt, "Binary file ", 12);
-                               output_color(opt, name, strlen(name),
+                               output_color(opt, gs->name, strlen(gs->name),
                                             opt->color_filename);
                                opt->output(opt, " matches\n", 9);
                                return 1;
@@ -1083,23 +1108,23 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                         * pre-context lines, we would need to show them.
                         */
                        if (opt->pre_context || opt->funcbody)
-                               show_pre_context(opt, name, buf, bol, eol, lno);
+                               show_pre_context(opt, gs, bol, eol, lno);
                        else if (opt->funcname)
-                               show_funcname_line(opt, name, buf, bol, lno);
-                       show_line(opt, bol, eol, name, lno, ':');
+                               show_funcname_line(opt, gs, bol, lno);
+                       show_line(opt, bol, eol, gs->name, lno, ':');
                        last_hit = lno;
                        if (opt->funcbody)
                                show_function = 1;
                        goto next_line;
                }
-               if (show_function && match_funcname(opt, bol, eol))
+               if (show_function && match_funcname(opt, gs, bol, eol))
                        show_function = 0;
                if (show_function ||
                    (last_hit && lno <= last_hit + opt->post_context)) {
                        /* If the last hit is within the post context,
                         * we need to show this line.
                         */
-                       show_line(opt, bol, eol, name, lno, '-');
+                       show_line(opt, bol, eol, gs->name, lno, '-');
                }
 
        next_line:
@@ -1117,7 +1142,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                return 0;
        if (opt->unmatch_name_only) {
                /* We did not see any hit, so we want to show this */
-               show_name(opt, name);
+               show_name(opt, gs->name);
                return 1;
        }
 
@@ -1131,7 +1156,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
         */
        if (opt->count && count) {
                char buf[32];
-               output_color(opt, name, strlen(name), opt->color_filename);
+               output_color(opt, gs->name, strlen(gs->name), opt->color_filename);
                output_sep(opt, ':');
                snprintf(buf, sizeof(buf), "%u\n", count);
                opt->output(opt, buf, strlen(buf));
@@ -1166,23 +1191,174 @@ static int chk_hit_marker(struct grep_expr *x)
        }
 }
 
-int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size)
+int grep_source(struct grep_opt *opt, struct grep_source *gs)
 {
        /*
         * we do not have to do the two-pass grep when we do not check
         * buffer-wide "all-match".
         */
        if (!opt->all_match)
-               return grep_buffer_1(opt, name, buf, size, 0);
+               return grep_source_1(opt, gs, 0);
 
        /* Otherwise the toplevel "or" terms hit a bit differently.
         * We first clear hit markers from them.
         */
        clr_hit_marker(opt->pattern_expression);
-       grep_buffer_1(opt, name, buf, size, 1);
+       grep_source_1(opt, gs, 1);
 
        if (!chk_hit_marker(opt->pattern_expression))
                return 0;
 
-       return grep_buffer_1(opt, name, buf, size, 0);
+       return grep_source_1(opt, gs, 0);
+}
+
+int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size)
+{
+       struct grep_source gs;
+       int r;
+
+       grep_source_init(&gs, GREP_SOURCE_BUF, NULL, NULL);
+       gs.buf = buf;
+       gs.size = size;
+
+       r = grep_source(opt, &gs);
+
+       grep_source_clear(&gs);
+       return r;
+}
+
+void grep_source_init(struct grep_source *gs, enum grep_source_type type,
+                     const char *name, const void *identifier)
+{
+       gs->type = type;
+       gs->name = name ? xstrdup(name) : NULL;
+       gs->buf = NULL;
+       gs->size = 0;
+       gs->driver = NULL;
+
+       switch (type) {
+       case GREP_SOURCE_FILE:
+               gs->identifier = xstrdup(identifier);
+               break;
+       case GREP_SOURCE_SHA1:
+               gs->identifier = xmalloc(20);
+               memcpy(gs->identifier, identifier, 20);
+               break;
+       case GREP_SOURCE_BUF:
+               gs->identifier = NULL;
+       }
+}
+
+void grep_source_clear(struct grep_source *gs)
+{
+       free(gs->name);
+       gs->name = NULL;
+       free(gs->identifier);
+       gs->identifier = NULL;
+       grep_source_clear_data(gs);
+}
+
+void grep_source_clear_data(struct grep_source *gs)
+{
+       switch (gs->type) {
+       case GREP_SOURCE_FILE:
+       case GREP_SOURCE_SHA1:
+               free(gs->buf);
+               gs->buf = NULL;
+               gs->size = 0;
+               break;
+       case GREP_SOURCE_BUF:
+               /* leave user-provided buf intact */
+               break;
+       }
+}
+
+static int grep_source_load_sha1(struct grep_source *gs)
+{
+       enum object_type type;
+
+       grep_read_lock();
+       gs->buf = read_sha1_file(gs->identifier, &type, &gs->size);
+       grep_read_unlock();
+
+       if (!gs->buf)
+               return error(_("'%s': unable to read %s"),
+                            gs->name,
+                            sha1_to_hex(gs->identifier));
+       return 0;
+}
+
+static int grep_source_load_file(struct grep_source *gs)
+{
+       const char *filename = gs->identifier;
+       struct stat st;
+       char *data;
+       size_t size;
+       int i;
+
+       if (lstat(filename, &st) < 0) {
+       err_ret:
+               if (errno != ENOENT)
+                       error(_("'%s': %s"), filename, strerror(errno));
+               return -1;
+       }
+       if (!S_ISREG(st.st_mode))
+               return -1;
+       size = xsize_t(st.st_size);
+       i = open(filename, O_RDONLY);
+       if (i < 0)
+               goto err_ret;
+       data = xmalloc(size + 1);
+       if (st.st_size != read_in_full(i, data, size)) {
+               error(_("'%s': short read %s"), filename, strerror(errno));
+               close(i);
+               free(data);
+               return -1;
+       }
+       close(i);
+       data[size] = 0;
+
+       gs->buf = data;
+       gs->size = size;
+       return 0;
+}
+
+int grep_source_load(struct grep_source *gs)
+{
+       if (gs->buf)
+               return 0;
+
+       switch (gs->type) {
+       case GREP_SOURCE_FILE:
+               return grep_source_load_file(gs);
+       case GREP_SOURCE_SHA1:
+               return grep_source_load_sha1(gs);
+       case GREP_SOURCE_BUF:
+               return gs->buf ? 0 : -1;
+       }
+       die("BUG: invalid grep_source type");
+}
+
+void grep_source_load_driver(struct grep_source *gs)
+{
+       if (gs->driver)
+               return;
+
+       grep_attr_lock();
+       gs->driver = userdiff_find_by_path(gs->name);
+       if (!gs->driver)
+               gs->driver = userdiff_find_by_name("default");
+       grep_attr_unlock();
+}
+
+int grep_source_is_binary(struct grep_source *gs)
+{
+       grep_source_load_driver(gs);
+       if (gs->driver->binary != -1)
+               return gs->driver->binary;
+
+       if (!grep_source_load(gs))
+               return buffer_is_binary(gs->buf, gs->size);
+
+       return 0;
 }
diff --git a/grep.h b/grep.h
index a65280026d5dee8ab059bead79f05d6a1111147a..36e49d8255e2952828f367a0e35d4fcb027d9772 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -8,6 +8,8 @@ typedef int pcre;
 typedef int pcre_extra;
 #endif
 #include "kwset.h"
+#include "thread-utils.h"
+#include "userdiff.h"
 
 enum grep_pat_token {
        GREP_PATTERN,
@@ -126,9 +128,61 @@ extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const cha
 extern void append_header_grep_pattern(struct grep_opt *, enum grep_header_field, const char *);
 extern void compile_grep_patterns(struct grep_opt *opt);
 extern void free_grep_patterns(struct grep_opt *opt);
-extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);
+extern int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size);
+
+struct grep_source {
+       char *name;
+
+       enum grep_source_type {
+               GREP_SOURCE_SHA1,
+               GREP_SOURCE_FILE,
+               GREP_SOURCE_BUF,
+       } type;
+       void *identifier;
+
+       char *buf;
+       unsigned long size;
+
+       struct userdiff_driver *driver;
+};
+
+void grep_source_init(struct grep_source *gs, enum grep_source_type type,
+                     const char *name, const void *identifier);
+int grep_source_load(struct grep_source *gs);
+void grep_source_clear_data(struct grep_source *gs);
+void grep_source_clear(struct grep_source *gs);
+void grep_source_load_driver(struct grep_source *gs);
+int grep_source_is_binary(struct grep_source *gs);
+
+int grep_source(struct grep_opt *opt, struct grep_source *gs);
 
 extern struct grep_opt *grep_opt_dup(const struct grep_opt *opt);
 extern int grep_threads_ok(const struct grep_opt *opt);
 
+#ifndef NO_PTHREADS
+/*
+ * Mutex used around access to the attributes machinery if
+ * opt->use_threads.  Must be initialized/destroyed by callers!
+ */
+extern int grep_use_locks;
+extern pthread_mutex_t grep_attr_mutex;
+extern pthread_mutex_t grep_read_mutex;
+
+static inline void grep_read_lock(void)
+{
+       if (grep_use_locks)
+               pthread_mutex_lock(&grep_read_mutex);
+}
+
+static inline void grep_read_unlock(void)
+{
+       if (grep_use_locks)
+               pthread_mutex_unlock(&grep_read_mutex);
+}
+
+#else
+#define grep_read_lock()
+#define grep_read_unlock()
+#endif
+
 #endif
diff --git a/help.c b/help.c
index cbbe966f685b276cac702bb0fd9a44bfbf5f0e79..14eefc91ced3890975d0833ffc83971c7986858b 100644 (file)
--- a/help.c
+++ b/help.c
@@ -5,28 +5,6 @@
 #include "help.h"
 #include "common-cmds.h"
 
-/* most GUI terminals set COLUMNS (although some don't export it) */
-static int term_columns(void)
-{
-       char *col_string = getenv("COLUMNS");
-       int n_cols;
-
-       if (col_string && (n_cols = atoi(col_string)) > 0)
-               return n_cols;
-
-#ifdef TIOCGWINSZ
-       {
-               struct winsize ws;
-               if (!ioctl(1, TIOCGWINSZ, &ws)) {
-                       if (ws.ws_col)
-                               return ws.ws_col;
-               }
-       }
-#endif
-
-       return 80;
-}
-
 void add_cmdname(struct cmdnames *cmds, const char *name, int len)
 {
        struct cmdname *ent = xmalloc(sizeof(*ent) + len + 1);
diff --git a/hex.c b/hex.c
index bb402fbaa2a04ef0849789fdd814dcbb8773fff5..9ebc050637532765b775cbd8e3b92951a67ea333 100644 (file)
--- a/hex.c
+++ b/hex.c
@@ -39,7 +39,15 @@ int get_sha1_hex(const char *hex, unsigned char *sha1)
 {
        int i;
        for (i = 0; i < 20; i++) {
-               unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
+               unsigned int val;
+               /*
+                * hex[1]=='\0' is caught when val is checked below,
+                * but if hex[0] is NUL we have to avoid reading
+                * past the end of the string:
+                */
+               if (!hex[0])
+                       return -1;
+               val = (hexval(hex[0]) << 4) | hexval(hex[1]);
                if (val & ~0xff)
                        return -1;
                *sha1++ = val;
index 59ad7da605f711af6970d6832db32efd62b6ea97..869d515383b5fc9c562ea7987b1cd485cce12032 100644 (file)
@@ -545,6 +545,8 @@ int main(int argc, char **argv)
        char *cmd_arg = NULL;
        int i;
 
+       git_setup_gettext();
+
        git_extract_argv0_path(argv[0]);
        set_die_routine(die_webcgi);
 
index 8c4c5d2224a2493a648e6a34257bc150f2712dd0..ba3ea106708de01fc933e6743ab109bef2697f75 100644 (file)
@@ -22,6 +22,8 @@ int main(int argc, const char **argv)
        int get_verbosely = 0;
        int get_recover = 0;
 
+       git_setup_gettext();
+
        git_extract_argv0_path(argv[0]);
 
        while (arg < argc && argv[arg][0] == '-') {
@@ -67,7 +69,7 @@ int main(int argc, const char **argv)
 
        git_config(git_default_config, NULL);
 
-       http_init(NULL);
+       http_init(NULL, url, 0);
        walker = get_http_walker(url);
        walker->get_tree = get_tree;
        walker->get_history = get_history;
index 44f814cda2a9756a55cb7f332d5fef0e5484256b..f22f7e43caa3e804c5c8275ba0312d58611d7da3 100644 (file)
@@ -1747,7 +1747,8 @@ int main(int argc, char **argv)
        int i;
        int new_refs;
        struct ref *ref, *local_refs;
-       struct remote *remote;
+
+       git_setup_gettext();
 
        git_extract_argv0_path(argv[0]);
 
@@ -1821,14 +1822,7 @@ int main(int argc, char **argv)
 
        memset(remote_dir_exists, -1, 256);
 
-       /*
-        * Create a minimum remote by hand to give to http_init(),
-        * primarily to allow it to look at the URL.
-        */
-       remote = xcalloc(sizeof(*remote), 1);
-       ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc);
-       remote->url[remote->url_nr++] = repo->url;
-       http_init(remote);
+       http_init(NULL, repo->url, 1);
 
 #ifdef USE_CURL_MULTI
        is_running_queue = 0;
@@ -1877,8 +1871,8 @@ int main(int argc, char **argv)
        }
 
        /* match them up */
-       if (match_refs(local_refs, &remote_refs,
-                      nr_refspec, (const char **) refspec, push_all)) {
+       if (match_push_refs(local_refs, &remote_refs,
+                           nr_refspec, (const char **) refspec, push_all)) {
                rc = -1;
                goto cleanup;
        }
diff --git a/http.c b/http.c
index fb3465f50c95ab9cb3e6fae1d1e594f6ae0b9c42..0ffd79cd81ba3e722dcdbf4c20469fa551ce9d80 100644 (file)
--- a/http.c
+++ b/http.c
@@ -3,8 +3,8 @@
 #include "sideband.h"
 #include "run-command.h"
 #include "url.h"
+#include "credential.h"
 
-int data_received;
 int active_requests;
 int http_is_verbose;
 size_t http_post_buffer = 16 * LARGE_PACKET_MAX;
@@ -42,7 +42,8 @@ static long curl_low_speed_time = -1;
 static int curl_ftp_no_epsv;
 static const char *curl_http_proxy;
 static const char *curl_cookie_file;
-static char *user_name, *user_pass;
+static struct credential http_auth = CREDENTIAL_INIT;
+static int http_proactive_auth;
 static const char *user_agent;
 
 #if LIBCURL_VERSION_NUM >= 0x071700
@@ -53,7 +54,7 @@ static const char *user_agent;
 #define CURLOPT_KEYPASSWD CURLOPT_SSLCERTPASSWD
 #endif
 
-static char *ssl_cert_password;
+static struct credential cert_auth = CREDENTIAL_INIT;
 static int ssl_cert_password_required;
 
 static struct curl_slist *pragma_header;
@@ -99,13 +100,11 @@ size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
        struct strbuf *buffer = buffer_;
 
        strbuf_add(buffer, ptr, size);
-       data_received++;
        return size;
 }
 
 size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf)
 {
-       data_received++;
        return eltsize * nmemb;
 }
 
@@ -211,11 +210,11 @@ static int http_options(const char *var, const char *value, void *cb)
 
 static void init_curl_http_auth(CURL *result)
 {
-       if (user_name) {
+       if (http_auth.username) {
                struct strbuf up = STRBUF_INIT;
-               if (!user_pass)
-                       user_pass = xstrdup(git_getpass("Password: "));
-               strbuf_addf(&up, "%s:%s", user_name, user_pass);
+               credential_fill(&http_auth);
+               strbuf_addf(&up, "%s:%s",
+                           http_auth.username, http_auth.password);
                curl_easy_setopt(result, CURLOPT_USERPWD,
                                 strbuf_detach(&up, NULL));
        }
@@ -223,18 +222,14 @@ static void init_curl_http_auth(CURL *result)
 
 static int has_cert_password(void)
 {
-       if (ssl_cert_password != NULL)
-               return 1;
        if (ssl_cert == NULL || ssl_cert_password_required != 1)
                return 0;
-       /* Only prompt the user once. */
-       ssl_cert_password_required = -1;
-       ssl_cert_password = git_getpass("Certificate Password: ");
-       if (ssl_cert_password != NULL) {
-               ssl_cert_password = xstrdup(ssl_cert_password);
-               return 1;
-       } else
-               return 0;
+       if (!cert_auth.password) {
+               cert_auth.protocol = xstrdup("cert");
+               cert_auth.path = xstrdup(ssl_cert);
+               credential_fill(&cert_auth);
+       }
+       return 1;
 }
 
 static CURL *get_curl_handle(void)
@@ -258,12 +253,13 @@ static CURL *get_curl_handle(void)
        curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
 #endif
 
-       init_curl_http_auth(result);
+       if (http_proactive_auth)
+               init_curl_http_auth(result);
 
        if (ssl_cert != NULL)
                curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
        if (has_cert_password())
-               curl_easy_setopt(result, CURLOPT_KEYPASSWD, ssl_cert_password);
+               curl_easy_setopt(result, CURLOPT_KEYPASSWD, cert_auth.password);
 #if LIBCURL_VERSION_NUM >= 0x070903
        if (ssl_key != NULL)
                curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
@@ -305,55 +301,6 @@ static CURL *get_curl_handle(void)
        return result;
 }
 
-static void http_auth_init(const char *url)
-{
-       char *at, *colon, *cp, *slash, *decoded;
-       int len;
-
-       cp = strstr(url, "://");
-       if (!cp)
-               return;
-
-       /*
-        * Ok, the URL looks like "proto://something".  Which one?
-        * "proto://<user>:<pass>@<host>/...",
-        * "proto://<user>@<host>/...", or just
-        * "proto://<host>/..."?
-        */
-       cp += 3;
-       at = strchr(cp, '@');
-       colon = strchr(cp, ':');
-       slash = strchrnul(cp, '/');
-       if (!at || slash <= at)
-               return; /* No credentials */
-       if (!colon || at <= colon) {
-               /* Only username */
-               len = at - cp;
-               user_name = xmalloc(len + 1);
-               memcpy(user_name, cp, len);
-               user_name[len] = '\0';
-               decoded = url_decode(user_name);
-               free(user_name);
-               user_name = decoded;
-               user_pass = NULL;
-       } else {
-               len = colon - cp;
-               user_name = xmalloc(len + 1);
-               memcpy(user_name, cp, len);
-               user_name[len] = '\0';
-               decoded = url_decode(user_name);
-               free(user_name);
-               user_name = decoded;
-               len = at - (colon + 1);
-               user_pass = xmalloc(len + 1);
-               memcpy(user_pass, colon + 1, len);
-               user_pass[len] = '\0';
-               decoded = url_decode(user_pass);
-               free(user_pass);
-               user_pass = decoded;
-       }
-}
-
 static void set_from_env(const char **var, const char *envname)
 {
        const char *val = getenv(envname);
@@ -361,7 +308,7 @@ static void set_from_env(const char **var, const char *envname)
                *var = val;
 }
 
-void http_init(struct remote *remote)
+void http_init(struct remote *remote, const char *url, int proactive_auth)
 {
        char *low_speed_limit;
        char *low_speed_time;
@@ -372,6 +319,8 @@ void http_init(struct remote *remote)
 
        curl_global_init(CURL_GLOBAL_ALL);
 
+       http_proactive_auth = proactive_auth;
+
        if (remote && remote->http_proxy)
                curl_http_proxy = xstrdup(remote->http_proxy);
 
@@ -425,11 +374,11 @@ void http_init(struct remote *remote)
        if (getenv("GIT_CURL_FTP_NO_EPSV"))
                curl_ftp_no_epsv = 1;
 
-       if (remote && remote->url && remote->url[0]) {
-               http_auth_init(remote->url[0]);
+       if (url) {
+               credential_from_url(&http_auth, url);
                if (!ssl_cert_password_required &&
                    getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") &&
-                   !prefixcmp(remote->url[0], "https://"))
+                   !prefixcmp(url, "https://"))
                        ssl_cert_password_required = 1;
        }
 
@@ -475,10 +424,10 @@ void http_cleanup(void)
                curl_http_proxy = NULL;
        }
 
-       if (ssl_cert_password != NULL) {
-               memset(ssl_cert_password, 0, strlen(ssl_cert_password));
-               free(ssl_cert_password);
-               ssl_cert_password = NULL;
+       if (cert_auth.password != NULL) {
+               memset(cert_auth.password, 0, strlen(cert_auth.password));
+               free(cert_auth.password);
+               cert_auth.password = NULL;
        }
        ssl_cert_password_required = 0;
 }
@@ -530,7 +479,6 @@ struct active_request_slot *get_active_slot(void)
 
        active_requests++;
        slot->in_use = 1;
-       slot->local = NULL;
        slot->results = NULL;
        slot->finished = NULL;
        slot->callback_data = NULL;
@@ -634,8 +582,6 @@ void step_active_slots(void)
 void run_active_slot(struct active_request_slot *slot)
 {
 #ifdef USE_CURL_MULTI
-       long last_pos = 0;
-       long current_pos;
        fd_set readfds;
        fd_set writefds;
        fd_set excfds;
@@ -645,25 +591,33 @@ void run_active_slot(struct active_request_slot *slot)
 
        slot->finished = &finished;
        while (!finished) {
-               data_received = 0;
                step_active_slots();
 
-               if (!data_received && slot->local != NULL) {
-                       current_pos = ftell(slot->local);
-                       if (current_pos > last_pos)
-                               data_received++;
-                       last_pos = current_pos;
-               }
+               if (slot->in_use) {
+#if LIBCURL_VERSION_NUM >= 0x070f04
+                       long curl_timeout;
+                       curl_multi_timeout(curlm, &curl_timeout);
+                       if (curl_timeout == 0) {
+                               continue;
+                       } else if (curl_timeout == -1) {
+                               select_timeout.tv_sec  = 0;
+                               select_timeout.tv_usec = 50000;
+                       } else {
+                               select_timeout.tv_sec  =  curl_timeout / 1000;
+                               select_timeout.tv_usec = (curl_timeout % 1000) * 1000;
+                       }
+#else
+                       select_timeout.tv_sec  = 0;
+                       select_timeout.tv_usec = 50000;
+#endif
 
-               if (slot->in_use && !data_received) {
-                       max_fd = 0;
+                       max_fd = -1;
                        FD_ZERO(&readfds);
                        FD_ZERO(&writefds);
                        FD_ZERO(&excfds);
-                       select_timeout.tv_sec = 0;
-                       select_timeout.tv_usec = 50000;
-                       select(max_fd, &readfds, &writefds,
-                              &excfds, &select_timeout);
+                       curl_multi_fdset(curlm, &readfds, &writefds, &excfds, &max_fd);
+
+                       select(max_fd+1, &readfds, &writefds, &excfds, &select_timeout);
                }
        }
 #else
@@ -741,14 +695,6 @@ static inline int needs_quote(int ch)
        return 1;
 }
 
-static inline int hex(int v)
-{
-       if (v < 10)
-               return '0' + v;
-       else
-               return 'A' + v - 10;
-}
-
 static char *quote_ref_url(const char *base, const char *ref)
 {
        struct strbuf buf = STRBUF_INIT;
@@ -816,7 +762,6 @@ static int http_request(const char *url, void *result, int target, int options)
                                headers = curl_slist_append(headers, buf.buf);
                                strbuf_reset(&buf);
                        }
-                       slot->local = result;
                } else
                        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
                                         fwrite_buffer);
@@ -838,16 +783,11 @@ static int http_request(const char *url, void *result, int target, int options)
                else if (missing_target(&results))
                        ret = HTTP_MISSING_TARGET;
                else if (results.http_code == 401) {
-                       if (user_name) {
+                       if (http_auth.username && http_auth.password) {
+                               credential_reject(&http_auth);
                                ret = HTTP_NOAUTH;
                        } else {
-                               /*
-                                * git_getpass is needed here because its very likely stdin/stdout are
-                                * pipes to our parent process.  So we instead need to use /dev/tty,
-                                * but that is non-portable.  Using git_getpass() can at least be stubbed
-                                * on other platforms with a different implementation if/when necessary.
-                                */
-                               user_name = xstrdup(git_getpass("Username: "));
+                               credential_fill(&http_auth);
                                init_curl_http_auth(slot->curl);
                                ret = HTTP_REAUTH;
                        }
@@ -863,20 +803,27 @@ static int http_request(const char *url, void *result, int target, int options)
                ret = HTTP_START_FAILED;
        }
 
-       slot->local = NULL;
        curl_slist_free_all(headers);
        strbuf_release(&buf);
 
+       if (ret == HTTP_OK)
+               credential_approve(&http_auth);
+
        return ret;
 }
 
+static int http_request_reauth(const char *url, void *result, int target,
+                              int options)
+{
+       int ret = http_request(url, result, target, options);
+       if (ret != HTTP_REAUTH)
+               return ret;
+       return http_request(url, result, target, options);
+}
+
 int http_get_strbuf(const char *url, struct strbuf *result, int options)
 {
-       int http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options);
-       if (http_ret == HTTP_REAUTH) {
-               http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options);
-       }
-       return http_ret;
+       return http_request_reauth(url, result, HTTP_REQUEST_STRBUF, options);
 }
 
 /*
@@ -899,7 +846,7 @@ static int http_get_file(const char *url, const char *filename, int options)
                goto cleanup;
        }
 
-       ret = http_request(url, result, HTTP_REQUEST_FILE, options);
+       ret = http_request_reauth(url, result, HTTP_REQUEST_FILE, options);
        fclose(result);
 
        if ((ret == HTTP_OK) && move_temp_to_file(tmpfile.buf, filename))
@@ -1053,7 +1000,6 @@ void release_http_pack_request(struct http_pack_request *preq)
        if (preq->packfile != NULL) {
                fclose(preq->packfile);
                preq->packfile = NULL;
-               preq->slot->local = NULL;
        }
        if (preq->range_header != NULL) {
                curl_slist_free_all(preq->range_header);
@@ -1075,7 +1021,6 @@ int finish_http_pack_request(struct http_pack_request *preq)
 
        fclose(preq->packfile);
        preq->packfile = NULL;
-       preq->slot->local = NULL;
 
        lst = preq->lst;
        while (*lst != p)
@@ -1144,7 +1089,6 @@ struct http_pack_request *new_http_pack_request(
        }
 
        preq->slot = get_active_slot();
-       preq->slot->local = preq->packfile;
        curl_easy_setopt(preq->slot->curl, CURLOPT_FILE, preq->packfile);
        curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
        curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url);
@@ -1201,7 +1145,6 @@ static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb,
                git_SHA1_Update(&freq->c, expn,
                                sizeof(expn) - freq->stream.avail_out);
        } while (freq->stream.avail_in && freq->zret == Z_OK);
-       data_received++;
        return size;
 }
 
diff --git a/http.h b/http.h
index 0bf8592dc45209c9be5b8ddac3a53f6fc11e29be..0b61653894eff606980427ee26770fa088438b94 100644 (file)
--- a/http.h
+++ b/http.h
@@ -49,7 +49,6 @@ struct slot_results {
 
 struct active_request_slot {
        CURL *curl;
-       FILE *local;
        int in_use;
        CURLcode curl_result;
        long http_code;
@@ -86,10 +85,10 @@ extern void add_fill_function(void *data, int (*fill)(void *));
 extern void step_active_slots(void);
 #endif
 
-extern void http_init(struct remote *remote);
+extern void http_init(struct remote *remote, const char *url,
+                     int proactive_auth);
 extern void http_cleanup(void);
 
-extern int data_received;
 extern int active_requests;
 extern int http_is_verbose;
 extern size_t http_post_buffer;
diff --git a/ident.c b/ident.c
index 35a6f264737e700ddd45714a0e966422a0d756c7..f619619b82379c2d205c7d7ea101049e373ab90a 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -50,6 +50,54 @@ static void copy_gecos(const struct passwd *w, char *name, size_t sz)
 
 }
 
+static int add_mailname_host(char *buf, size_t len)
+{
+       FILE *mailname;
+
+       mailname = fopen("/etc/mailname", "r");
+       if (!mailname) {
+               if (errno != ENOENT)
+                       warning("cannot open /etc/mailname: %s",
+                               strerror(errno));
+               return -1;
+       }
+       if (!fgets(buf, len, mailname)) {
+               if (ferror(mailname))
+                       warning("cannot read /etc/mailname: %s",
+                               strerror(errno));
+               fclose(mailname);
+               return -1;
+       }
+       /* success! */
+       fclose(mailname);
+       return 0;
+}
+
+static void add_domainname(char *buf, size_t len)
+{
+       struct hostent *he;
+       size_t namelen;
+       const char *domainname;
+
+       if (gethostname(buf, len)) {
+               warning("cannot get host name: %s", strerror(errno));
+               strlcpy(buf, "(none)", len);
+               return;
+       }
+       namelen = strlen(buf);
+       if (memchr(buf, '.', namelen))
+               return;
+
+       he = gethostbyname(buf);
+       buf[namelen++] = '.';
+       buf += namelen;
+       len -= namelen;
+       if (he && (domainname = strchr(he->h_name, '.')))
+               strlcpy(buf, domainname + 1, len);
+       else
+               strlcpy(buf, "(none)", len);
+}
+
 static void copy_email(const struct passwd *pw)
 {
        /*
@@ -61,35 +109,29 @@ static void copy_email(const struct passwd *pw)
                die("Your sysadmin must hate you!");
        memcpy(git_default_email, pw->pw_name, len);
        git_default_email[len++] = '@';
-       gethostname(git_default_email + len, sizeof(git_default_email) - len);
-       if (!strchr(git_default_email+len, '.')) {
-               struct hostent *he = gethostbyname(git_default_email + len);
-               char *domainname;
-
-               len = strlen(git_default_email);
-               git_default_email[len++] = '.';
-               if (he && (domainname = strchr(he->h_name, '.')))
-                       strlcpy(git_default_email + len, domainname + 1,
-                               sizeof(git_default_email) - len);
-               else
-                       strlcpy(git_default_email + len, "(none)",
-                               sizeof(git_default_email) - len);
-       }
+
+       if (!add_mailname_host(git_default_email + len,
+                               sizeof(git_default_email) - len))
+               return; /* read from "/etc/mailname" (Debian) */
+       add_domainname(git_default_email + len,
+                       sizeof(git_default_email) - len);
 }
 
-static void setup_ident(void)
+static void setup_ident(const char **name, const char **emailp)
 {
        struct passwd *pw = NULL;
 
        /* Get the name ("gecos") */
-       if (!git_default_name[0]) {
+       if (!*name && !git_default_name[0]) {
                pw = getpwuid(getuid());
                if (!pw)
                        die("You don't exist. Go away!");
                copy_gecos(pw, git_default_name, sizeof(git_default_name));
        }
+       if (!*name)
+               *name = git_default_name;
 
-       if (!git_default_email[0]) {
+       if (!*emailp && !git_default_email[0]) {
                const char *email = getenv("EMAIL");
 
                if (email && email[0]) {
@@ -104,6 +146,8 @@ static void setup_ident(void)
                        copy_email(pw);
                }
        }
+       if (!*emailp)
+               *emailp = git_default_email;
 
        /* And set the default date */
        if (!git_default_date[0])
@@ -199,11 +243,7 @@ const char *fmt_ident(const char *name, const char *email,
        int warn_on_no_name = (flag & IDENT_WARN_ON_NO_NAME);
        int name_addr_only = (flag & IDENT_NO_DATE);
 
-       setup_ident();
-       if (!name)
-               name = git_default_name;
-       if (!email)
-               email = git_default_email;
+       setup_ident(&name, &email);
 
        if (!*name) {
                struct passwd *pw;
index 9992233b96ba50f5dec590e3cebb4e628f3d5c5a..972ad62cd92ff2b09fb5caa21b24f471009ef19b 100644 (file)
@@ -25,6 +25,7 @@
 #include "cache.h"
 #include "exec_cmd.h"
 #include "run-command.h"
+#include "prompt.h"
 #ifdef NO_OPENSSL
 typedef void *SSL;
 #else
@@ -138,7 +139,6 @@ static struct imap_server_conf server = {
 struct imap_store_conf {
        struct store_conf gen;
        struct imap_server_conf *server;
-       unsigned use_namespace:1;
 };
 
 #define NIL    (void *)0x1
@@ -1186,13 +1186,10 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
                        goto bail;
                }
                if (!srvc->pass) {
-                       char prompt[80];
-                       sprintf(prompt, "Password (%s@%s): ", srvc->user, srvc->host);
-                       arg = git_getpass(prompt);
-                       if (!arg) {
-                               perror("getpass");
-                               exit(1);
-                       }
+                       struct strbuf prompt = STRBUF_INIT;
+                       strbuf_addf(&prompt, "Password (%s@%s): ", srvc->user, srvc->host);
+                       arg = git_getpass(prompt.buf);
+                       strbuf_release(&prompt);
                        if (!*arg) {
                                fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host);
                                goto bail;
@@ -1516,6 +1513,8 @@ int main(int argc, char **argv)
 
        git_extract_argv0_path(argv[0]);
 
+       git_setup_gettext();
+
        if (argc != 1)
                usage(imap_send_usage);
 
diff --git a/kwset.c b/kwset.c
index 956ae729501ed09b947eb33e161551beed2a1d95..51b2ab6c7e61413851a958f7a4009a0194744b68 100644 (file)
--- a/kwset.c
+++ b/kwset.c
@@ -674,7 +674,7 @@ cwexec (kwset_t kws, char const *text, size_t len, struct kwsmatch *kwsmatch)
      copy of the preceding main search loops. */
   if (lim - mch > kwset->maxd)
     lim = mch + kwset->maxd;
-  lmch = 0;
+  lmch = NULL;
   d = 1;
   while (lim - end >= d)
     {
index 0fb44e7ed7e8d7a98b8609778191715148f56c8d..3dd4a960190a1b0016b26dec9187692111b73e3f 100644 (file)
@@ -12,7 +12,8 @@ static void process_blob(struct rev_info *revs,
                         struct blob *blob,
                         show_object_fn show,
                         struct name_path *path,
-                        const char *name)
+                        const char *name,
+                        void *cb_data)
 {
        struct object *obj = &blob->object;
 
@@ -23,7 +24,7 @@ static void process_blob(struct rev_info *revs,
        if (obj->flags & (UNINTERESTING | SEEN))
                return;
        obj->flags |= SEEN;
-       show(obj, path, name);
+       show(obj, path, name, cb_data);
 }
 
 /*
@@ -52,7 +53,8 @@ static void process_gitlink(struct rev_info *revs,
                            const unsigned char *sha1,
                            show_object_fn show,
                            struct name_path *path,
-                           const char *name)
+                           const char *name,
+                           void *cb_data)
 {
        /* Nothing to do */
 }
@@ -62,13 +64,15 @@ static void process_tree(struct rev_info *revs,
                         show_object_fn show,
                         struct name_path *path,
                         struct strbuf *base,
-                        const char *name)
+                        const char *name,
+                        void *cb_data)
 {
        struct object *obj = &tree->object;
        struct tree_desc desc;
        struct name_entry entry;
        struct name_path me;
-       int match = revs->diffopt.pathspec.nr == 0 ? 2 : 0;
+       enum interesting match = revs->diffopt.pathspec.nr == 0 ?
+               all_entries_interesting: entry_not_interesting;
        int baselen = base->len;
 
        if (!revs->tree_objects)
@@ -80,7 +84,7 @@ static void process_tree(struct rev_info *revs,
        if (parse_tree(tree) < 0)
                die("bad tree object %s", sha1_to_hex(obj->sha1));
        obj->flags |= SEEN;
-       show(obj, path, name);
+       show(obj, path, name, cb_data);
        me.up = path;
        me.elem = name;
        me.elem_len = strlen(name);
@@ -94,26 +98,29 @@ static void process_tree(struct rev_info *revs,
        init_tree_desc(&desc, tree->buffer, tree->size);
 
        while (tree_entry(&desc, &entry)) {
-               if (match != 2) {
+               if (match != all_entries_interesting) {
                        match = tree_entry_interesting(&entry, base, 0,
                                                       &revs->diffopt.pathspec);
-                       if (match < 0)
+                       if (match == all_entries_not_interesting)
                                break;
-                       if (match == 0)
+                       if (match == entry_not_interesting)
                                continue;
                }
 
                if (S_ISDIR(entry.mode))
                        process_tree(revs,
                                     lookup_tree(entry.sha1),
-                                    show, &me, base, entry.path);
+                                    show, &me, base, entry.path,
+                                    cb_data);
                else if (S_ISGITLINK(entry.mode))
                        process_gitlink(revs, entry.sha1,
-                                       show, &me, entry.path);
+                                       show, &me, entry.path,
+                                       cb_data);
                else
                        process_blob(revs,
                                     lookup_blob(entry.sha1),
-                                    show, &me, entry.path);
+                                    show, &me, entry.path,
+                                    cb_data);
        }
        strbuf_setlen(base, baselen);
        free(tree->buffer);
@@ -185,17 +192,17 @@ void traverse_commit_list(struct rev_info *revs,
                        continue;
                if (obj->type == OBJ_TAG) {
                        obj->flags |= SEEN;
-                       show_object(obj, NULL, name);
+                       show_object(obj, NULL, name, data);
                        continue;
                }
                if (obj->type == OBJ_TREE) {
                        process_tree(revs, (struct tree *)obj, show_object,
-                                    NULL, &base, name);
+                                    NULL, &base, name, data);
                        continue;
                }
                if (obj->type == OBJ_BLOB) {
                        process_blob(revs, (struct blob *)obj, show_object,
-                                    NULL, name);
+                                    NULL, name, data);
                        continue;
                }
                die("unknown pending object %s (%s)",
index d65dbf03e657facb29a2846144eda2fa3687bc2f..3db7bb6fa386df2ccb73b78ee72881f32074a1b8 100644 (file)
@@ -2,11 +2,10 @@
 #define LIST_OBJECTS_H
 
 typedef void (*show_commit_fn)(struct commit *, void *);
-typedef void (*show_object_fn)(struct object *, const struct name_path *, const char *);
-typedef void (*show_edge_fn)(struct commit *);
-
+typedef void (*show_object_fn)(struct object *, const struct name_path *, const char *, void *);
 void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, void *);
 
+typedef void (*show_edge_fn)(struct commit *);
 void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn);
 
 #endif
index 24c295ea1dc91180b9685299f48812593162bdfe..c719a6e38597130597083666e5328517b075228e 100644 (file)
@@ -8,6 +8,7 @@
 #include "refs.h"
 #include "string-list.h"
 #include "color.h"
+#include "gpg-interface.h"
 
 struct decoration name_decoration = { "object names" };
 
@@ -165,6 +166,14 @@ static void show_parents(struct commit *commit, int abbrev)
        }
 }
 
+static void show_children(struct rev_info *opt, struct commit *commit, int abbrev)
+{
+       struct commit_list *p = lookup_decoration(&opt->children, &commit->object);
+       for ( ; p; p = p->next) {
+               printf(" %s", find_unique_abbrev(p->item->object.sha1, abbrev));
+       }
+}
+
 void show_decorations(struct rev_info *opt, struct commit *commit)
 {
        const char *prefix;
@@ -395,6 +404,129 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
        *extra_headers_p = extra_headers;
 }
 
+static void show_sig_lines(struct rev_info *opt, int status, const char *bol)
+{
+       const char *color, *reset, *eol;
+
+       color = diff_get_color_opt(&opt->diffopt,
+                                  status ? DIFF_WHITESPACE : DIFF_FRAGINFO);
+       reset = diff_get_color_opt(&opt->diffopt, DIFF_RESET);
+       while (*bol) {
+               eol = strchrnul(bol, '\n');
+               printf("%s%.*s%s%s", color, (int)(eol - bol), bol, reset,
+                      *eol ? "\n" : "");
+               bol = (*eol) ? (eol + 1) : eol;
+       }
+}
+
+static void show_signature(struct rev_info *opt, struct commit *commit)
+{
+       struct strbuf payload = STRBUF_INIT;
+       struct strbuf signature = STRBUF_INIT;
+       struct strbuf gpg_output = STRBUF_INIT;
+       int status;
+
+       if (parse_signed_commit(commit->object.sha1, &payload, &signature) <= 0)
+               goto out;
+
+       status = verify_signed_buffer(payload.buf, payload.len,
+                                     signature.buf, signature.len,
+                                     &gpg_output);
+       if (status && !gpg_output.len)
+               strbuf_addstr(&gpg_output, "No signature\n");
+
+       show_sig_lines(opt, status, gpg_output.buf);
+
+ out:
+       strbuf_release(&gpg_output);
+       strbuf_release(&payload);
+       strbuf_release(&signature);
+}
+
+static int which_parent(const unsigned char *sha1, const struct commit *commit)
+{
+       int nth;
+       const struct commit_list *parent;
+
+       for (nth = 0, parent = commit->parents; parent; parent = parent->next) {
+               if (!hashcmp(parent->item->object.sha1, sha1))
+                       return nth;
+               nth++;
+       }
+       return -1;
+}
+
+static int is_common_merge(const struct commit *commit)
+{
+       return (commit->parents
+               && commit->parents->next
+               && !commit->parents->next->next);
+}
+
+static void show_one_mergetag(struct rev_info *opt,
+                             struct commit_extra_header *extra,
+                             struct commit *commit)
+{
+       unsigned char sha1[20];
+       struct tag *tag;
+       struct strbuf verify_message;
+       int status, nth;
+       size_t payload_size, gpg_message_offset;
+
+       hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), sha1);
+       tag = lookup_tag(sha1);
+       if (!tag)
+               return; /* error message already given */
+
+       strbuf_init(&verify_message, 256);
+       if (parse_tag_buffer(tag, extra->value, extra->len))
+               strbuf_addstr(&verify_message, "malformed mergetag\n");
+       else if (is_common_merge(commit) &&
+                !hashcmp(tag->tagged->sha1,
+                         commit->parents->next->item->object.sha1))
+               strbuf_addf(&verify_message,
+                           "merged tag '%s'\n", tag->tag);
+       else if ((nth = which_parent(tag->tagged->sha1, commit)) < 0)
+               strbuf_addf(&verify_message, "tag %s names a non-parent %s\n",
+                                   tag->tag, tag->tagged->sha1);
+       else
+               strbuf_addf(&verify_message,
+                           "parent #%d, tagged '%s'\n", nth + 1, tag->tag);
+       gpg_message_offset = verify_message.len;
+
+       payload_size = parse_signature(extra->value, extra->len);
+       if ((extra->len <= payload_size) ||
+           (verify_signed_buffer(extra->value, payload_size,
+                                 extra->value + payload_size,
+                                 extra->len - payload_size,
+                                 &verify_message) &&
+            verify_message.len <= gpg_message_offset)) {
+               strbuf_addstr(&verify_message, "No signature\n");
+               status = -1;
+       }
+       else if (strstr(verify_message.buf + gpg_message_offset,
+                       ": Good signature from "))
+               status = 0;
+       else
+               status = -1;
+
+       show_sig_lines(opt, status, verify_message.buf);
+       strbuf_release(&verify_message);
+}
+
+static void show_mergetag(struct rev_info *opt, struct commit *commit)
+{
+       struct commit_extra_header *extra, *to_free;
+
+       to_free = read_commit_extra_headers(commit, NULL);
+       for (extra = to_free; extra; extra = extra->next) {
+               if (strcmp(extra->key, "mergetag"))
+                       continue; /* not a merge tag */
+               show_one_mergetag(opt, extra, commit);
+       }
+       free_commit_extra_headers(to_free);
+}
+
 void show_log(struct rev_info *opt)
 {
        struct strbuf msgbuf = STRBUF_INIT;
@@ -414,6 +546,8 @@ void show_log(struct rev_info *opt)
                fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
                if (opt->print_parents)
                        show_parents(commit, abbrev_commit);
+               if (opt->children.name)
+                       show_children(opt, commit, abbrev_commit);
                show_decorations(opt, commit);
                if (opt->graph && !graph_is_commit_finished(opt->graph)) {
                        putchar('\n');
@@ -473,6 +607,8 @@ void show_log(struct rev_info *opt)
                      stdout);
                if (opt->print_parents)
                        show_parents(commit, abbrev_commit);
+               if (opt->children.name)
+                       show_children(opt, commit, abbrev_commit);
                if (parent)
                        printf(" (from %s)",
                               find_unique_abbrev(parent->object.sha1,
@@ -502,6 +638,11 @@ void show_log(struct rev_info *opt)
                }
        }
 
+       if (opt->show_signature) {
+               show_signature(opt, commit);
+               show_mergetag(opt, commit);
+       }
+
        if (!commit->buffer)
                return;
 
@@ -587,9 +728,7 @@ int log_tree_diff_flush(struct rev_info *opt)
 
 static int do_diff_combined(struct rev_info *opt, struct commit *commit)
 {
-       unsigned const char *sha1 = commit->object.sha1;
-
-       diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);
+       diff_tree_combined_merge(commit, opt->dense_combined_merges, opt);
        return !opt->loginfo;
 }
 
index 8c3196c7d76ff90e90a9fb4ff569aff6a429861a..47aa41924507f7603aab5e35abb51b3956a33dad 100644 (file)
--- a/mailmap.c
+++ b/mailmap.c
@@ -190,27 +190,27 @@ void clear_mailmap(struct string_list *map)
 int map_user(struct string_list *map,
             char *email, int maxlen_email, char *name, int maxlen_name)
 {
-       char *p;
+       char *end_of_email;
        struct string_list_item *item;
        struct mailmap_entry *me;
        char buf[1024], *mailbuf;
        int i;
 
        /* figure out space requirement for email */
-       p = strchr(email, '>');
-       if (!p) {
+       end_of_email = strchr(email, '>');
+       if (!end_of_email) {
                /* email passed in might not be wrapped in <>, but end with a \0 */
-               p = memchr(email, '\0', maxlen_email);
-               if (!p)
+               end_of_email = memchr(email, '\0', maxlen_email);
+               if (!end_of_email)
                        return 0;
        }
-       if (p - email + 1 < sizeof(buf))
+       if (end_of_email - email + 1 < sizeof(buf))
                mailbuf = buf;
        else
-               mailbuf = xmalloc(p - email + 1);
+               mailbuf = xmalloc(end_of_email - email + 1);
 
        /* downcase the email address */
-       for (i = 0; i < p - email; i++)
+       for (i = 0; i < end_of_email - email; i++)
                mailbuf[i] = tolower(email[i]);
        mailbuf[i] = 0;
 
@@ -236,6 +236,8 @@ int map_user(struct string_list *map,
                }
                if (maxlen_email && mi->email)
                        strlcpy(email, mi->email, maxlen_email);
+               else
+                       *end_of_email = '\0';
                if (maxlen_name && mi->name)
                        strlcpy(name, mi->name, maxlen_name);
                debug_mm("map_user:  to '%s' <%s>\n", name, mi->email ? mi->email : "");
index c34a4f148b65cf81f28e2aed6c35e141e175b324..d83cd6c662847fb51641d7b8bf16739e588f67a2 100644 (file)
@@ -38,16 +38,15 @@ static struct tree *shift_tree_object(struct tree *one, struct tree *two,
        return lookup_tree(shifted);
 }
 
-/*
- * A virtual commit has (const char *)commit->util set to the name.
- */
-
 static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
 {
        struct commit *commit = xcalloc(1, sizeof(struct commit));
+       struct merge_remote_desc *desc = xmalloc(sizeof(*desc));
+
+       desc->name = comment;
+       desc->obj = (struct object *)commit;
        commit->tree = tree;
-       commit->util = (void*)comment;
-       /* avoid warnings */
+       commit->util = desc;
        commit->object.parsed = 1;
        return commit;
 }
@@ -184,7 +183,7 @@ static void output_commit_title(struct merge_options *o, struct commit *commit)
        for (i = o->call_depth; i--;)
                fputs("  ", stdout);
        if (commit->util)
-               printf("virtual %s\n", (char *)commit->util);
+               printf("virtual %s\n", merge_remote_util(commit)->name);
        else {
                printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
                if (parse_commit(commit) != 0)
@@ -265,7 +264,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 
        if (!cache_tree_fully_valid(active_cache_tree) &&
            cache_tree_update(active_cache_tree,
-                             active_cache, active_nr, 0, 0) < 0)
+                             active_cache, active_nr, 0, 0, 0) < 0)
                die("error building trees");
 
        result = lookup_tree(active_cache_tree->sha1);
@@ -946,8 +945,10 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
                        free(result_buf.ptr);
                        result.clean = (merge_status == 0);
                } else if (S_ISGITLINK(a->mode)) {
-                       result.clean = merge_submodule(result.sha, one->path, one->sha1,
-                                                      a->sha1, b->sha1);
+                       result.clean = merge_submodule(result.sha,
+                                                      one->path, one->sha1,
+                                                      a->sha1, b->sha1,
+                                                      !o->call_depth);
                } else if (S_ISLNK(a->mode)) {
                        hashcpy(result.sha, a->sha1);
 
index 27b3dd48b840139acd65ea742e04221c6400a485..b6319d206e2333e42469486604d4635e846e5faa 100644 (file)
@@ -16,5 +16,10 @@ merge_cmd () {
 }
 
 translate_merge_tool_path() {
-       echo bcompare
+       if type bcomp >/dev/null 2>/dev/null
+       then
+               echo bcomp
+       else
+               echo bcompare
+       fi
 }
index eaa115ccb7a1154715c1d9dd9b7d4d360234aa01..cb672a55192cb44335aa2cbe1f10004bb26d86ff 100644 (file)
@@ -23,7 +23,7 @@ check_meld_for_output_version () {
        meld_path="$(git config mergetool.meld.path)"
        meld_path="${meld_path:-meld}"
 
-       if "$meld_path" --output /dev/null --help >/dev/null 2>&1
+       if "$meld_path" --help 2>&1 | grep -e --output >/dev/null
        then
                meld_has_output_option=true
        else
index c6b6a3fe4cd94e48893b172c17b6e7df3bfa36f8..d8d25c23e99dddd9bd0bf83d73f2ae136d7307b5 100644 (file)
@@ -57,12 +57,10 @@ static void hash_index_entry_directories(struct index_state *istate, struct cach
                if (*ptr == '/') {
                        ++ptr;
                        hash = hash_name(ce->name, ptr - ce->name);
-                       if (!lookup_hash(hash, &istate->name_hash)) {
-                               pos = insert_hash(hash, ce, &istate->name_hash);
-                               if (pos) {
-                                       ce->next = *pos;
-                                       *pos = ce;
-                               }
+                       pos = insert_hash(hash, ce, &istate->name_hash);
+                       if (pos) {
+                               ce->dir_next = *pos;
+                               *pos = ce;
                        }
                }
        }
@@ -76,7 +74,7 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
        if (ce->ce_flags & CE_HASHED)
                return;
        ce->ce_flags |= CE_HASHED;
-       ce->next = NULL;
+       ce->next = ce->dir_next = NULL;
        hash = hash_name(ce->name, ce_namelen(ce));
        pos = insert_hash(hash, ce, &istate->name_hash);
        if (pos) {
@@ -166,7 +164,10 @@ struct cache_entry *index_name_exists(struct index_state *istate, const char *na
                        if (same_name(ce, name, namelen, icase))
                                return ce;
                }
-               ce = ce->next;
+               if (icase && name[namelen - 1] == '/')
+                       ce = ce->dir_next;
+               else
+                       ce = ce->next;
        }
 
        /*
index 4c8984ede1e218d3e0aaadb6d8c72bfcafddeee9..eabe4a0d9bf44050dba43cbd821642cdfd3fb40b 100644 (file)
@@ -48,6 +48,7 @@ int notes_cache_write(struct notes_cache *c)
 {
        unsigned char tree_sha1[20];
        unsigned char commit_sha1[20];
+       struct strbuf msg = STRBUF_INIT;
 
        if (!c || !c->tree.initialized || !c->tree.ref || !*c->tree.ref)
                return -1;
@@ -56,7 +57,9 @@ int notes_cache_write(struct notes_cache *c)
 
        if (write_notes_tree(&c->tree, tree_sha1))
                return -1;
-       if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0)
+       strbuf_attach(&msg, c->validity,
+                     strlen(c->validity), strlen(c->validity) + 1);
+       if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
                return -1;
        if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
                       0, QUIET_ON_ERR) < 0)
index d0e5034da4991777bad8e0a42a45801e0e785b7d..fb0832f97d218ecd1812361721800d6288935c06 100644 (file)
@@ -530,7 +530,7 @@ static int merge_from_diffs(struct notes_merge_options *o,
 }
 
 void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
-                        const char *msg, unsigned char *result_sha1)
+                        const struct strbuf *msg, unsigned char *result_sha1)
 {
        unsigned char tree_sha1[20];
 
@@ -551,7 +551,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
                /* else: t->ref points to nothing, assume root/orphan commit */
        }
 
-       if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))
+       if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))
                die("Failed to commit notes tree to database");
 }
 
@@ -573,9 +573,10 @@ int notes_merge(struct notes_merge_options *o,
               o->local_ref, o->remote_ref);
 
        /* Dereference o->local_ref into local_sha1 */
-       if (!resolve_ref(o->local_ref, local_sha1, 0, NULL))
+       if (read_ref_full(o->local_ref, local_sha1, 0, NULL))
                die("Failed to resolve local notes ref '%s'", o->local_ref);
-       else if (!check_ref_format(o->local_ref) && is_null_sha1(local_sha1))
+       else if (!check_refname_format(o->local_ref, 0) &&
+               is_null_sha1(local_sha1))
                local = NULL; /* local_sha1 == null_sha1 indicates unborn ref */
        else if (!(local = lookup_commit_reference(local_sha1)))
                die("Could not parse local commit %s (%s)",
@@ -588,7 +589,7 @@ int notes_merge(struct notes_merge_options *o,
                 * Failed to get remote_sha1. If o->remote_ref looks like an
                 * unborn ref, perform the merge using an empty notes tree.
                 */
-               if (!check_ref_format(o->remote_ref)) {
+               if (!check_refname_format(o->remote_ref, 0)) {
                        hashclr(remote_sha1);
                        remote = NULL;
                } else {
@@ -667,7 +668,7 @@ int notes_merge(struct notes_merge_options *o,
                struct commit_list *parents = NULL;
                commit_list_insert(remote, &parents); /* LIFO order */
                commit_list_insert(local, &parents);
-               create_notes_commit(local_tree, parents, o->commit_msg.buf,
+               create_notes_commit(local_tree, parents, &o->commit_msg,
                                    result_sha1);
        }
 
@@ -694,7 +695,8 @@ int notes_merge_commit(struct notes_merge_options *o,
        struct dir_struct dir;
        char *path = xstrdup(git_path(NOTES_MERGE_WORKTREE "/"));
        int path_len = strlen(path), i;
-       const char *msg = strstr(partial_commit->buffer, "\n\n");
+       char *msg = strstr(partial_commit->buffer, "\n\n");
+       struct strbuf sb_msg = STRBUF_INIT;
 
        if (o->verbosity >= 3)
                printf("Committing notes in notes merge worktree at %.*s\n",
@@ -732,7 +734,8 @@ int notes_merge_commit(struct notes_merge_options *o,
                                sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
        }
 
-       create_notes_commit(partial_tree, partial_commit->parents, msg,
+       strbuf_attach(&sb_msg, msg, strlen(msg), strlen(msg) + 1);
+       create_notes_commit(partial_tree, partial_commit->parents, &sb_msg,
                            result_sha1);
        if (o->verbosity >= 4)
                printf("Finalized notes merge commit: %s\n",
index 168a6724cd873602e3da1ff3b7e2cc67a5b240ae..0c11b173a1e38ddeb702e022ebdd571ed2ad12f3 100644 (file)
@@ -37,7 +37,7 @@ void init_notes_merge_options(struct notes_merge_options *o);
  * The resulting commit SHA1 is stored in result_sha1.
  */
 void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
-                        const char *msg, unsigned char *result_sha1);
+                        const struct strbuf *msg, unsigned char *result_sha1);
 
 /*
  * Merge notes from o->remote_ref into o->local_ref
index 31976b5d70b6310552b04ce79c7ea0b07bc536d7..d8d09f92aacd114e23378af2cfbeb78a3dd785a0 100644 (file)
--- a/object.c
+++ b/object.c
@@ -149,6 +149,8 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t
                struct tree *tree = lookup_tree(sha1);
                if (tree) {
                        obj = &tree->object;
+                       if (!tree->buffer)
+                               tree->object.parsed = 0;
                        if (!tree->object.parsed) {
                                if (parse_tree_buffer(tree, buffer, size))
                                        return NULL;
index 0c19b6e5a5677bd14989175abddc119381fac4ef..63a595c45c961fce54cca95ab6e09e3336f0bb8e 100644 (file)
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "pack.h"
 #include "pack-revindex.h"
+#include "progress.h"
 
 struct idx_entry {
        off_t                offset;
@@ -42,7 +43,10 @@ int check_pack_crc(struct packed_git *p, struct pack_window **w_curs,
 }
 
 static int verify_packfile(struct packed_git *p,
-               struct pack_window **w_curs)
+                          struct pack_window **w_curs,
+                          verify_fn fn,
+                          struct progress *progress, uint32_t base_count)
+
 {
        off_t index_size = p->index_size;
        const unsigned char *index_base = p->index_data;
@@ -113,20 +117,25 @@ static int verify_packfile(struct packed_git *p,
                                            p->pack_name, (uintmax_t)offset);
                }
                data = unpack_entry(p, entries[i].offset, &type, &size);
-               if (!data) {
+               if (!data)
                        err = error("cannot unpack %s from %s at offset %"PRIuMAX"",
                                    sha1_to_hex(entries[i].sha1), p->pack_name,
                                    (uintmax_t)entries[i].offset);
-                       break;
-               }
-               if (check_sha1_signature(entries[i].sha1, data, size, typename(type))) {
+               else if (check_sha1_signature(entries[i].sha1, data, size, typename(type)))
                        err = error("packed %s from %s is corrupt",
                                    sha1_to_hex(entries[i].sha1), p->pack_name);
-                       free(data);
-                       break;
+               else if (fn) {
+                       int eaten = 0;
+                       fn(entries[i].sha1, type, size, data, &eaten);
+                       if (eaten)
+                               data = NULL;
                }
+               if (((base_count + i) & 1023) == 0)
+                       display_progress(progress, base_count + i);
                free(data);
+
        }
+       display_progress(progress, base_count + i);
        free(entries);
 
        return err;
@@ -155,7 +164,8 @@ int verify_pack_index(struct packed_git *p)
        return err;
 }
 
-int verify_pack(struct packed_git *p)
+int verify_pack(struct packed_git *p, verify_fn fn,
+               struct progress *progress, uint32_t base_count)
 {
        int err = 0;
        struct pack_window *w_curs = NULL;
@@ -164,7 +174,7 @@ int verify_pack(struct packed_git *p)
        if (!p->index_data)
                return -1;
 
-       err |= verify_packfile(p, &w_curs);
+       err |= verify_packfile(p, &w_curs, fn, progress, base_count);
        unuse_pack(&w_curs);
 
        return err;
index 1290570260d184b9c94fdab62650d6e474b365b4..23bbd00e3e542e844fce08156c5c1073e6437d43 100644 (file)
@@ -72,7 +72,7 @@ static void try_remove_empty_parents(char *name)
        for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
                while (*p && *p != '/')
                        p++;
-               /* tolerate duplicate slashes; see check_ref_format() */
+               /* tolerate duplicate slashes; see check_refname_format() */
                while (*p == '/')
                        p++;
        }
index 9cd3bfbb4b3859cbbdc1b9375ea95f511fffc94e..ca9e63be18f9333bf0f603ddb9ddd923588be0aa 100644 (file)
@@ -73,9 +73,9 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
                f = sha1fd_check(index_name);
        } else {
                if (!index_name) {
-                       static char tmpfile[PATH_MAX];
-                       fd = odb_mkstemp(tmpfile, sizeof(tmpfile), "pack/tmp_idx_XXXXXX");
-                       index_name = xstrdup(tmpfile);
+                       static char tmp_file[PATH_MAX];
+                       fd = odb_mkstemp(tmp_file, sizeof(tmp_file), "pack/tmp_idx_XXXXXX");
+                       index_name = xstrdup(tmp_file);
                } else {
                        unlink(index_name);
                        fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
@@ -129,6 +129,10 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
                }
                sha1write(f, obj->sha1, 20);
                git_SHA1_Update(&ctx, obj->sha1, 20);
+               if ((opts->flags & WRITE_IDX_STRICT) &&
+                   (i && !hashcmp(list[-2]->sha1, obj->sha1)))
+                       die("The same object %s appears twice in the pack",
+                           sha1_to_hex(obj->sha1));
        }
 
        if (index_version >= 2) {
@@ -178,6 +182,18 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
        return index_name;
 }
 
+off_t write_pack_header(struct sha1file *f, uint32_t nr_entries)
+{
+       struct pack_header hdr;
+
+       hdr.hdr_signature = htonl(PACK_SIGNATURE);
+       hdr.hdr_version = htonl(PACK_VERSION);
+       hdr.hdr_entries = htonl(nr_entries);
+       if (sha1write(f, &hdr, sizeof(hdr)))
+               return 0;
+       return sizeof(hdr);
+}
+
 /*
  * Update pack header with object_count and compute new SHA1 for pack data
  * associated to pack_fd, and write that SHA1 at the end.  That new SHA1
@@ -316,3 +332,44 @@ int encode_in_pack_object_header(enum object_type type, uintmax_t size, unsigned
        *hdr = c;
        return n;
 }
+
+struct sha1file *create_tmp_packfile(char **pack_tmp_name)
+{
+       char tmpname[PATH_MAX];
+       int fd;
+
+       fd = odb_mkstemp(tmpname, sizeof(tmpname), "pack/tmp_pack_XXXXXX");
+       *pack_tmp_name = xstrdup(tmpname);
+       return sha1fd(fd, *pack_tmp_name);
+}
+
+void finish_tmp_packfile(char *name_buffer,
+                        const char *pack_tmp_name,
+                        struct pack_idx_entry **written_list,
+                        uint32_t nr_written,
+                        struct pack_idx_option *pack_idx_opts,
+                        unsigned char sha1[])
+{
+       const char *idx_tmp_name;
+       char *end_of_name_prefix = strrchr(name_buffer, 0);
+
+       if (adjust_shared_perm(pack_tmp_name))
+               die_errno("unable to make temporary pack file readable");
+
+       idx_tmp_name = write_idx_file(NULL, written_list, nr_written,
+                                     pack_idx_opts, sha1);
+       if (adjust_shared_perm(idx_tmp_name))
+               die_errno("unable to make temporary index file readable");
+
+       sprintf(end_of_name_prefix, "%s.pack", sha1_to_hex(sha1));
+       free_pack_by_name(name_buffer);
+
+       if (rename(pack_tmp_name, name_buffer))
+               die_errno("unable to rename temporary pack file");
+
+       sprintf(end_of_name_prefix, "%s.idx", sha1_to_hex(sha1));
+       if (rename(idx_tmp_name, name_buffer))
+               die_errno("unable to rename temporary index file");
+
+       free((void *)idx_tmp_name);
+}
diff --git a/pack.h b/pack.h
index 722a54e00a2cb7d9514c12f799fb1ec15930cf5d..aa6ee7d606f1d5336bff9a3067b44057c362e788 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -2,6 +2,7 @@
 #define PACK_H
 
 #include "object.h"
+#include "csum-file.h"
 
 /*
  * Packed object header
@@ -37,7 +38,8 @@ struct pack_header {
 struct pack_idx_option {
        unsigned flags;
        /* flag bits */
-#define WRITE_IDX_VERIFY 01
+#define WRITE_IDX_VERIFY 01 /* verify only, do not write the idx file */
+#define WRITE_IDX_STRICT 02
 
        uint32_t version;
        uint32_t off32_limit;
@@ -70,10 +72,15 @@ struct pack_idx_entry {
        off_t offset;
 };
 
+
+struct progress;
+typedef int (*verify_fn)(const unsigned char*, enum object_type, unsigned long, void*, int*);
+
 extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, const struct pack_idx_option *, unsigned char *sha1);
 extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr);
 extern int verify_pack_index(struct packed_git *);
-extern int verify_pack(struct packed_git *);
+extern int verify_pack(struct packed_git *, verify_fn fn, struct progress *, uint32_t);
+extern off_t write_pack_header(struct sha1file *f, uint32_t);
 extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t);
 extern char *index_pack_lockfile(int fd);
 extern int encode_in_pack_object_header(enum object_type, uintmax_t, unsigned char *);
@@ -82,4 +89,8 @@ extern int encode_in_pack_object_header(enum object_type, uintmax_t, unsigned ch
 #define PH_ERROR_PACK_SIGNATURE        (-2)
 #define PH_ERROR_PROTOCOL      (-3)
 extern int read_pack_header(int fd, struct pack_header *);
+
+extern struct sha1file *create_tmp_packfile(char **pack_tmp_name);
+extern void finish_tmp_packfile(char *name_buffer, const char *pack_tmp_name, struct pack_idx_entry **written_list, uint32_t nr_written, struct pack_idx_option *pack_idx_opts, unsigned char sha1[]);
+
 #endif
diff --git a/pager.c b/pager.c
index 975955ba82a0dbb128d6733090cd74c2b509ea81..b7909678f459d90d1c8907c78bb5fcb512fe558d 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -76,6 +76,12 @@ void setup_pager(void)
        if (!pager)
                return;
 
+       /*
+        * force computing the width of the terminal before we redirect
+        * the standard output to the pager.
+        */
+       (void) term_columns();
+
        setenv("GIT_PAGER_IN_USE", "true", 1);
 
        /* spawn the pager */
@@ -110,3 +116,34 @@ int pager_in_use(void)
        env = getenv("GIT_PAGER_IN_USE");
        return env ? git_config_bool("GIT_PAGER_IN_USE", env) : 0;
 }
+
+/*
+ * Return cached value (if set) or $COLUMNS environment variable (if
+ * set and positive) or ioctl(1, TIOCGWINSZ).ws_col (if positive),
+ * and default to 80 if all else fails.
+ */
+int term_columns(void)
+{
+       static int term_columns_at_startup;
+
+       char *col_string;
+       int n_cols;
+
+       if (term_columns_at_startup)
+               return term_columns_at_startup;
+
+       term_columns_at_startup = 80;
+
+       col_string = getenv("COLUMNS");
+       if (col_string && (n_cols = atoi(col_string)) > 0)
+               term_columns_at_startup = n_cols;
+#ifdef TIOCGWINSZ
+       else {
+               struct winsize ws;
+               if (!ioctl(1, TIOCGWINSZ, &ws) && ws.ws_col)
+                       term_columns_at_startup = ws.ws_col;
+       }
+#endif
+
+       return term_columns_at_startup;
+}
index 6db0921fc1fde3b5fbf829577bdd019ae95462e0..0de5fb168a5c1b86c50be0d0c3f2d7228e7159a1 100644 (file)
@@ -123,3 +123,8 @@ int parse_opt_string_list(const struct option *opt, const char *arg, int unset)
        string_list_append(v, xstrdup(arg));
        return 0;
 }
+
+int parse_opt_noop_cb(const struct option *opt, const char *arg, int unset)
+{
+       return 0;
+}
index 503ab5d500c8ca2fe30f50601717979e752b9254..f0098eb8ea7eed27d8a67952481f043ce7c75d4a 100644 (file)
@@ -83,7 +83,7 @@ static int get_value(struct parse_opt_ctx_t *p,
                        *(int *)opt->value &= ~opt->defval;
                return 0;
 
-       case OPTION_BOOLEAN:
+       case OPTION_COUNTUP:
                *(int *)opt->value = unset ? 0 : *(int *)opt->value + 1;
                return 0;
 
@@ -319,7 +319,7 @@ static void parse_options_check(const struct option *opts)
                        err |= optbug(opts, "uses feature "
                                        "not supported for dashless options");
                switch (opts->type) {
-               case OPTION_BOOLEAN:
+               case OPTION_COUNTUP:
                case OPTION_BIT:
                case OPTION_NEGBIT:
                case OPTION_SET_INT:
index 59e0b524bdcbe1c061f49b8e5f3c6365a9f2eba4..2e811dc7da8e6adc7ebce1a9929e8c345424fe4a 100644 (file)
@@ -10,7 +10,7 @@ enum parse_opt_type {
        /* options with no arguments */
        OPTION_BIT,
        OPTION_NEGBIT,
-       OPTION_BOOLEAN, /* _INCR would have been a better name */
+       OPTION_COUNTUP,
        OPTION_SET_INT,
        OPTION_SET_PTR,
        /* options with arguments (usually) */
@@ -21,6 +21,9 @@ enum parse_opt_type {
        OPTION_FILENAME
 };
 
+/* Deprecated synonym */
+#define OPTION_BOOLEAN OPTION_COUNTUP
+
 enum parse_opt_flags {
        PARSE_OPT_KEEP_DASHDASH = 1,
        PARSE_OPT_STOP_AT_NON_OPTION = 2,
@@ -122,10 +125,11 @@ struct option {
                                      PARSE_OPT_NOARG, NULL, (b) }
 #define OPT_NEGBIT(s, l, v, h, b)   { OPTION_NEGBIT, (s), (l), (v), NULL, \
                                      (h), PARSE_OPT_NOARG, NULL, (b) }
-#define OPT_BOOLEAN(s, l, v, h)     { OPTION_BOOLEAN, (s), (l), (v), NULL, \
+#define OPT_COUNTUP(s, l, v, h)     { OPTION_COUNTUP, (s), (l), (v), NULL, \
                                      (h), PARSE_OPT_NOARG }
 #define OPT_SET_INT(s, l, v, h, i)  { OPTION_SET_INT, (s), (l), (v), NULL, \
                                      (h), PARSE_OPT_NOARG, NULL, (i) }
+#define OPT_BOOL(s, l, v, h)        OPT_SET_INT(s, l, v, h, 1)
 #define OPT_SET_PTR(s, l, v, h, p)  { OPTION_SET_PTR, (s), (l), (v), NULL, \
                                      (h), PARSE_OPT_NOARG, NULL, (p) }
 #define OPT_INTEGER(s, l, v, h)     { OPTION_INTEGER, (s), (l), (v), "n", (h) }
@@ -149,6 +153,13 @@ struct option {
        { OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \
                parse_opt_color_flag_cb, (intptr_t)"always" }
 
+#define OPT_NOOP_NOARG(s, l) \
+       { OPTION_CALLBACK, (s), (l), NULL, NULL, \
+         "no-op (backward compatibility)", \
+         PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, parse_opt_noop_cb }
+
+/* Deprecated synonym */
+#define OPT_BOOLEAN OPT_COUNTUP
 
 /* parse_options() will filter out the processed options and leave the
  * non-option arguments in argv[].
@@ -210,6 +221,7 @@ extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
 extern int parse_opt_with_commit(const struct option *, const char *, int);
 extern int parse_opt_tertiary(const struct option *, const char *, int);
 extern int parse_opt_string_list(const struct option *, const char *, int);
+extern int parse_opt_noop_cb(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var, h)  OPT_BOOLEAN('v', "verbose", (var), (h))
 #define OPT__QUIET(var, h)    OPT_BOOLEAN('q', "quiet",   (var), (h))
diff --git a/path.c b/path.c
index 6f3f5d56c0ed76f50d1aa37646d18ae280f1edbb..6f2aa699ad63c2f7c65632761efbaebb7f461868 100644 (file)
--- a/path.c
+++ b/path.c
@@ -283,7 +283,7 @@ return_null:
  * links.  User relative paths are also returned as they are given,
  * except DWIM suffixing.
  */
-char *enter_repo(char *path, int strict)
+const char *enter_repo(const char *path, int strict)
 {
        static char used_path[PATH_MAX];
        static char validated_path[PATH_MAX];
@@ -293,18 +293,21 @@ char *enter_repo(char *path, int strict)
 
        if (!strict) {
                static const char *suffix[] = {
-                       ".git/.git", "/.git", ".git", "", NULL,
+                       "/.git", "", ".git/.git", ".git", NULL,
                };
+               const char *gitfile;
                int len = strlen(path);
                int i;
-               while ((1 < len) && (path[len-1] == '/')) {
-                       path[len-1] = 0;
+               while ((1 < len) && (path[len-1] == '/'))
                        len--;
-               }
+
                if (PATH_MAX <= len)
                        return NULL;
-               if (path[0] == '~') {
-                       char *newpath = expand_user_path(path);
+               strncpy(used_path, path, len); used_path[len] = 0 ;
+               strcpy(validated_path, used_path);
+
+               if (used_path[0] == '~') {
+                       char *newpath = expand_user_path(used_path);
                        if (!newpath || (PATH_MAX - 10 < strlen(newpath))) {
                                free(newpath);
                                return NULL;
@@ -316,24 +319,26 @@ char *enter_repo(char *path, int strict)
                         * anyway.
                         */
                        strcpy(used_path, newpath); free(newpath);
-                       strcpy(validated_path, path);
-                       path = used_path;
                }
                else if (PATH_MAX - 10 < len)
                        return NULL;
-               else {
-                       path = strcpy(used_path, path);
-                       strcpy(validated_path, path);
-               }
-               len = strlen(path);
+               len = strlen(used_path);
                for (i = 0; suffix[i]; i++) {
-                       strcpy(path + len, suffix[i]);
-                       if (!access(path, F_OK)) {
+                       struct stat st;
+                       strcpy(used_path + len, suffix[i]);
+                       if (!stat(used_path, &st) &&
+                           (S_ISREG(st.st_mode) ||
+                           (S_ISDIR(st.st_mode) && is_git_directory(used_path)))) {
                                strcat(validated_path, suffix[i]);
                                break;
                        }
                }
-               if (!suffix[i] || chdir(path))
+               if (!suffix[i])
+                       return NULL;
+               gitfile = read_gitfile(used_path) ;
+               if (gitfile)
+                       strcpy(used_path, gitfile);
+               if (chdir(used_path))
                        return NULL;
                path = validated_path;
        }
index a86ab709c25b5e110aa2708941cea560f82c1fa8..f7ce511bbbfbff5a479200f2814ad87b96f16791 100644 (file)
@@ -570,30 +570,10 @@ does. In scalar context requires the variable to be set only one time
 (exception is thrown otherwise), in array context returns allows the
 variable to be set multiple times and returns all the values.
 
-This currently wraps command('config') so it is not so fast.
-
 =cut
 
 sub config {
-       my ($self, $var) = _maybe_self(@_);
-
-       try {
-               my @cmd = ('config');
-               unshift @cmd, $self if $self;
-               if (wantarray) {
-                       return command(@cmd, '--get-all', $var);
-               } else {
-                       return command_oneline(@cmd, '--get', $var);
-               }
-       } catch Git::Error::Command with {
-               my $E = shift;
-               if ($E->value() == 1) {
-                       # Key not found.
-                       return;
-               } else {
-                       throw $E;
-               }
-       };
+       return _config_common({}, @_);
 }
 
 
@@ -603,30 +583,33 @@ Retrieve the bool configuration C<VARIABLE>. The return value
 is usable as a boolean in perl (and C<undef> if it's not defined,
 of course).
 
-This currently wraps command('config') so it is not so fast.
-
 =cut
 
 sub config_bool {
-       my ($self, $var) = _maybe_self(@_);
+       my $val = scalar _config_common({'kind' => '--bool'}, @_);
 
-       try {
-               my @cmd = ('config', '--bool', '--get', $var);
-               unshift @cmd, $self if $self;
-               my $val = command_oneline(@cmd);
-               return undef unless defined $val;
+       # Do not rewrite this as return (defined $val && $val eq 'true')
+       # as some callers do care what kind of falsehood they receive.
+       if (!defined $val) {
+               return undef;
+       } else {
                return $val eq 'true';
-       } catch Git::Error::Command with {
-               my $E = shift;
-               if ($E->value() == 1) {
-                       # Key not found.
-                       return undef;
-               } else {
-                       throw $E;
-               }
-       };
+       }
 }
 
+
+=item config_path ( VARIABLE )
+
+Retrieve the path configuration C<VARIABLE>. The return value
+is an expanded path or C<undef> if it's not defined.
+
+=cut
+
+sub config_path {
+       return _config_common({'kind' => '--path'}, @_);
+}
+
+
 =item config_int ( VARIABLE )
 
 Retrieve the integer configuration C<VARIABLE>. The return value
@@ -635,22 +618,31 @@ or 'g' in the config file will cause the value to be multiplied
 by 1024, 1048576 (1024^2), or 1073741824 (1024^3) prior to output.
 It would return C<undef> if configuration variable is not defined,
 
-This currently wraps command('config') so it is not so fast.
-
 =cut
 
 sub config_int {
+       return scalar _config_common({'kind' => '--int'}, @_);
+}
+
+# Common subroutine to implement bulk of what the config* family of methods
+# do. This curently wraps command('config') so it is not so fast.
+sub _config_common {
+       my ($opts) = shift @_;
        my ($self, $var) = _maybe_self(@_);
 
        try {
-               my @cmd = ('config', '--int', '--get', $var);
+               my @cmd = ('config', $opts->{'kind'} ? $opts->{'kind'} : ());
                unshift @cmd, $self if $self;
-               return command_oneline(@cmd);
+               if (wantarray) {
+                       return command(@cmd, '--get-all', $var);
+               } else {
+                       return command_oneline(@cmd, '--get', $var);
+               }
        } catch Git::Error::Command with {
                my $E = shift;
                if ($E->value() == 1) {
                        # Key not found.
-                       return undef;
+                       return;
                } else {
                        throw $E;
                }
diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm
new file mode 100644 (file)
index 0000000..07597dc
--- /dev/null
@@ -0,0 +1,89 @@
+package Git::I18N;
+use 5.008;
+use strict;
+use warnings;
+use Exporter 'import';
+
+our @EXPORT = qw(__);
+our @EXPORT_OK = @EXPORT;
+
+sub __bootstrap_locale_messages {
+       our $TEXTDOMAIN = 'git';
+       our $TEXTDOMAINDIR = $ENV{GIT_TEXTDOMAINDIR} || '++LOCALEDIR++';
+
+       require POSIX;
+       POSIX->import(qw(setlocale));
+       # Non-core prerequisite module
+       require Locale::Messages;
+       Locale::Messages->import(qw(:locale_h :libintl_h));
+
+       setlocale(LC_MESSAGES(), '');
+       setlocale(LC_CTYPE(), '');
+       textdomain($TEXTDOMAIN);
+       bindtextdomain($TEXTDOMAIN => $TEXTDOMAINDIR);
+
+       return;
+}
+
+BEGIN
+{
+       # Used by our test script to see if it should test fallbacks or
+       # not.
+       our $__HAS_LIBRARY = 1;
+
+       local $@;
+       eval {
+               __bootstrap_locale_messages();
+               *__ = \&Locale::Messages::gettext;
+               1;
+       } or do {
+               # Tell test.pl that we couldn't load the gettext library.
+               $Git::I18N::__HAS_LIBRARY = 0;
+
+               # Just a fall-through no-op
+               *__ = sub ($) { $_[0] };
+       };
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Git::I18N - Perl interface to Git's Gettext localizations
+
+=head1 SYNOPSIS
+
+       use Git::I18N;
+
+       print __("Welcome to Git!\n");
+
+       printf __("The following error occured: %s\n"), $error;
+
+=head1 DESCRIPTION
+
+Git's internal Perl interface to gettext via L<Locale::Messages>. If
+L<Locale::Messages> can't be loaded (it's not a core module) we
+provide stub passthrough fallbacks.
+
+This is a distilled interface to gettext, see C<info '(gettext)Perl'>
+for the full interface. This module implements only a small part of
+it.
+
+=head1 FUNCTIONS
+
+=head2 __($)
+
+L<Locale::Messages>'s gettext function if all goes well, otherwise our
+passthrough fallback function.
+
+=head1 AUTHOR
+
+E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=head1 COPYRIGHT
+
+Copyright 2010 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=cut
index a2ffb6402d45420dff4dcd545dfa08b57305d8cd..b2977cd0bc8f23d75a228ca13d6cb42e1c72628f 100644 (file)
@@ -5,6 +5,7 @@ makfile:=perl.mak
 
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 prefix_SQ = $(subst ','\'',$(prefix))
+localedir_SQ = $(subst ','\'',$(localedir))
 
 ifndef V
        QUIET = @
@@ -38,7 +39,7 @@ $(makfile): ../GIT-CFLAGS Makefile
        echo '  echo $(instdir_SQ)' >> $@
 else
 $(makfile): Makefile.PL ../GIT-CFLAGS
-       $(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE=''
+       $(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE='' --localedir='$(localedir_SQ)'
 endif
 
 # this is just added comfort for calling make directly in perl dir
index 0b9deca2cc6ef77897a23b2096a7acdd577c2482..456d45bf4092467e290ce478ceb8032938a01aac 100644 (file)
@@ -1,4 +1,12 @@
+use strict;
+use warnings;
 use ExtUtils::MakeMaker;
+use Getopt::Long;
+
+# Sanity: die at first unknown option
+Getopt::Long::Configure qw/ pass_through /;
+
+GetOptions("localedir=s" => \my $localedir);
 
 sub MY::postamble {
        return <<'MAKE_FRAG';
@@ -16,7 +24,10 @@ endif
 MAKE_FRAG
 }
 
-my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
+my %pm = (
+       'Git.pm' => '$(INST_LIBDIR)/Git.pm',
+       'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',
+);
 
 # We come with our own bundled Error.pm. It's not in the set of default
 # Perl modules so install it if it's not available on the system yet.
@@ -33,6 +44,7 @@ WriteMakefile(
        NAME            => 'Git',
        VERSION_FROM    => 'Git.pm',
        PM              => \%pm,
+       PM_FILTER       => qq[\$(PERL) -pe "s<\\Q++LOCALEDIR++\\E><$localedir>"],
        MAKEFILE        => 'perl.mak',
        INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3'
 );
index a242a86e9376402cefa9dce7cee266a510a1ab80..4caa631ff020e0e81d0e5507eef085c7feca9912 100644 (file)
@@ -1 +1,2 @@
 /git.pot
+/build
diff --git a/po/README b/po/README
new file mode 100644 (file)
index 0000000..10b0ad2
--- /dev/null
+++ b/po/README
@@ -0,0 +1,229 @@
+Core GIT Translations
+=====================
+
+This directory holds the translations for the core of Git. This
+document describes how to add to and maintain these translations, and
+how to mark source strings for translation.
+
+
+Generating a .pot file
+----------------------
+
+The po/git.pot file contains a message catalog extracted from Git's
+sources. You need to generate it to add new translations with
+msginit(1), or update existing ones with msgmerge(1).
+
+Since the file can be automatically generated it's not checked into
+git.git. To generate it do, at the top-level:
+
+    make pot
+
+
+Initializing a .po file
+-----------------------
+
+To add a new translation first generate git.pot (see above) and then
+in the po/ directory do:
+
+    msginit --locale=XX
+
+Where XX is your locale, e.g. "is", "de" or "pt_BR".
+
+Then edit the automatically generated copyright info in your new XX.po
+to be correct, e.g. for Icelandic:
+
+    @@ -1,6 +1,6 @@
+    -# Icelandic translations for PACKAGE package.
+    -# Copyright (C) 2010 THE PACKAGE'S COPYRIGHT HOLDER
+    -# This file is distributed under the same license as the PACKAGE package.
+    +# Icelandic translations for Git.
+    +# Copyright (C) 2010 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+    +# This file is distributed under the same license as the Git package.
+     # Ævar Arnfjörð Bjarmason <avarab@gmail.com>, 2010.
+
+And change references to PACKAGE VERSION in the PO Header Entry to
+just "Git":
+
+    perl -pi -e 's/(?<="Project-Id-Version: )PACKAGE VERSION/Git/' XX.po
+
+
+Updating a .po file
+-------------------
+
+If there's an existing *.po file for your language but you need to
+update the translation you first need to generate git.pot (see above)
+and then in the po/ directory do:
+
+    msgmerge --add-location --backup=off -U XX.po git.pot
+
+Where XX.po is the file you want to update.
+
+Testing your changes
+--------------------
+
+Before you submit your changes go back to the top-level and do:
+
+    make
+
+On systems with GNU gettext (i.e. not Solaris) this will compile your
+changed PO file with `msgfmt --check`, the --check option flags many
+common errors, e.g. missing printf format strings, or translated
+messages that deviate from the originals in whether they begin/end
+with a newline or not.
+
+
+Marking strings for translation
+-------------------------------
+
+Before strings can be translated they first have to be marked for
+translation.
+
+Git uses an internationalization interface that wraps the system's
+gettext library, so most of the advice in your gettext documentation
+(on GNU systems `info gettext` in a terminal) applies.
+
+General advice:
+
+ - Don't mark everything for translation, only strings which will be
+   read by humans (the porcelain interface) should be translated.
+
+   The output from Git's plumbing utilities will primarily be read by
+   programs and would break scripts under non-C locales if it was
+   translated. Plumbing strings should not be translated, since
+   they're part of Git's API.
+
+ - Adjust the strings so that they're easy to translate. Most of the
+   advice in `info '(gettext)Preparing Strings'` applies here.
+
+ - If something is unclear or ambiguous you can use a "TRANSLATORS"
+   comment to tell the translators what to make of it. These will be
+   extracted by xgettext(1) and put in the po/*.po files, e.g. from
+   git-am.sh:
+
+       # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+       # in your translation. The program will only accept English
+       # input at this point.
+       gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+
+   Or in C, from builtin/revert.c:
+
+       /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
+       die(_("%s: Unable to write new index file"), action_name(opts));
+
+We provide wrappers for C, Shell and Perl programs. Here's how they're
+used:
+
+C:
+
+ - Include builtin.h at the top, it'll pull in in gettext.h, which
+   defines the gettext interface. Consult with the list if you need to
+   use gettext.h directly.
+
+ - The C interface is a subset of the normal GNU gettext
+   interface. We currently export these functions:
+
+   - _()
+
+    Mark and translate a string. E.g.:
+
+        printf(_("HEAD is now at %s"), hex);
+
+   - Q_()
+
+    Mark and translate a plural string. E.g.:
+
+        printf(Q_("%d commit", "%d commits", number_of_commits));
+
+    This is just a wrapper for the ngettext() function.
+
+   - N_()
+
+    A no-op pass-through macro for marking strings inside static
+    initializations, e.g.:
+
+        static const char *reset_type_names[] = {
+            N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL
+        };
+
+    And then, later:
+
+        die(_("%s reset is not allowed in a bare repository"),
+               _(reset_type_names[reset_type]));
+
+    Here _() couldn't have statically determined what the translation
+    string will be, but since it was already marked for translation
+    with N_() the look-up in the message catalog will succeed.
+
+Shell:
+
+ - The Git gettext shell interface is just a wrapper for
+   gettext.sh. Import it right after git-sh-setup like this:
+
+       . git-sh-setup
+       . git-sh-i18n
+
+   And then use the gettext or eval_gettext functions:
+
+       # For constant interface messages:
+       gettext "A message for the user"; echo
+
+       # To interpolate variables:
+       details="oh noes"
+       eval_gettext "An error occured: \$details"; echo
+
+   In addition we have wrappers for messages that end with a trailing
+   newline. I.e. you could write the above as:
+
+       # For constant interface messages:
+       gettextln "A message for the user"
+
+       # To interpolate variables:
+       details="oh noes"
+       eval_gettextln "An error occured: \$details"
+
+   More documentation about the interface is available in the GNU info
+   page: `info '(gettext)sh'`. Looking at git-am.sh (the first shell
+   command to be translated) for examples is also useful:
+
+       git log --reverse -p --grep=i18n git-am.sh
+
+Perl:
+
+ - The Git::I18N module provides a limited subset of the
+   Locale::Messages functionality, e.g.:
+
+       use Git::I18N;
+       print __("Welcome to Git!\n");
+       printf __("The following error occured: %s\n"), $error;
+
+   Run `perldoc perl/Git/I18N.pm` for more info.
+
+
+Testing marked strings
+----------------------
+
+Even if you've correctly marked porcelain strings for translation
+something in the test suite might still depend on the US English
+version of the strings, e.g. to grep some error message or other
+output.
+
+To smoke out issues like these Git can be compiled with gettext poison
+support, at the top-level:
+
+    make GETTEXT_POISON=YesPlease
+
+That'll give you a git which emits gibberish on every call to
+gettext. It's obviously not meant to be installed, but you should run
+the test suite with it:
+
+    cd t && prove -j 9 ./t[0-9]*.sh
+
+If tests break with it you should inspect them manually and see if
+what you're translating is sane, i.e. that you're not translating
+plumbing output.
+
+If not you should replace calls to grep with test_i18ngrep, or
+test_cmp calls with test_i18ncmp. If that's not enough you can skip
+the whole test by making it depend on the C_LOCALE_OUTPUT
+prerequisite. See existing test files with this prerequisite for
+examples.
diff --git a/po/is.po b/po/is.po
new file mode 100644 (file)
index 0000000..8692a8b
--- /dev/null
+++ b/po/is.po
@@ -0,0 +1,93 @@
+# Icelandic translations for Git.
+# Copyright (C) 2010 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+# This file is distributed under the same license as the Git package.
+# Ævar Arnfjörð Bjarmason <avarab@gmail.com>, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"POT-Creation-Date: 2010-09-20 14:44+0000\n"
+"PO-Revision-Date: 2010-06-05 19:06 +0000\n"
+"Last-Translator: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n"
+"Language-Team: Git Mailing List <git@vger.kernel.org>\n"
+"Language: is\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:5
+msgid "See 'git help COMMAND' for more information on a specific command."
+msgstr "Sjá 'git help SKIPUN' til að sjá hjálp fyrir tiltekna skipun."
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:10
+msgid "TEST: A C test string"
+msgstr "TILRAUN: C tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:13
+#, c-format
+msgid "TEST: A C test string %s"
+msgstr "TILRAUN: C tilraunastrengur %s"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:16
+#, c-format
+msgid "TEST: Hello World!"
+msgstr "TILRAUN: Halló Heimur!"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:19
+#, c-format
+msgid "TEST: Old English Runes"
+msgstr "TILRAUN: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:22
+#, c-format
+msgid "TEST: ‘single’ and “double” quotes"
+msgstr "TILRAUN: ‚einfaldar‘ og „tvöfaldar“ gæsalappir"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:8
+msgid "TEST: A Shell test string"
+msgstr "TILRAUN: Skeljartilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:11
+#, sh-format
+msgid "TEST: A Shell test $variable"
+msgstr "TILRAUN: Skeljartilraunastrengur með breytunni $variable"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:8
+msgid "TEST: A Perl test string"
+msgstr "TILRAUN: Perl tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:11
+#, perl-format
+msgid "TEST: A Perl test variable %s"
+msgstr "TILRAUN: Perl tilraunastrengur með breytunni %s"
+
+#. TRANSLATORS: The first '%s' is either "Reinitialized
+#. existing" or "Initialized empty", the second " shared" or
+#. "", and the last '%s%s' is the verbatim directory name.
+#: builtin/init-db.c:355
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr "%s%s Git lind í %s%s\n"
+
+#: builtin/init-db.c:356
+msgid "Reinitialized existing"
+msgstr "Endurgerði"
+
+#: builtin/init-db.c:356
+msgid "Initialized empty"
+msgstr "Bjó til tóma"
+
+#: builtin/init-db.c:357
+msgid " shared"
+msgstr " sameiginlega"
index f45eb54e4c99b8d67e4aa85f9a6218ea7a560592..8688b8f2d45a493aa8b51b29f7d46b2abff7f30e 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -9,6 +9,7 @@
 #include "notes.h"
 #include "color.h"
 #include "reflog-walk.h"
+#include "gpg-interface.h"
 
 static char *user_format;
 static struct cmt_fmt_map {
@@ -640,6 +641,12 @@ struct format_commit_context {
        const struct pretty_print_context *pretty_ctx;
        unsigned commit_header_parsed:1;
        unsigned commit_message_parsed:1;
+       unsigned commit_signature_parsed:1;
+       struct {
+               char *gpg_output;
+               char good_bad;
+               char *signer;
+       } signature;
        char *message;
        size_t width, indent1, indent2;
 
@@ -822,6 +829,76 @@ static void rewrap_message_tail(struct strbuf *sb,
        c->indent2 = new_indent2;
 }
 
+static struct {
+       char result;
+       const char *check;
+} signature_check[] = {
+       { 'G', ": Good signature from " },
+       { 'B', ": BAD signature from " },
+};
+
+static void parse_signature_lines(struct format_commit_context *ctx)
+{
+       const char *buf = ctx->signature.gpg_output;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
+               const char *found = strstr(buf, signature_check[i].check);
+               const char *next;
+               if (!found)
+                       continue;
+               ctx->signature.good_bad = signature_check[i].result;
+               found += strlen(signature_check[i].check);
+               next = strchrnul(found, '\n');
+               ctx->signature.signer = xmemdupz(found, next - found);
+               break;
+       }
+}
+
+static void parse_commit_signature(struct format_commit_context *ctx)
+{
+       struct strbuf payload = STRBUF_INIT;
+       struct strbuf signature = STRBUF_INIT;
+       struct strbuf gpg_output = STRBUF_INIT;
+       int status;
+
+       ctx->commit_signature_parsed = 1;
+
+       if (parse_signed_commit(ctx->commit->object.sha1,
+                               &payload, &signature) <= 0)
+               goto out;
+       status = verify_signed_buffer(payload.buf, payload.len,
+                                     signature.buf, signature.len,
+                                     &gpg_output);
+       if (status && !gpg_output.len)
+               goto out;
+       ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
+       parse_signature_lines(ctx);
+
+ out:
+       strbuf_release(&gpg_output);
+       strbuf_release(&payload);
+       strbuf_release(&signature);
+}
+
+
+static int format_reflog_person(struct strbuf *sb,
+                               char part,
+                               struct reflog_walk_info *log,
+                               enum date_mode dmode)
+{
+       const char *ident;
+
+       if (!log)
+               return 2;
+
+       ident = get_reflog_ident(log);
+       if (!ident)
+               return 2;
+
+       return format_person_part(sb, part, ident, strlen(ident), dmode);
+}
+
 static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
                                void *context)
 {
@@ -963,6 +1040,14 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
                        if (c->pretty_ctx->reflog_info)
                                get_reflog_message(sb, c->pretty_ctx->reflog_info);
                        return 2;
+               case 'n':
+               case 'N':
+               case 'e':
+               case 'E':
+                       return format_reflog_person(sb,
+                                                   placeholder[1],
+                                                   c->pretty_ctx->reflog_info,
+                                                   c->pretty_ctx->date_mode);
                }
                return 0;       /* unknown %g placeholder */
        case 'N':
@@ -974,6 +1059,30 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
                return 0;
        }
 
+       if (placeholder[0] == 'G') {
+               if (!c->commit_signature_parsed)
+                       parse_commit_signature(c);
+               switch (placeholder[1]) {
+               case 'G':
+                       if (c->signature.gpg_output)
+                               strbuf_addstr(sb, c->signature.gpg_output);
+                       break;
+               case '?':
+                       switch (c->signature.good_bad) {
+                       case 'G':
+                       case 'B':
+                               strbuf_addch(sb, c->signature.good_bad);
+                       }
+                       break;
+               case 'S':
+                       if (c->signature.signer)
+                               strbuf_addstr(sb, c->signature.signer);
+                       break;
+               }
+               return 2;
+       }
+
+
        /* For the rest we have to parse the commit header. */
        if (!c->commit_header_parsed)
                parse_commit_header(c);
@@ -1094,7 +1203,6 @@ void format_commit_message(const struct commit *commit,
 {
        struct format_commit_context context;
        static const char utf8[] = "UTF-8";
-       const char *enc;
        const char *output_enc = pretty_ctx->output_encoding;
 
        memset(&context, 0, sizeof(context));
@@ -1103,10 +1211,13 @@ void format_commit_message(const struct commit *commit,
        context.wrap_start = sb->len;
        context.message = commit->buffer;
        if (output_enc) {
-               enc = get_header(commit, "encoding");
-               enc = enc ? enc : utf8;
-               if (strcmp(enc, output_enc))
+               char *enc = get_header(commit, "encoding");
+               if (strcmp(enc ? enc : utf8, output_enc)) {
                        context.message = logmsg_reencode(commit, output_enc);
+                       if (!context.message)
+                               context.message = commit->buffer;
+               }
+               free(enc);
        }
 
        strbuf_expand(sb, format, format_commit_item, &context);
@@ -1114,6 +1225,8 @@ void format_commit_message(const struct commit *commit,
 
        if (context.message != commit->buffer)
                free(context.message);
+       free(context.signature.gpg_output);
+       free(context.signature.signer);
 }
 
 static void pp_header(const struct pretty_print_context *pp,
diff --git a/prompt.c b/prompt.c
new file mode 100644 (file)
index 0000000..d851807
--- /dev/null
+++ b/prompt.c
@@ -0,0 +1,72 @@
+#include "cache.h"
+#include "run-command.h"
+#include "strbuf.h"
+#include "prompt.h"
+#include "compat/terminal.h"
+
+static char *do_askpass(const char *cmd, const char *prompt)
+{
+       struct child_process pass;
+       const char *args[3];
+       static struct strbuf buffer = STRBUF_INIT;
+       int err = 0;
+
+       args[0] = cmd;
+       args[1] = prompt;
+       args[2] = NULL;
+
+       memset(&pass, 0, sizeof(pass));
+       pass.argv = args;
+       pass.out = -1;
+
+       if (start_command(&pass))
+               return NULL;
+
+       if (strbuf_read(&buffer, pass.out, 20) < 0)
+               err = 1;
+
+       close(pass.out);
+
+       if (finish_command(&pass))
+               err = 1;
+
+       if (err) {
+               error("unable to read askpass response from '%s'", cmd);
+               strbuf_release(&buffer);
+               return NULL;
+       }
+
+       strbuf_setlen(&buffer, strcspn(buffer.buf, "\r\n"));
+
+       return strbuf_detach(&buffer, NULL);
+}
+
+char *git_prompt(const char *prompt, int flags)
+{
+       char *r = NULL;
+
+       if (flags & PROMPT_ASKPASS) {
+               const char *askpass;
+
+               askpass = getenv("GIT_ASKPASS");
+               if (!askpass)
+                       askpass = askpass_program;
+               if (!askpass)
+                       askpass = getenv("SSH_ASKPASS");
+               if (askpass && *askpass)
+                       r = do_askpass(askpass, prompt);
+       }
+
+       if (!r)
+               r = git_terminal_prompt(prompt, flags & PROMPT_ECHO);
+       if (!r) {
+               /* prompts already contain ": " at the end */
+               die("could not read %s%s", prompt, strerror(errno));
+       }
+       return r;
+}
+
+char *git_getpass(const char *prompt)
+{
+       return git_prompt(prompt, PROMPT_ASKPASS);
+}
diff --git a/prompt.h b/prompt.h
new file mode 100644 (file)
index 0000000..04f321a
--- /dev/null
+++ b/prompt.h
@@ -0,0 +1,10 @@
+#ifndef PROMPT_H
+#define PROMPT_H
+
+#define PROMPT_ASKPASS (1<<0)
+#define PROMPT_ECHO    (1<<1)
+
+char *git_prompt(const char *prompt, int flags);
+char *git_getpass(const char *prompt);
+
+#endif /* PROMPT_H */
index 3fc6b1d320faad3a528db016a0d800ff1bdde4f0..bf7970661f26664e461ed5d6fd7a1982f7e8fadd 100644 (file)
@@ -7,11 +7,25 @@
 #include "revision.h"
 #include "reachable.h"
 #include "cache-tree.h"
+#include "progress.h"
+
+struct connectivity_progress {
+       struct progress *progress;
+       unsigned long count;
+};
+
+static void update_progress(struct connectivity_progress *cp)
+{
+       cp->count++;
+       if ((cp->count & 1023) == 0)
+               display_progress(cp->progress, cp->count);
+}
 
 static void process_blob(struct blob *blob,
                         struct object_array *p,
                         struct name_path *path,
-                        const char *name)
+                        const char *name,
+                        struct connectivity_progress *cp)
 {
        struct object *obj = &blob->object;
 
@@ -20,6 +34,7 @@ static void process_blob(struct blob *blob,
        if (obj->flags & SEEN)
                return;
        obj->flags |= SEEN;
+       update_progress(cp);
        /* Nothing to do, really .. The blob lookup was the important part */
 }
 
@@ -34,7 +49,8 @@ static void process_gitlink(const unsigned char *sha1,
 static void process_tree(struct tree *tree,
                         struct object_array *p,
                         struct name_path *path,
-                        const char *name)
+                        const char *name,
+                        struct connectivity_progress *cp)
 {
        struct object *obj = &tree->object;
        struct tree_desc desc;
@@ -46,6 +62,7 @@ static void process_tree(struct tree *tree,
        if (obj->flags & SEEN)
                return;
        obj->flags |= SEEN;
+       update_progress(cp);
        if (parse_tree(tree) < 0)
                die("bad tree object %s", sha1_to_hex(obj->sha1));
        add_object(obj, p, path, name);
@@ -57,23 +74,25 @@ static void process_tree(struct tree *tree,
 
        while (tree_entry(&desc, &entry)) {
                if (S_ISDIR(entry.mode))
-                       process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
+                       process_tree(lookup_tree(entry.sha1), p, &me, entry.path, cp);
                else if (S_ISGITLINK(entry.mode))
                        process_gitlink(entry.sha1, p, &me, entry.path);
                else
-                       process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
+                       process_blob(lookup_blob(entry.sha1), p, &me, entry.path, cp);
        }
        free(tree->buffer);
        tree->buffer = NULL;
 }
 
-static void process_tag(struct tag *tag, struct object_array *p, const char *name)
+static void process_tag(struct tag *tag, struct object_array *p,
+                       const char *name, struct connectivity_progress *cp)
 {
        struct object *obj = &tag->object;
 
        if (obj->flags & SEEN)
                return;
        obj->flags |= SEEN;
+       update_progress(cp);
 
        if (parse_tag(tag) < 0)
                die("bad tag object %s", sha1_to_hex(obj->sha1));
@@ -81,15 +100,18 @@ static void process_tag(struct tag *tag, struct object_array *p, const char *nam
                add_object(tag->tagged, p, NULL, name);
 }
 
-static void walk_commit_list(struct rev_info *revs)
+static void walk_commit_list(struct rev_info *revs,
+                            struct connectivity_progress *cp)
 {
        int i;
        struct commit *commit;
        struct object_array objects = OBJECT_ARRAY_INIT;
 
        /* Walk all commits, process their trees */
-       while ((commit = get_revision(revs)) != NULL)
-               process_tree(commit->tree, &objects, NULL, "");
+       while ((commit = get_revision(revs)) != NULL) {
+               process_tree(commit->tree, &objects, NULL, "", cp);
+               update_progress(cp);
+       }
 
        /* Then walk all the pending objects, recursively processing them too */
        for (i = 0; i < revs->pending.nr; i++) {
@@ -97,15 +119,15 @@ static void walk_commit_list(struct rev_info *revs)
                struct object *obj = pending->item;
                const char *name = pending->name;
                if (obj->type == OBJ_TAG) {
-                       process_tag((struct tag *) obj, &objects, name);
+                       process_tag((struct tag *) obj, &objects, name, cp);
                        continue;
                }
                if (obj->type == OBJ_TREE) {
-                       process_tree((struct tree *)obj, &objects, NULL, name);
+                       process_tree((struct tree *)obj, &objects, NULL, name, cp);
                        continue;
                }
                if (obj->type == OBJ_BLOB) {
-                       process_blob((struct blob *)obj, &objects, NULL, name);
+                       process_blob((struct blob *)obj, &objects, NULL, name, cp);
                        continue;
                }
                die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
@@ -191,8 +213,11 @@ static void add_cache_refs(struct rev_info *revs)
                add_cache_tree(active_cache_tree, revs);
 }
 
-void mark_reachable_objects(struct rev_info *revs, int mark_reflog)
+void mark_reachable_objects(struct rev_info *revs, int mark_reflog,
+                           struct progress *progress)
 {
+       struct connectivity_progress cp;
+
        /*
         * Set up revision parsing, and mark us as being interested
         * in all object types, not just commits.
@@ -211,11 +236,15 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog)
        if (mark_reflog)
                for_each_reflog(add_one_reflog, revs);
 
+       cp.progress = progress;
+       cp.count = 0;
+
        /*
         * Set up the revision walk - this will move all commits
         * from the pending list to the commit walking list.
         */
        if (prepare_revision_walk(revs))
                die("revision walk setup failed");
-       walk_commit_list(revs);
+       walk_commit_list(revs, &cp);
+       display_progress(cp.progress, cp.count);
 }
index 40751810b64f8bbf9c0a633472a0ef27d23ed1a5..5d082adfecc47c40074212df2ddedc00d8a5ecb1 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef REACHEABLE_H
 #define REACHEABLE_H
 
-extern void mark_reachable_objects(struct rev_info *revs, int mark_reflog);
+struct progress;
+extern void mark_reachable_objects(struct rev_info *revs, int mark_reflog, struct progress *);
 
 #endif
index 5790a91044e4fdf5b2eec515051a66c110e0daa4..274e54b4f31da69bf7c0721d4c8ba8e264db5dde 100644 (file)
@@ -1001,7 +1001,8 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti
  */
 static struct cache_entry *refresh_cache_ent(struct index_state *istate,
                                             struct cache_entry *ce,
-                                            unsigned int options, int *err)
+                                            unsigned int options, int *err,
+                                            int *changed_ret)
 {
        struct stat st;
        struct cache_entry *updated;
@@ -1033,6 +1034,8 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
        }
 
        changed = ie_match_stat(istate, ce, &st, options);
+       if (changed_ret)
+               *changed_ret = changed;
        if (!changed) {
                /*
                 * The path is unchanged.  If we were told to ignore
@@ -1102,19 +1105,31 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
        int first = 1;
        int in_porcelain = (flags & REFRESH_IN_PORCELAIN);
        unsigned int options = really ? CE_MATCH_IGNORE_VALID : 0;
-       const char *needs_update_fmt;
-       const char *needs_merge_fmt;
-
-       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");
+       const char *modified_fmt;
+       const char *deleted_fmt;
+       const char *typechange_fmt;
+       const char *added_fmt;
+       const char *unmerged_fmt;
+
+       modified_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n");
+       deleted_fmt = (in_porcelain ? "D\t%s\n" : "%s: needs update\n");
+       typechange_fmt = (in_porcelain ? "T\t%s\n" : "%s needs update\n");
+       added_fmt = (in_porcelain ? "A\t%s\n" : "%s needs update\n");
+       unmerged_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;
+               int changed = 0;
+               int filtered = 0;
 
                ce = istate->cache[i];
                if (ignore_submodules && S_ISGITLINK(ce->ce_mode))
                        continue;
 
+               if (pathspec &&
+                   !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen))
+                       filtered = 1;
+
                if (ce_stage(ce)) {
                        while ((i < istate->cache_nr) &&
                               ! strcmp(istate->cache[i]->name, ce->name))
@@ -1122,18 +1137,22 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
                        i--;
                        if (allow_unmerged)
                                continue;
-                       show_file(needs_merge_fmt, ce->name, in_porcelain, &first, header_msg);
+                       if (!filtered)
+                               show_file(unmerged_fmt, ce->name, in_porcelain,
+                                         &first, header_msg);
                        has_errors = 1;
                        continue;
                }
 
-               if (pathspec && !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen))
+               if (filtered)
                        continue;
 
-               new = refresh_cache_ent(istate, ce, options, &cache_errno);
+               new = refresh_cache_ent(istate, ce, options, &cache_errno, &changed);
                if (new == ce)
                        continue;
                if (!new) {
+                       const char *fmt;
+
                        if (not_new && cache_errno == ENOENT)
                                continue;
                        if (really && cache_errno == EINVAL) {
@@ -1145,7 +1164,17 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
                        }
                        if (quiet)
                                continue;
-                       show_file(needs_update_fmt, ce->name, in_porcelain, &first, header_msg);
+
+                       if (cache_errno == ENOENT)
+                               fmt = deleted_fmt;
+                       else if (ce->ce_flags & CE_INTENT_TO_ADD)
+                               fmt = added_fmt; /* must be before other checks */
+                       else if (changed & TYPE_CHANGED)
+                               fmt = typechange_fmt;
+                       else
+                               fmt = modified_fmt;
+                       show_file(fmt,
+                                 ce->name, in_porcelain, &first, header_msg);
                        has_errors = 1;
                        continue;
                }
@@ -1157,7 +1186,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
 
 static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
 {
-       return refresh_cache_ent(&the_index, ce, really, NULL);
+       return refresh_cache_ent(&the_index, ce, really, NULL, NULL);
 }
 
 static int verify_hdr(struct cache_header *hdr, unsigned long size)
@@ -1202,29 +1231,18 @@ int read_index(struct index_state *istate)
        return read_index_from(istate, get_index_file());
 }
 
-static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_entry *ce)
+static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk)
 {
+       struct cache_entry *ce;
        size_t len;
        const char *name;
+       unsigned int flags;
 
-       ce->ce_ctime.sec = ntohl(ondisk->ctime.sec);
-       ce->ce_mtime.sec = ntohl(ondisk->mtime.sec);
-       ce->ce_ctime.nsec = ntohl(ondisk->ctime.nsec);
-       ce->ce_mtime.nsec = ntohl(ondisk->mtime.nsec);
-       ce->ce_dev   = ntohl(ondisk->dev);
-       ce->ce_ino   = ntohl(ondisk->ino);
-       ce->ce_mode  = ntohl(ondisk->mode);
-       ce->ce_uid   = ntohl(ondisk->uid);
-       ce->ce_gid   = ntohl(ondisk->gid);
-       ce->ce_size  = ntohl(ondisk->size);
        /* On-disk flags are just 16 bits */
-       ce->ce_flags = ntohs(ondisk->flags);
-
-       hashcpy(ce->sha1, ondisk->sha1);
+       flags = ntohs(ondisk->flags);
+       len = flags & CE_NAMEMASK;
 
-       len = ce->ce_flags & CE_NAMEMASK;
-
-       if (ce->ce_flags & CE_EXTENDED) {
+       if (flags & CE_EXTENDED) {
                struct ondisk_cache_entry_extended *ondisk2;
                int extended_flags;
                ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
@@ -1232,7 +1250,7 @@ static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_en
                /* We do not yet understand any bit out of CE_EXTENDED_FLAGS */
                if (extended_flags & ~CE_EXTENDED_FLAGS)
                        die("Unknown index entry format %08x", extended_flags);
-               ce->ce_flags |= extended_flags;
+               flags |= extended_flags;
                name = ondisk2->name;
        }
        else
@@ -1240,25 +1258,26 @@ static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_en
 
        if (len == CE_NAMEMASK)
                len = strlen(name);
-       /*
-        * NEEDSWORK: If the original index is crafted, this copy could
-        * go unchecked.
-        */
-       memcpy(ce->name, name, len + 1);
-}
 
-static inline size_t estimate_cache_size(size_t ondisk_size, unsigned int entries)
-{
-       size_t fix_size_mem = offsetof(struct cache_entry, name);
-       size_t fix_size_dsk = offsetof(struct ondisk_cache_entry, name);
-       long per_entry = (fix_size_mem - fix_size_dsk + 7) & ~7;
+       ce = xmalloc(cache_entry_size(len));
 
-       /*
-        * Alignment can cause differences. This should be "alignof", but
-        * since that's a gcc'ism, just use the size of a pointer.
-        */
-       per_entry += sizeof(void *);
-       return ondisk_size + entries*per_entry;
+       ce->ce_ctime.sec = ntohl(ondisk->ctime.sec);
+       ce->ce_mtime.sec = ntohl(ondisk->mtime.sec);
+       ce->ce_ctime.nsec = ntohl(ondisk->ctime.nsec);
+       ce->ce_mtime.nsec = ntohl(ondisk->mtime.nsec);
+       ce->ce_dev   = ntohl(ondisk->dev);
+       ce->ce_ino   = ntohl(ondisk->ino);
+       ce->ce_mode  = ntohl(ondisk->mode);
+       ce->ce_uid   = ntohl(ondisk->uid);
+       ce->ce_gid   = ntohl(ondisk->gid);
+       ce->ce_size  = ntohl(ondisk->size);
+       ce->ce_flags = flags;
+
+       hashcpy(ce->sha1, ondisk->sha1);
+
+       memcpy(ce->name, name, len);
+       ce->name[len] = '\0';
+       return ce;
 }
 
 /* remember to discard_cache() before reading a different cache! */
@@ -1266,7 +1285,7 @@ int read_index_from(struct index_state *istate, const char *path)
 {
        int fd, i;
        struct stat st;
-       unsigned long src_offset, dst_offset;
+       unsigned long src_offset;
        struct cache_header *hdr;
        void *mmap;
        size_t mmap_size;
@@ -1305,29 +1324,18 @@ int read_index_from(struct index_state *istate, const char *path)
        istate->cache_nr = ntohl(hdr->hdr_entries);
        istate->cache_alloc = alloc_nr(istate->cache_nr);
        istate->cache = xcalloc(istate->cache_alloc, sizeof(struct cache_entry *));
-
-       /*
-        * The disk format is actually larger than the in-memory format,
-        * due to space for nsec etc, so even though the in-memory one
-        * has room for a few  more flags, we can allocate using the same
-        * index size
-        */
-       istate->alloc = xmalloc(estimate_cache_size(mmap_size, istate->cache_nr));
        istate->initialized = 1;
 
        src_offset = sizeof(*hdr);
-       dst_offset = 0;
        for (i = 0; i < istate->cache_nr; i++) {
                struct ondisk_cache_entry *disk_ce;
                struct cache_entry *ce;
 
                disk_ce = (struct ondisk_cache_entry *)((char *)mmap + src_offset);
-               ce = (struct cache_entry *)((char *)istate->alloc + dst_offset);
-               convert_from_disk(disk_ce, ce);
+               ce = create_from_disk(disk_ce);
                set_index_entry(istate, i, ce);
 
                src_offset += ondisk_ce_size(ce);
-               dst_offset += ce_size(ce);
        }
        istate->timestamp.sec = st.st_mtime;
        istate->timestamp.nsec = ST_MTIME_NSEC(st);
@@ -1361,11 +1369,15 @@ unmap:
 
 int is_index_unborn(struct index_state *istate)
 {
-       return (!istate->cache_nr && !istate->alloc && !istate->timestamp.sec);
+       return (!istate->cache_nr && !istate->timestamp.sec);
 }
 
 int discard_index(struct index_state *istate)
 {
+       int i;
+
+       for (i = 0; i < istate->cache_nr; i++)
+               free(istate->cache[i]);
        resolve_undo_clear_index(istate);
        istate->cache_nr = 0;
        istate->cache_changed = 0;
@@ -1374,8 +1386,6 @@ int discard_index(struct index_state *istate)
        istate->name_hash_initialized = 0;
        free_hash(&istate->name_hash);
        cache_tree_free(&(istate->cache_tree));
-       free(istate->alloc);
-       istate->alloc = NULL;
        istate->initialized = 0;
 
        /* no need to throw away allocated active_cache */
index 5d81d39a525830f6bacba88143ab6a4552748441..86d18843f52046d87741bffa9f865ec973a2ae73 100644 (file)
@@ -50,9 +50,13 @@ static struct complete_reflogs *read_complete_reflog(const char *ref)
        for_each_reflog_ent(ref, read_one_reflog, reflogs);
        if (reflogs->nr == 0) {
                unsigned char sha1[20];
-               const char *name = resolve_ref(ref, sha1, 1, NULL);
-               if (name)
+               const char *name;
+               void *name_to_free;
+               name = name_to_free = resolve_refdup(ref, sha1, 1, NULL);
+               if (name) {
                        for_each_reflog_ent(name, read_one_reflog, reflogs);
+                       free(name_to_free);
+               }
        }
        if (reflogs->nr == 0) {
                int len = strlen(ref);
@@ -168,11 +172,11 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
        else {
                if (*branch == '\0') {
                        unsigned char sha1[20];
-                       const char *head = resolve_ref("HEAD", sha1, 0, NULL);
-                       if (!head)
-                               die ("No current branch");
                        free(branch);
-                       branch = xstrdup(head);
+                       branch = resolve_refdup("HEAD", sha1, 0, NULL);
+                       if (!branch)
+                               die ("No current branch");
+
                }
                reflogs = read_complete_reflog(branch);
                if (!reflogs || reflogs->nr == 0) {
@@ -291,6 +295,18 @@ void get_reflog_message(struct strbuf *sb,
        strbuf_add(sb, info->message, len);
 }
 
+const char *get_reflog_ident(struct reflog_walk_info *reflog_info)
+{
+       struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
+       struct reflog_info *info;
+
+       if (!commit_reflog)
+               return NULL;
+
+       info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
+       return info->email;
+}
+
 void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline,
        enum date_mode dmode)
 {
index 7bd2cd4c4e5cd9d51e645509eeacc49a83a44ba7..afb1ae3fde93a5c61def211be56b0554a25dd45e 100644 (file)
@@ -14,6 +14,7 @@ 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 const char *get_reflog_ident(struct reflog_walk_info *reflog_info);
 extern void get_reflog_selector(struct strbuf *sb,
                struct reflog_walk_info *reflog_info,
                enum date_mode dmode,
diff --git a/refs.c b/refs.c
index fb258ea9928ee22ca22ff9a4862993dace92dff9..6f436f1cb05e62c6afda086f5e410a268cf8bb52 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -4,18 +4,27 @@
 #include "tag.h"
 #include "dir.h"
 
-/* ISSYMREF=01 and ISPACKED=02 are public interfaces */
-#define REF_KNOWS_PEELED 04
-#define REF_BROKEN 010
+/* ISSYMREF=0x01, ISPACKED=0x02 and ISBROKEN=0x04 are public interfaces */
+#define REF_KNOWS_PEELED 0x10
 
-struct ref_list {
-       struct ref_list *next;
+struct ref_entry {
        unsigned char flag; /* ISSYMREF? ISPACKED? */
        unsigned char sha1[20];
        unsigned char peeled[20];
+       /* The full name of the reference (e.g., "refs/heads/master"): */
        char name[FLEX_ARRAY];
 };
 
+struct ref_array {
+       int nr, alloc;
+       struct ref_entry **refs;
+};
+
+/*
+ * Parse one line from a packed-refs file.  Write the SHA1 to sha1.
+ * Return a pointer to the refname within the line (null-terminated),
+ * or NULL if there was a problem.
+ */
 static const char *parse_ref_line(char *line, unsigned char *sha1)
 {
        /*
@@ -44,155 +53,198 @@ static const char *parse_ref_line(char *line, unsigned char *sha1)
        return line;
 }
 
-static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
-                               int flag, struct ref_list *list,
-                               struct ref_list **new_entry)
+static struct ref_entry *create_ref_entry(const char *refname,
+                                         const unsigned char *sha1, int flag,
+                                         int check_name)
 {
        int len;
-       struct ref_list *entry;
-
-       /* Allocate it and add it in.. */
-       len = strlen(name) + 1;
-       entry = xmalloc(sizeof(struct ref_list) + len);
-       hashcpy(entry->sha1, sha1);
-       hashclr(entry->peeled);
-       memcpy(entry->name, name, len);
-       entry->flag = flag;
-       entry->next = list;
-       if (new_entry)
-               *new_entry = entry;
-       return entry;
-}
-
-/* merge sort the ref list */
-static struct ref_list *sort_ref_list(struct ref_list *list)
-{
-       int psize, qsize, last_merge_count, cmp;
-       struct ref_list *p, *q, *l, *e;
-       struct ref_list *new_list = list;
-       int k = 1;
-       int merge_count = 0;
-
-       if (!list)
-               return list;
-
-       do {
-               last_merge_count = merge_count;
-               merge_count = 0;
-
-               psize = 0;
-
-               p = new_list;
-               q = new_list;
-               new_list = NULL;
-               l = NULL;
-
-               while (p) {
-                       merge_count++;
-
-                       while (psize < k && q->next) {
-                               q = q->next;
-                               psize++;
-                       }
-                       qsize = k;
-
-                       while ((psize > 0) || (qsize > 0 && q)) {
-                               if (qsize == 0 || !q) {
-                                       e = p;
-                                       p = p->next;
-                                       psize--;
-                               } else if (psize == 0) {
-                                       e = q;
-                                       q = q->next;
-                                       qsize--;
-                               } else {
-                                       cmp = strcmp(q->name, p->name);
-                                       if (cmp < 0) {
-                                               e = q;
-                                               q = q->next;
-                                               qsize--;
-                                       } else if (cmp > 0) {
-                                               e = p;
-                                               p = p->next;
-                                               psize--;
-                                       } else {
-                                               if (hashcmp(q->sha1, p->sha1))
-                                                       die("Duplicated ref, and SHA1s don't match: %s",
-                                                           q->name);
-                                               warning("Duplicated ref: %s", q->name);
-                                               e = q;
-                                               q = q->next;
-                                               qsize--;
-                                               free(e);
-                                               e = p;
-                                               p = p->next;
-                                               psize--;
-                                       }
-                               }
+       struct ref_entry *ref;
+
+       if (check_name &&
+           check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))
+               die("Reference has invalid format: '%s'", refname);
+       len = strlen(refname) + 1;
+       ref = xmalloc(sizeof(struct ref_entry) + len);
+       hashcpy(ref->sha1, sha1);
+       hashclr(ref->peeled);
+       memcpy(ref->name, refname, len);
+       ref->flag = flag;
+       return ref;
+}
 
-                               e->next = NULL;
+/* Add a ref_entry to the end of the ref_array (unsorted). */
+static void add_ref(struct ref_array *refs, struct ref_entry *ref)
+{
+       ALLOC_GROW(refs->refs, refs->nr + 1, refs->alloc);
+       refs->refs[refs->nr++] = ref;
+}
 
-                               if (l)
-                                       l->next = e;
-                               if (!new_list)
-                                       new_list = e;
-                               l = e;
-                       }
+static int ref_entry_cmp(const void *a, const void *b)
+{
+       struct ref_entry *one = *(struct ref_entry **)a;
+       struct ref_entry *two = *(struct ref_entry **)b;
+       return strcmp(one->name, two->name);
+}
+
+/*
+ * Emit a warning and return true iff ref1 and ref2 have the same name
+ * and the same sha1.  Die if they have the same name but different
+ * sha1s.
+ */
+static int is_dup_ref(const struct ref_entry *ref1, const struct ref_entry *ref2)
+{
+       if (!strcmp(ref1->name, ref2->name)) {
+               /* Duplicate name; make sure that the SHA1s match: */
+               if (hashcmp(ref1->sha1, ref2->sha1))
+                       die("Duplicated ref, and SHA1s don't match: %s",
+                           ref1->name);
+               warning("Duplicated ref: %s", ref1->name);
+               return 1;
+       } else {
+               return 0;
+       }
+}
+
+static void sort_ref_array(struct ref_array *array)
+{
+       int i, j;
+
+       /* Nothing to sort unless there are at least two entries */
+       if (array->nr < 2)
+               return;
 
-                       p = q;
-               };
+       qsort(array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
 
-               k = k * 2;
-       } while ((last_merge_count != merge_count) || (last_merge_count != 1));
+       /* Remove any duplicates from the ref_array */
+       i = 0;
+       for (j = 1; j < array->nr; j++) {
+               if (is_dup_ref(array->refs[i], array->refs[j])) {
+                       free(array->refs[j]);
+                       continue;
+               }
+               array->refs[++i] = array->refs[j];
+       }
+       array->nr = i + 1;
+}
+
+static struct ref_entry *search_ref_array(struct ref_array *array, const char *refname)
+{
+       struct ref_entry *e, **r;
+       int len;
+
+       if (refname == NULL)
+               return NULL;
+
+       if (!array->nr)
+               return NULL;
+
+       len = strlen(refname) + 1;
+       e = xmalloc(sizeof(struct ref_entry) + len);
+       memcpy(e->name, refname, len);
 
-       return new_list;
+       r = bsearch(&e, array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
+
+       free(e);
+
+       if (r == NULL)
+               return NULL;
+
+       return *r;
 }
 
 /*
  * Future: need to be in "struct repository"
  * when doing a full libification.
  */
-static struct cached_refs {
+static struct ref_cache {
+       struct ref_cache *next;
        char did_loose;
        char did_packed;
-       struct ref_list *loose;
-       struct ref_list *packed;
-} cached_refs, submodule_refs;
-static struct ref_list *current_ref;
+       struct ref_array loose;
+       struct ref_array packed;
+       /* The submodule name, or "" for the main repo. */
+       char name[FLEX_ARRAY];
+} *ref_cache;
+
+static struct ref_entry *current_ref;
 
-static struct ref_list *extra_refs;
+static struct ref_array extra_refs;
 
-static void free_ref_list(struct ref_list *list)
+static void clear_ref_array(struct ref_array *array)
 {
-       struct ref_list *next;
-       for ( ; list; list = next) {
-               next = list->next;
-               free(list);
-       }
+       int i;
+       for (i = 0; i < array->nr; i++)
+               free(array->refs[i]);
+       free(array->refs);
+       array->nr = array->alloc = 0;
+       array->refs = NULL;
 }
 
-static void invalidate_cached_refs(void)
+static void clear_packed_ref_cache(struct ref_cache *refs)
 {
-       struct cached_refs *ca = &cached_refs;
+       if (refs->did_packed)
+               clear_ref_array(&refs->packed);
+       refs->did_packed = 0;
+}
 
-       if (ca->did_loose && ca->loose)
-               free_ref_list(ca->loose);
-       if (ca->did_packed && ca->packed)
-               free_ref_list(ca->packed);
-       ca->loose = ca->packed = NULL;
-       ca->did_loose = ca->did_packed = 0;
+static void clear_loose_ref_cache(struct ref_cache *refs)
+{
+       if (refs->did_loose)
+               clear_ref_array(&refs->loose);
+       refs->did_loose = 0;
 }
 
-static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
+static struct ref_cache *create_ref_cache(const char *submodule)
 {
-       struct ref_list *list = NULL;
-       struct ref_list *last = NULL;
+       int len;
+       struct ref_cache *refs;
+       if (!submodule)
+               submodule = "";
+       len = strlen(submodule) + 1;
+       refs = xcalloc(1, sizeof(struct ref_cache) + len);
+       memcpy(refs->name, submodule, len);
+       return refs;
+}
+
+/*
+ * Return a pointer to a ref_cache for the specified submodule. For
+ * the main repository, use submodule==NULL. The returned structure
+ * will be allocated and initialized but not necessarily populated; it
+ * should not be freed.
+ */
+static struct ref_cache *get_ref_cache(const char *submodule)
+{
+       struct ref_cache *refs = ref_cache;
+       if (!submodule)
+               submodule = "";
+       while (refs) {
+               if (!strcmp(submodule, refs->name))
+                       return refs;
+               refs = refs->next;
+       }
+
+       refs = create_ref_cache(submodule);
+       refs->next = ref_cache;
+       ref_cache = refs;
+       return refs;
+}
+
+void invalidate_ref_cache(const char *submodule)
+{
+       struct ref_cache *refs = get_ref_cache(submodule);
+       clear_packed_ref_cache(refs);
+       clear_loose_ref_cache(refs);
+}
+
+static void read_packed_refs(FILE *f, struct ref_array *array)
+{
+       struct ref_entry *last = NULL;
        char refline[PATH_MAX];
        int flag = REF_ISPACKED;
 
        while (fgets(refline, sizeof(refline), f)) {
                unsigned char sha1[20];
-               const char *name;
+               const char *refname;
                static const char header[] = "# pack-refs with:";
 
                if (!strncmp(refline, header, sizeof(header)-1)) {
@@ -203,9 +255,10 @@ static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
                        continue;
                }
 
-               name = parse_ref_line(refline, sha1);
-               if (name) {
-                       list = add_ref(name, sha1, flag, list, &last);
+               refname = parse_ref_line(refline, sha1);
+               if (refname) {
+                       last = create_ref_entry(refname, sha1, flag, 1);
+                       add_ref(array, last);
                        continue;
                }
                if (last &&
@@ -215,54 +268,47 @@ static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
                    !get_sha1_hex(refline + 1, sha1))
                        hashcpy(last->peeled, sha1);
        }
-       cached_refs->packed = sort_ref_list(list);
+       sort_ref_array(array);
 }
 
-void add_extra_ref(const char *name, const unsigned char *sha1, int flag)
+void add_extra_ref(const char *refname, const unsigned char *sha1, int flag)
 {
-       extra_refs = add_ref(name, sha1, flag, extra_refs, NULL);
+       add_ref(&extra_refs, create_ref_entry(refname, sha1, flag, 0));
 }
 
 void clear_extra_refs(void)
 {
-       free_ref_list(extra_refs);
-       extra_refs = NULL;
+       clear_ref_array(&extra_refs);
 }
 
-static struct ref_list *get_packed_refs(const char *submodule)
+static struct ref_array *get_packed_refs(struct ref_cache *refs)
 {
-       const char *packed_refs_file;
-       struct cached_refs *refs;
+       if (!refs->did_packed) {
+               const char *packed_refs_file;
+               FILE *f;
 
-       if (submodule) {
-               packed_refs_file = git_path_submodule(submodule, "packed-refs");
-               refs = &submodule_refs;
-               free_ref_list(refs->packed);
-       } else {
-               packed_refs_file = git_path("packed-refs");
-               refs = &cached_refs;
-       }
-
-       if (!refs->did_packed || submodule) {
-               FILE *f = fopen(packed_refs_file, "r");
-               refs->packed = NULL;
+               if (*refs->name)
+                       packed_refs_file = git_path_submodule(refs->name, "packed-refs");
+               else
+                       packed_refs_file = git_path("packed-refs");
+               f = fopen(packed_refs_file, "r");
                if (f) {
-                       read_packed_refs(f, refs);
+                       read_packed_refs(f, &refs->packed);
                        fclose(f);
                }
                refs->did_packed = 1;
        }
-       return refs->packed;
+       return &refs->packed;
 }
 
-static struct ref_list *get_ref_dir(const char *submodule, const char *base,
-                                   struct ref_list *list)
+static void get_ref_dir(struct ref_cache *refs, const char *base,
+                       struct ref_array *array)
 {
        DIR *dir;
        const char *path;
 
-       if (submodule)
-               path = git_path_submodule(submodule, "%s", base);
+       if (*refs->name)
+               path = git_path_submodule(refs->name, "%s", base);
        else
                path = git_path("%s", base);
 
@@ -272,11 +318,11 @@ static struct ref_list *get_ref_dir(const char *submodule, const char *base,
        if (dir) {
                struct dirent *de;
                int baselen = strlen(base);
-               char *ref = xmalloc(baselen + 257);
+               char *refname = xmalloc(baselen + 257);
 
-               memcpy(ref, base, baselen);
+               memcpy(refname, base, baselen);
                if (baselen && base[baselen-1] != '/')
-                       ref[baselen++] = '/';
+                       refname[baselen++] = '/';
 
                while ((de = readdir(dir)) != NULL) {
                        unsigned char sha1[20];
@@ -292,34 +338,32 @@ static struct ref_list *get_ref_dir(const char *submodule, const char *base,
                                continue;
                        if (has_extension(de->d_name, ".lock"))
                                continue;
-                       memcpy(ref + baselen, de->d_name, namelen+1);
-                       refdir = submodule
-                               ? git_path_submodule(submodule, "%s", ref)
-                               : git_path("%s", ref);
+                       memcpy(refname + baselen, de->d_name, namelen+1);
+                       refdir = *refs->name
+                               ? git_path_submodule(refs->name, "%s", refname)
+                               : git_path("%s", refname);
                        if (stat(refdir, &st) < 0)
                                continue;
                        if (S_ISDIR(st.st_mode)) {
-                               list = get_ref_dir(submodule, ref, list);
+                               get_ref_dir(refs, refname, array);
                                continue;
                        }
-                       if (submodule) {
+                       if (*refs->name) {
                                hashclr(sha1);
                                flag = 0;
-                               if (resolve_gitlink_ref(submodule, ref, sha1) < 0) {
-                                       hashclr(sha1);
-                                       flag |= REF_BROKEN;
-                               }
-                       } else
-                               if (!resolve_ref(ref, sha1, 1, &flag)) {
+                               if (resolve_gitlink_ref(refs->name, refname, sha1) < 0) {
                                        hashclr(sha1);
-                                       flag |= REF_BROKEN;
+                                       flag |= REF_ISBROKEN;
                                }
-                       list = add_ref(ref, sha1, flag, list, NULL);
+                       } else if (read_ref_full(refname, sha1, 1, &flag)) {
+                               hashclr(sha1);
+                               flag |= REF_ISBROKEN;
+                       }
+                       add_ref(array, create_ref_entry(refname, sha1, flag, 1));
                }
-               free(ref);
+               free(refname);
                closedir(dir);
        }
-       return sort_ref_list(list);
 }
 
 struct warn_if_dangling_data {
@@ -338,7 +382,7 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha
        if (!(flags & REF_ISSYMREF))
                return 0;
 
-       resolves_to = resolve_ref(refname, junk, 0, NULL);
+       resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);
        if (!resolves_to || strcmp(resolves_to, d->refname))
                return 0;
 
@@ -356,63 +400,55 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
        for_each_rawref(warn_if_dangling_symref, &data);
 }
 
-static struct ref_list *get_loose_refs(const char *submodule)
+static struct ref_array *get_loose_refs(struct ref_cache *refs)
 {
-       if (submodule) {
-               free_ref_list(submodule_refs.loose);
-               submodule_refs.loose = get_ref_dir(submodule, "refs", NULL);
-               return submodule_refs.loose;
-       }
-
-       if (!cached_refs.did_loose) {
-               cached_refs.loose = get_ref_dir(NULL, "refs", NULL);
-               cached_refs.did_loose = 1;
+       if (!refs->did_loose) {
+               get_ref_dir(refs, "refs", &refs->loose);
+               sort_ref_array(&refs->loose);
+               refs->did_loose = 1;
        }
-       return cached_refs.loose;
+       return &refs->loose;
 }
 
 /* We allow "recursive" symbolic refs. Only within reason, though */
 #define MAXDEPTH 5
 #define MAXREFLEN (1024)
 
-static int resolve_gitlink_packed_ref(char *name, int pathlen, const char *refname, unsigned char *result)
+/*
+ * Called by resolve_gitlink_ref_recursive() after it failed to read
+ * from the loose refs in ref_cache refs. Find <refname> in the
+ * packed-refs file for the submodule.
+ */
+static int resolve_gitlink_packed_ref(struct ref_cache *refs,
+                                     const char *refname, unsigned char *sha1)
 {
-       FILE *f;
-       struct cached_refs refs;
-       struct ref_list *ref;
-       int retval;
+       struct ref_entry *ref;
+       struct ref_array *array = get_packed_refs(refs);
 
-       strcpy(name + pathlen, "packed-refs");
-       f = fopen(name, "r");
-       if (!f)
+       ref = search_ref_array(array, refname);
+       if (ref == NULL)
                return -1;
-       read_packed_refs(f, &refs);
-       fclose(f);
-       ref = refs.packed;
-       retval = -1;
-       while (ref) {
-               if (!strcmp(ref->name, refname)) {
-                       retval = 0;
-                       memcpy(result, ref->sha1, 20);
-                       break;
-               }
-               ref = ref->next;
-       }
-       free_ref_list(refs.packed);
-       return retval;
+
+       memcpy(sha1, ref->sha1, 20);
+       return 0;
 }
 
-static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *refname, unsigned char *result, int recursion)
+static int resolve_gitlink_ref_recursive(struct ref_cache *refs,
+                                        const char *refname, unsigned char *sha1,
+                                        int recursion)
 {
-       int fd, len = strlen(refname);
+       int fd, len;
        char buffer[128], *p;
+       char *path;
 
-       if (recursion > MAXDEPTH || len > MAXREFLEN)
+       if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)
                return -1;
-       memcpy(name + pathlen, refname, len+1);
-       fd = open(name, O_RDONLY);
+       path = *refs->name
+               ? git_path_submodule(refs->name, "%s", refname)
+               : git_path("%s", refname);
+       fd = open(path, O_RDONLY);
        if (fd < 0)
-               return resolve_gitlink_packed_ref(name, pathlen, refname, result);
+               return resolve_gitlink_packed_ref(refs, refname, sha1);
 
        len = read(fd, buffer, sizeof(buffer)-1);
        close(fd);
@@ -423,7 +459,7 @@ static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *re
        buffer[len] = 0;
 
        /* Was it a detached head or an old-fashioned symlink? */
-       if (!get_sha1_hex(buffer, result))
+       if (!get_sha1_hex(buffer, sha1))
                return 0;
 
        /* Symref? */
@@ -433,60 +469,55 @@ static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *re
        while (isspace(*p))
                p++;
 
-       return resolve_gitlink_ref_recursive(name, pathlen, p, result, recursion+1);
+       return resolve_gitlink_ref_recursive(refs, p, sha1, recursion+1);
 }
 
-int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *result)
+int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1)
 {
        int len = strlen(path), retval;
-       char *gitdir;
-       const char *tmp;
+       char *submodule;
+       struct ref_cache *refs;
 
        while (len && path[len-1] == '/')
                len--;
        if (!len)
                return -1;
-       gitdir = xmalloc(len + MAXREFLEN + 8);
-       memcpy(gitdir, path, len);
-       memcpy(gitdir + len, "/.git", 6);
-       len += 5;
-
-       tmp = read_gitfile(gitdir);
-       if (tmp) {
-               free(gitdir);
-               len = strlen(tmp);
-               gitdir = xmalloc(len + MAXREFLEN + 3);
-               memcpy(gitdir, tmp, len);
-       }
-       gitdir[len] = '/';
-       gitdir[++len] = '\0';
-       retval = resolve_gitlink_ref_recursive(gitdir, len, refname, result, 0);
-       free(gitdir);
+       submodule = xstrndup(path, len);
+       refs = get_ref_cache(submodule);
+       free(submodule);
+
+       retval = resolve_gitlink_ref_recursive(refs, refname, sha1, 0);
        return retval;
 }
 
 /*
- * If the "reading" argument is set, this function finds out what _object_
- * the ref points at by "reading" the ref.  The ref, if it is not symbolic,
- * has to exist, and if it is symbolic, it has to point at an existing ref,
- * because the "read" goes through the symref to the ref it points at.
- *
- * The access that is not "reading" may often be "writing", but does not
- * have to; it can be merely checking _where it leads to_. If it is a
- * prelude to "writing" to the ref, a write to a symref that points at
- * yet-to-be-born ref will create the real ref pointed by the symref.
- * reading=0 allows the caller to check where such a symref leads to.
+ * Try to read ref from the packed references.  On success, set sha1
+ * and return 0; otherwise, return -1.
  */
-const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
+static int get_packed_ref(const char *refname, unsigned char *sha1)
+{
+       struct ref_array *packed = get_packed_refs(get_ref_cache(NULL));
+       struct ref_entry *entry = search_ref_array(packed, refname);
+       if (entry) {
+               hashcpy(sha1, entry->sha1);
+               return 0;
+       }
+       return -1;
+}
+
+const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
 {
        int depth = MAXDEPTH;
        ssize_t len;
        char buffer[256];
-       static char ref_buffer[256];
+       static char refname_buffer[256];
 
        if (flag)
                *flag = 0;
 
+       if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
+               return NULL;
+
        for (;;) {
                char path[PATH_MAX];
                struct stat st;
@@ -496,32 +527,39 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                if (--depth < 0)
                        return NULL;
 
-               git_snpath(path, sizeof(path), "%s", ref);
-               /* Special case: non-existing file. */
+               git_snpath(path, sizeof(path), "%s", refname);
+
                if (lstat(path, &st) < 0) {
-                       struct ref_list *list = get_packed_refs(NULL);
-                       while (list) {
-                               if (!strcmp(ref, list->name)) {
-                                       hashcpy(sha1, list->sha1);
-                                       if (flag)
-                                               *flag |= REF_ISPACKED;
-                                       return ref;
-                               }
-                               list = list->next;
+                       if (errno != ENOENT)
+                               return NULL;
+                       /*
+                        * The loose reference file does not exist;
+                        * check for a packed reference.
+                        */
+                       if (!get_packed_ref(refname, sha1)) {
+                               if (flag)
+                                       *flag |= REF_ISPACKED;
+                               return refname;
                        }
-                       if (reading || errno != ENOENT)
+                       /* The reference is not a packed reference, either. */
+                       if (reading) {
                                return NULL;
-                       hashclr(sha1);
-                       return ref;
+                       } else {
+                               hashclr(sha1);
+                               return refname;
+                       }
                }
 
                /* Follow "normalized" - ie "refs/.." symlinks by hand */
                if (S_ISLNK(st.st_mode)) {
                        len = readlink(path, buffer, sizeof(buffer)-1);
-                       if (len >= 5 && !memcmp("refs/", buffer, 5)) {
-                               buffer[len] = 0;
-                               strcpy(ref_buffer, buffer);
-                               ref = ref_buffer;
+                       if (len < 0)
+                               return NULL;
+                       buffer[len] = 0;
+                       if (!prefixcmp(buffer, "refs/") &&
+                                       !check_refname_format(buffer, 0)) {
+                               strcpy(refname_buffer, buffer);
+                               refname = refname_buffer;
                                if (flag)
                                        *flag |= REF_ISSYMREF;
                                continue;
@@ -543,27 +581,42 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                        return NULL;
                len = read_in_full(fd, buffer, sizeof(buffer)-1);
                close(fd);
+               if (len < 0)
+                       return NULL;
+               while (len && isspace(buffer[len-1]))
+                       len--;
+               buffer[len] = '\0';
 
                /*
                 * Is it a symbolic ref?
                 */
-               if (len < 4 || memcmp("ref:", buffer, 4))
+               if (prefixcmp(buffer, "ref:"))
                        break;
-               buf = buffer + 4;
-               len -= 4;
-               while (len && isspace(*buf))
-                       buf++, len--;
-               while (len && isspace(buf[len-1]))
-                       len--;
-               buf[len] = 0;
-               memcpy(ref_buffer, buf, len + 1);
-               ref = ref_buffer;
                if (flag)
                        *flag |= REF_ISSYMREF;
+               buf = buffer + 4;
+               while (isspace(*buf))
+                       buf++;
+               if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
+                       if (flag)
+                               *flag |= REF_ISBROKEN;
+                       return NULL;
+               }
+               refname = strcpy(refname_buffer, buf);
        }
-       if (len < 40 || get_sha1_hex(buffer, sha1))
+       /* Please note that FETCH_HEAD has a second line containing other data. */
+       if (get_sha1_hex(buffer, sha1) || (buffer[40] != '\0' && !isspace(buffer[40]))) {
+               if (flag)
+                       *flag |= REF_ISBROKEN;
                return NULL;
-       return ref;
+       }
+       return refname;
+}
+
+char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)
+{
+       const char *ret = resolve_ref_unsafe(ref, sha1, reading, flag);
+       return ret ? xstrdup(ret) : NULL;
 }
 
 /* The argument to filter_refs */
@@ -573,23 +626,28 @@ struct ref_filter {
        void *cb_data;
 };
 
-int read_ref(const char *ref, unsigned char *sha1)
+int read_ref_full(const char *refname, unsigned char *sha1, int reading, int *flags)
 {
-       if (resolve_ref(ref, sha1, 1, NULL))
+       if (resolve_ref_unsafe(refname, sha1, reading, flags))
                return 0;
        return -1;
 }
 
+int read_ref(const char *refname, unsigned char *sha1)
+{
+       return read_ref_full(refname, sha1, 1, NULL);
+}
+
 #define DO_FOR_EACH_INCLUDE_BROKEN 01
 static int do_one_ref(const char *base, each_ref_fn fn, int trim,
-                     int flags, void *cb_data, struct ref_list *entry)
+                     int flags, void *cb_data, struct ref_entry *entry)
 {
        if (prefixcmp(entry->name, base))
                return 0;
 
        if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
-               if (entry->flag & REF_BROKEN)
-                       return 0; /* ignore dangling symref */
+               if (entry->flag & REF_ISBROKEN)
+                       return 0; /* ignore broken refs e.g. dangling symref */
                if (!has_sha1_file(entry->sha1)) {
                        error("%s does not point to a valid object!", entry->name);
                        return 0;
@@ -599,23 +657,23 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim,
        return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
 }
 
-static int filter_refs(const char *ref, const unsigned char *sha, int flags,
-       void *data)
+static int filter_refs(const char *refname, const unsigned char *sha1, int flags,
+                      void *data)
 {
        struct ref_filter *filter = (struct ref_filter *)data;
-       if (fnmatch(filter->pattern, ref, 0))
+       if (fnmatch(filter->pattern, refname, 0))
                return 0;
-       return filter->fn(ref, sha, flags, filter->cb_data);
+       return filter->fn(refname, sha1, flags, filter->cb_data);
 }
 
-int peel_ref(const char *ref, unsigned char *sha1)
+int peel_ref(const char *refname, unsigned char *sha1)
 {
        int flag;
        unsigned char base[20];
        struct object *o;
 
-       if (current_ref && (current_ref->name == ref
-               || !strcmp(current_ref->name, ref))) {
+       if (current_ref && (current_ref->name == refname
+               || !strcmp(current_ref->name, refname))) {
                if (current_ref->flag & REF_KNOWS_PEELED) {
                        hashcpy(sha1, current_ref->peeled);
                        return 0;
@@ -624,29 +682,23 @@ int peel_ref(const char *ref, unsigned char *sha1)
                goto fallback;
        }
 
-       if (!resolve_ref(ref, base, 1, &flag))
+       if (read_ref_full(refname, base, 1, &flag))
                return -1;
 
        if ((flag & REF_ISPACKED)) {
-               struct ref_list *list = get_packed_refs(NULL);
+               struct ref_array *array = get_packed_refs(get_ref_cache(NULL));
+               struct ref_entry *r = search_ref_array(array, refname);
 
-               while (list) {
-                       if (!strcmp(list->name, ref)) {
-                               if (list->flag & REF_KNOWS_PEELED) {
-                                       hashcpy(sha1, list->peeled);
-                                       return 0;
-                               }
-                               /* older pack-refs did not leave peeled ones */
-                               break;
-                       }
-                       list = list->next;
+               if (r != NULL && r->flag & REF_KNOWS_PEELED) {
+                       hashcpy(sha1, r->peeled);
+                       return 0;
                }
        }
 
 fallback:
        o = parse_object(base);
        if (o && o->type == OBJ_TAG) {
-               o = deref_tag(o, ref, 0);
+               o = deref_tag(o, refname, 0);
                if (o) {
                        hashcpy(sha1, o->sha1);
                        return 0;
@@ -658,36 +710,40 @@ fallback:
 static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
                           int trim, int flags, void *cb_data)
 {
-       int retval = 0;
-       struct ref_list *packed = get_packed_refs(submodule);
-       struct ref_list *loose = get_loose_refs(submodule);
+       int retval = 0, i, p = 0, l = 0;
+       struct ref_cache *refs = get_ref_cache(submodule);
+       struct ref_array *packed = get_packed_refs(refs);
+       struct ref_array *loose = get_loose_refs(refs);
 
-       struct ref_list *extra;
+       struct ref_array *extra = &extra_refs;
 
-       for (extra = extra_refs; extra; extra = extra->next)
-               retval = do_one_ref(base, fn, trim, flags, cb_data, extra);
+       for (i = 0; i < extra->nr; i++)
+               retval = do_one_ref(base, fn, trim, flags, cb_data, extra->refs[i]);
 
-       while (packed && loose) {
-               struct ref_list *entry;
-               int cmp = strcmp(packed->name, loose->name);
+       while (p < packed->nr && l < loose->nr) {
+               struct ref_entry *entry;
+               int cmp = strcmp(packed->refs[p]->name, loose->refs[l]->name);
                if (!cmp) {
-                       packed = packed->next;
+                       p++;
                        continue;
                }
                if (cmp > 0) {
-                       entry = loose;
-                       loose = loose->next;
+                       entry = loose->refs[l++];
                } else {
-                       entry = packed;
-                       packed = packed->next;
+                       entry = packed->refs[p++];
                }
                retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
                if (retval)
                        goto end_each;
        }
 
-       for (packed = packed ? packed : loose; packed; packed = packed->next) {
-               retval = do_one_ref(base, fn, trim, flags, cb_data, packed);
+       if (l < loose->nr) {
+               p = l;
+               packed = loose;
+       }
+
+       for (; p < packed->nr; p++) {
+               retval = do_one_ref(base, fn, trim, flags, cb_data, packed->refs[p]);
                if (retval)
                        goto end_each;
        }
@@ -710,7 +766,7 @@ static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
                return 0;
        }
 
-       if (resolve_ref("HEAD", sha1, 1, &flag))
+       if (!read_ref_full("HEAD", sha1, 1, &flag))
                return fn("HEAD", sha1, flag, cb_data);
 
        return 0;
@@ -790,7 +846,7 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)
        int flag;
 
        strbuf_addf(&buf, "%sHEAD", get_git_namespace());
-       if (resolve_ref(buf.buf, sha1, 1, &flag))
+       if (!read_ref_full(buf.buf, sha1, 1, &flag))
                ret = fn(buf.buf, sha1, flag, cb_data);
        strbuf_release(&buf);
 
@@ -860,70 +916,87 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)
  * - it contains a "\" (backslash)
  */
 
+/* Return true iff ch is not allowed in reference names. */
 static inline int bad_ref_char(int ch)
 {
        if (((unsigned) ch) <= ' ' || ch == 0x7f ||
            ch == '~' || ch == '^' || ch == ':' || ch == '\\')
                return 1;
        /* 2.13 Pattern Matching Notation */
-       if (ch == '?' || ch == '[') /* Unsupported */
+       if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */
                return 1;
-       if (ch == '*') /* Supported at the end */
-               return 2;
        return 0;
 }
 
-int check_ref_format(const char *ref)
+/*
+ * Try to read one refname component from the front of refname.  Return
+ * the length of the component found, or -1 if the component is not
+ * legal.
+ */
+static int check_refname_component(const char *refname, int flags)
 {
-       int ch, level, bad_type, last;
-       int ret = CHECK_REF_FORMAT_OK;
-       const char *cp = ref;
-
-       level = 0;
-       while (1) {
-               while ((ch = *cp++) == '/')
-                       ; /* tolerate duplicated slashes */
-               if (!ch)
-                       /* should not end with slashes */
-                       return CHECK_REF_FORMAT_ERROR;
-
-               /* we are at the beginning of the path component */
-               if (ch == '.')
-                       return CHECK_REF_FORMAT_ERROR;
-               bad_type = bad_ref_char(ch);
-               if (bad_type) {
-                       if (bad_type == 2 && (!*cp || *cp == '/') &&
-                           ret == CHECK_REF_FORMAT_OK)
-                               ret = CHECK_REF_FORMAT_WILDCARD;
-                       else
-                               return CHECK_REF_FORMAT_ERROR;
-               }
+       const char *cp;
+       char last = '\0';
 
+       for (cp = refname; ; cp++) {
+               char ch = *cp;
+               if (ch == '\0' || ch == '/')
+                       break;
+               if (bad_ref_char(ch))
+                       return -1; /* Illegal character in refname. */
+               if (last == '.' && ch == '.')
+                       return -1; /* Refname contains "..". */
+               if (last == '@' && ch == '{')
+                       return -1; /* Refname contains "@{". */
                last = ch;
-               /* scan the rest of the path component */
-               while ((ch = *cp++) != 0) {
-                       bad_type = bad_ref_char(ch);
-                       if (bad_type)
-                               return CHECK_REF_FORMAT_ERROR;
-                       if (ch == '/')
-                               break;
-                       if (last == '.' && ch == '.')
-                               return CHECK_REF_FORMAT_ERROR;
-                       if (last == '@' && ch == '{')
-                               return CHECK_REF_FORMAT_ERROR;
-                       last = ch;
-               }
-               level++;
-               if (!ch) {
-                       if (ref <= cp - 2 && cp[-2] == '.')
-                               return CHECK_REF_FORMAT_ERROR;
-                       if (level < 2)
-                               return CHECK_REF_FORMAT_ONELEVEL;
-                       if (has_extension(ref, ".lock"))
-                               return CHECK_REF_FORMAT_ERROR;
-                       return ret;
+       }
+       if (cp == refname)
+               return -1; /* Component has zero length. */
+       if (refname[0] == '.') {
+               if (!(flags & REFNAME_DOT_COMPONENT))
+                       return -1; /* Component starts with '.'. */
+               /*
+                * Even if leading dots are allowed, don't allow "."
+                * as a component (".." is prevented by a rule above).
+                */
+               if (refname[1] == '\0')
+                       return -1; /* Component equals ".". */
+       }
+       if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5))
+               return -1; /* Refname ends with ".lock". */
+       return cp - refname;
+}
+
+int check_refname_format(const char *refname, int flags)
+{
+       int component_len, component_count = 0;
+
+       while (1) {
+               /* We are at the start of a path component. */
+               component_len = check_refname_component(refname, flags);
+               if (component_len < 0) {
+                       if ((flags & REFNAME_REFSPEC_PATTERN) &&
+                                       refname[0] == '*' &&
+                                       (refname[1] == '\0' || refname[1] == '/')) {
+                               /* Accept one wildcard as a full refname component. */
+                               flags &= ~REFNAME_REFSPEC_PATTERN;
+                               component_len = 1;
+                       } else {
+                               return -1;
+                       }
                }
+               component_count++;
+               if (refname[component_len] == '\0')
+                       break;
+               /* Skip to next component. */
+               refname += component_len + 1;
        }
+
+       if (refname[component_len - 1] == '.')
+               return -1; /* Refname ends with '.'. */
+       if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2)
+               return -1; /* Refname has only one component. */
+       return 0;
 }
 
 const char *prettify_refname(const char *name)
@@ -945,13 +1018,6 @@ const char *ref_rev_parse_rules[] = {
        NULL
 };
 
-const char *ref_fetch_rules[] = {
-       "%.*s",
-       "refs/%.*s",
-       "refs/heads/%.*s",
-       NULL
-};
-
 int refname_match(const char *abbrev_name, const char *full_name, const char **rules)
 {
        const char **p;
@@ -969,7 +1035,7 @@ int refname_match(const char *abbrev_name, const char *full_name, const char **r
 static struct ref_lock *verify_lock(struct ref_lock *lock,
        const unsigned char *old_sha1, int mustexist)
 {
-       if (!resolve_ref(lock->ref_name, lock->old_sha1, mustexist, NULL)) {
+       if (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) {
                error("Can't verify ref %s", lock->ref_name);
                unlock_ref(lock);
                return NULL;
@@ -1002,33 +1068,129 @@ static int remove_empty_directories(const char *file)
        return result;
 }
 
-static int is_refname_available(const char *ref, const char *oldref,
-                               struct ref_list *list, int quiet)
-{
-       int namlen = strlen(ref); /* e.g. 'foo/bar' */
-       while (list) {
-               /* list->name could be 'foo' or 'foo/bar/baz' */
-               if (!oldref || strcmp(oldref, list->name)) {
-                       int len = strlen(list->name);
+/*
+ * Return true iff a reference named refname could be created without
+ * conflicting with the name of an existing reference.  If oldrefname
+ * is non-NULL, ignore potential conflicts with oldrefname (e.g.,
+ * because oldrefname is scheduled for deletion in the same
+ * operation).
+ */
+static int is_refname_available(const char *refname, const char *oldrefname,
+                               struct ref_array *array)
+{
+       int i, namlen = strlen(refname); /* e.g. 'foo/bar' */
+       for (i = 0; i < array->nr; i++ ) {
+               struct ref_entry *entry = array->refs[i];
+               /* entry->name could be 'foo' or 'foo/bar/baz' */
+               if (!oldrefname || strcmp(oldrefname, entry->name)) {
+                       int len = strlen(entry->name);
                        int cmplen = (namlen < len) ? namlen : len;
-                       const char *lead = (namlen < len) ? list->name : ref;
-                       if (!strncmp(ref, list->name, cmplen) &&
+                       const char *lead = (namlen < len) ? entry->name : refname;
+                       if (!strncmp(refname, entry->name, cmplen) &&
                            lead[cmplen] == '/') {
-                               if (!quiet)
-                                       error("'%s' exists; cannot create '%s'",
-                                             list->name, ref);
+                               error("'%s' exists; cannot create '%s'",
+                                     entry->name, refname);
                                return 0;
                        }
                }
-               list = list->next;
        }
        return 1;
 }
 
-static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int flags, int *type_p)
+/*
+ * *string and *len will only be substituted, and *string returned (for
+ * later free()ing) if the string passed in is a magic short-hand form
+ * to name a branch.
+ */
+static char *substitute_branch_name(const char **string, int *len)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int ret = interpret_branch_name(*string, &buf);
+
+       if (ret == *len) {
+               size_t size;
+               *string = strbuf_detach(&buf, &size);
+               *len = size;
+               return (char *)*string;
+       }
+
+       return NULL;
+}
+
+int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
+{
+       char *last_branch = substitute_branch_name(&str, &len);
+       const char **p, *r;
+       int refs_found = 0;
+
+       *ref = NULL;
+       for (p = ref_rev_parse_rules; *p; p++) {
+               char fullref[PATH_MAX];
+               unsigned char sha1_from_ref[20];
+               unsigned char *this_result;
+               int flag;
+
+               this_result = refs_found ? sha1_from_ref : sha1;
+               mksnpath(fullref, sizeof(fullref), *p, len, str);
+               r = resolve_ref_unsafe(fullref, this_result, 1, &flag);
+               if (r) {
+                       if (!refs_found++)
+                               *ref = xstrdup(r);
+                       if (!warn_ambiguous_refs)
+                               break;
+               } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD")) {
+                       warning("ignoring dangling symref %s.", fullref);
+               } else if ((flag & REF_ISBROKEN) && strchr(fullref, '/')) {
+                       warning("ignoring broken ref %s.", fullref);
+               }
+       }
+       free(last_branch);
+       return refs_found;
+}
+
+int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
+{
+       char *last_branch = substitute_branch_name(&str, &len);
+       const char **p;
+       int logs_found = 0;
+
+       *log = NULL;
+       for (p = ref_rev_parse_rules; *p; p++) {
+               struct stat st;
+               unsigned char hash[20];
+               char path[PATH_MAX];
+               const char *ref, *it;
+
+               mksnpath(path, sizeof(path), *p, len, str);
+               ref = resolve_ref_unsafe(path, hash, 1, NULL);
+               if (!ref)
+                       continue;
+               if (!stat(git_path("logs/%s", path), &st) &&
+                   S_ISREG(st.st_mode))
+                       it = path;
+               else if (strcmp(ref, path) &&
+                        !stat(git_path("logs/%s", ref), &st) &&
+                        S_ISREG(st.st_mode))
+                       it = ref;
+               else
+                       continue;
+               if (!logs_found++) {
+                       *log = xstrdup(it);
+                       hashcpy(sha1, hash);
+               }
+               if (!warn_ambiguous_refs)
+                       break;
+       }
+       free(last_branch);
+       return logs_found;
+}
+
+static struct ref_lock *lock_ref_sha1_basic(const char *refname,
+                                           const unsigned char *old_sha1,
+                                           int flags, int *type_p)
 {
        char *ref_file;
-       const char *orig_ref = ref;
+       const char *orig_refname = refname;
        struct ref_lock *lock;
        int last_errno = 0;
        int type, lflags;
@@ -1038,27 +1200,27 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
        lock = xcalloc(1, sizeof(struct ref_lock));
        lock->lock_fd = -1;
 
-       ref = resolve_ref(ref, lock->old_sha1, mustexist, &type);
-       if (!ref && errno == EISDIR) {
+       refname = resolve_ref_unsafe(refname, lock->old_sha1, mustexist, &type);
+       if (!refname && errno == EISDIR) {
                /* we are trying to lock foo but we used to
                 * have foo/bar which now does not exist;
                 * it is normal for the empty directory 'foo'
                 * to remain.
                 */
-               ref_file = git_path("%s", orig_ref);
+               ref_file = git_path("%s", orig_refname);
                if (remove_empty_directories(ref_file)) {
                        last_errno = errno;
-                       error("there are still refs under '%s'", orig_ref);
+                       error("there are still refs under '%s'", orig_refname);
                        goto error_return;
                }
-               ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, &type);
+               refname = resolve_ref_unsafe(orig_refname, lock->old_sha1, mustexist, &type);
        }
        if (type_p)
            *type_p = type;
-       if (!ref) {
+       if (!refname) {
                last_errno = errno;
                error("unable to resolve reference %s: %s",
-                       orig_ref, strerror(errno));
+                       orig_refname, strerror(errno));
                goto error_return;
        }
        missing = is_null_sha1(lock->old_sha1);
@@ -1068,7 +1230,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
         * name is a proper prefix of our refname.
         */
        if (missing &&
-            !is_refname_available(ref, NULL, get_packed_refs(NULL), 0)) {
+            !is_refname_available(refname, NULL, get_packed_refs(get_ref_cache(NULL)))) {
                last_errno = ENOTDIR;
                goto error_return;
        }
@@ -1077,12 +1239,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
 
        lflags = LOCK_DIE_ON_ERROR;
        if (flags & REF_NODEREF) {
-               ref = orig_ref;
+               refname = orig_refname;
                lflags |= LOCK_NODEREF;
        }
-       lock->ref_name = xstrdup(ref);
-       lock->orig_ref_name = xstrdup(orig_ref);
-       ref_file = git_path("%s", ref);
+       lock->ref_name = xstrdup(refname);
+       lock->orig_ref_name = xstrdup(orig_refname);
+       ref_file = git_path("%s", refname);
        if (missing)
                lock->force_write = 1;
        if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
@@ -1103,42 +1265,32 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
        return NULL;
 }
 
-struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
+struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)
 {
        char refpath[PATH_MAX];
-       if (check_ref_format(ref))
+       if (check_refname_format(refname, 0))
                return NULL;
-       strcpy(refpath, mkpath("refs/%s", ref));
+       strcpy(refpath, mkpath("refs/%s", refname));
        return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);
 }
 
-struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags)
+struct ref_lock *lock_any_ref_for_update(const char *refname,
+                                        const unsigned char *old_sha1, int flags)
 {
-       switch (check_ref_format(ref)) {
-       default:
+       if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
                return NULL;
-       case 0:
-       case CHECK_REF_FORMAT_ONELEVEL:
-               return lock_ref_sha1_basic(ref, old_sha1, flags, NULL);
-       }
+       return lock_ref_sha1_basic(refname, old_sha1, flags, NULL);
 }
 
 static struct lock_file packlock;
 
 static int repack_without_ref(const char *refname)
 {
-       struct ref_list *list, *packed_ref_list;
-       int fd;
-       int found = 0;
+       struct ref_array *packed;
+       int fd, i;
 
-       packed_ref_list = get_packed_refs(NULL);
-       for (list = packed_ref_list; list; list = list->next) {
-               if (!strcmp(refname, list->name)) {
-                       found = 1;
-                       break;
-               }
-       }
-       if (!found)
+       packed = get_packed_refs(get_ref_cache(NULL));
+       if (search_ref_array(packed, refname) == NULL)
                return 0;
        fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
        if (fd < 0) {
@@ -1146,17 +1298,18 @@ static int repack_without_ref(const char *refname)
                return error("cannot delete '%s' from packed refs", refname);
        }
 
-       for (list = packed_ref_list; list; list = list->next) {
+       for (i = 0; i < packed->nr; i++) {
                char line[PATH_MAX + 100];
                int len;
+               struct ref_entry *ref = packed->refs[i];
 
-               if (!strcmp(refname, list->name))
+               if (!strcmp(refname, ref->name))
                        continue;
                len = snprintf(line, sizeof(line), "%s %s\n",
-                              sha1_to_hex(list->sha1), list->name);
+                              sha1_to_hex(ref->sha1), ref->name);
                /* this should not happen but just being defensive */
                if (len > sizeof(line))
-                       die("too long a refname '%s'", list->name);
+                       die("too long a refname '%s'", ref->name);
                write_or_die(fd, line, len);
        }
        return commit_lock_file(&packlock);
@@ -1195,7 +1348,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
        ret |= repack_without_ref(refname);
 
        unlink_or_warn(git_path("logs/%s", lock->ref_name));
-       invalidate_cached_refs();
+       invalidate_ref_cache(NULL);
        unlock_ref(lock);
        return ret;
 }
@@ -1209,96 +1362,98 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
  */
 #define TMP_RENAMED_LOG  "logs/refs/.tmp-renamed-log"
 
-int rename_ref(const char *oldref, const char *newref, const char *logmsg)
+int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
 {
        unsigned char sha1[20], orig_sha1[20];
        int flag = 0, logmoved = 0;
        struct ref_lock *lock;
        struct stat loginfo;
-       int log = !lstat(git_path("logs/%s", oldref), &loginfo);
+       int log = !lstat(git_path("logs/%s", oldrefname), &loginfo);
        const char *symref = NULL;
+       struct ref_cache *refs = get_ref_cache(NULL);
 
        if (log && S_ISLNK(loginfo.st_mode))
-               return error("reflog for %s is a symlink", oldref);
+               return error("reflog for %s is a symlink", oldrefname);
 
-       symref = resolve_ref(oldref, orig_sha1, 1, &flag);
+       symref = resolve_ref_unsafe(oldrefname, orig_sha1, 1, &flag);
        if (flag & REF_ISSYMREF)
                return error("refname %s is a symbolic ref, renaming it is not supported",
-                       oldref);
+                       oldrefname);
        if (!symref)
-               return error("refname %s not found", oldref);
+               return error("refname %s not found", oldrefname);
 
-       if (!is_refname_available(newref, oldref, get_packed_refs(NULL), 0))
+       if (!is_refname_available(newrefname, oldrefname, get_packed_refs(refs)))
                return 1;
 
-       if (!is_refname_available(newref, oldref, get_loose_refs(NULL), 0))
+       if (!is_refname_available(newrefname, oldrefname, get_loose_refs(refs)))
                return 1;
 
-       if (log && rename(git_path("logs/%s", oldref), git_path(TMP_RENAMED_LOG)))
+       if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG)))
                return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s",
-                       oldref, strerror(errno));
+                       oldrefname, strerror(errno));
 
-       if (delete_ref(oldref, orig_sha1, REF_NODEREF)) {
-               error("unable to delete old %s", oldref);
+       if (delete_ref(oldrefname, orig_sha1, REF_NODEREF)) {
+               error("unable to delete old %s", oldrefname);
                goto rollback;
        }
 
-       if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1, REF_NODEREF)) {
+       if (!read_ref_full(newrefname, sha1, 1, &flag) &&
+           delete_ref(newrefname, sha1, REF_NODEREF)) {
                if (errno==EISDIR) {
-                       if (remove_empty_directories(git_path("%s", newref))) {
-                               error("Directory not empty: %s", newref);
+                       if (remove_empty_directories(git_path("%s", newrefname))) {
+                               error("Directory not empty: %s", newrefname);
                                goto rollback;
                        }
                } else {
-                       error("unable to delete existing %s", newref);
+                       error("unable to delete existing %s", newrefname);
                        goto rollback;
                }
        }
 
-       if (log && safe_create_leading_directories(git_path("logs/%s", newref))) {
-               error("unable to create directory for %s", newref);
+       if (log && safe_create_leading_directories(git_path("logs/%s", newrefname))) {
+               error("unable to create directory for %s", newrefname);
                goto rollback;
        }
 
  retry:
-       if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newref))) {
+       if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
                if (errno==EISDIR || errno==ENOTDIR) {
                        /*
                         * rename(a, b) when b is an existing
                         * directory ought to result in ISDIR, but
                         * Solaris 5.8 gives ENOTDIR.  Sheesh.
                         */
-                       if (remove_empty_directories(git_path("logs/%s", newref))) {
-                               error("Directory not empty: logs/%s", newref);
+                       if (remove_empty_directories(git_path("logs/%s", newrefname))) {
+                               error("Directory not empty: logs/%s", newrefname);
                                goto rollback;
                        }
                        goto retry;
                } else {
                        error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
-                               newref, strerror(errno));
+                               newrefname, strerror(errno));
                        goto rollback;
                }
        }
        logmoved = log;
 
-       lock = lock_ref_sha1_basic(newref, NULL, 0, NULL);
+       lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL);
        if (!lock) {
-               error("unable to lock %s for update", newref);
+               error("unable to lock %s for update", newrefname);
                goto rollback;
        }
        lock->force_write = 1;
        hashcpy(lock->old_sha1, orig_sha1);
        if (write_ref_sha1(lock, orig_sha1, logmsg)) {
-               error("unable to write current sha1 into %s", newref);
+               error("unable to write current sha1 into %s", newrefname);
                goto rollback;
        }
 
        return 0;
 
  rollback:
-       lock = lock_ref_sha1_basic(oldref, NULL, 0, NULL);
+       lock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL);
        if (!lock) {
-               error("unable to lock %s for rollback", oldref);
+               error("unable to lock %s for rollback", oldrefname);
                goto rollbacklog;
        }
 
@@ -1306,17 +1461,17 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        flag = log_all_ref_updates;
        log_all_ref_updates = 0;
        if (write_ref_sha1(lock, orig_sha1, NULL))
-               error("unable to write current sha1 into %s", oldref);
+               error("unable to write current sha1 into %s", oldrefname);
        log_all_ref_updates = flag;
 
  rollbacklog:
-       if (logmoved && rename(git_path("logs/%s", newref), git_path("logs/%s", oldref)))
+       if (logmoved && rename(git_path("logs/%s", newrefname), git_path("logs/%s", oldrefname)))
                error("unable to restore logfile %s from %s: %s",
-                       oldref, newref, strerror(errno));
+                       oldrefname, newrefname, strerror(errno));
        if (!logmoved && log &&
-           rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldref)))
+           rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldrefname)))
                error("unable to restore logfile %s from "TMP_RENAMED_LOG": %s",
-                       oldref, strerror(errno));
+                       oldrefname, strerror(errno));
 
        return 1;
 }
@@ -1373,16 +1528,16 @@ static int copy_msg(char *buf, const char *msg)
        return cp - buf;
 }
 
-int log_ref_setup(const char *ref_name, char *logfile, int bufsize)
+int log_ref_setup(const char *refname, char *logfile, int bufsize)
 {
        int logfd, oflags = O_APPEND | O_WRONLY;
 
-       git_snpath(logfile, bufsize, "logs/%s", ref_name);
+       git_snpath(logfile, bufsize, "logs/%s", refname);
        if (log_all_ref_updates &&
-           (!prefixcmp(ref_name, "refs/heads/") ||
-            !prefixcmp(ref_name, "refs/remotes/") ||
-            !prefixcmp(ref_name, "refs/notes/") ||
-            !strcmp(ref_name, "HEAD"))) {
+           (!prefixcmp(refname, "refs/heads/") ||
+            !prefixcmp(refname, "refs/remotes/") ||
+            !prefixcmp(refname, "refs/notes/") ||
+            !strcmp(refname, "HEAD"))) {
                if (safe_create_leading_directories(logfile) < 0)
                        return error("unable to create directory for %s",
                                     logfile);
@@ -1412,7 +1567,7 @@ int log_ref_setup(const char *ref_name, char *logfile, int bufsize)
        return 0;
 }
 
-static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
+static int log_ref_write(const char *refname, const unsigned char *old_sha1,
                         const unsigned char *new_sha1, const char *msg)
 {
        int logfd, result, written, oflags = O_APPEND | O_WRONLY;
@@ -1425,7 +1580,7 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
        if (log_all_ref_updates < 0)
                log_all_ref_updates = !is_bare_repository();
 
-       result = log_ref_setup(ref_name, log_file, sizeof(log_file));
+       result = log_ref_setup(refname, log_file, sizeof(log_file));
        if (result)
                return result;
 
@@ -1486,7 +1641,7 @@ int write_ref_sha1(struct ref_lock *lock,
                unlock_ref(lock);
                return -1;
        }
-       invalidate_cached_refs();
+       clear_loose_ref_cache(get_ref_cache(NULL));
        if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 ||
            (strcmp(lock->ref_name, lock->orig_ref_name) &&
             log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) {
@@ -1509,7 +1664,7 @@ int write_ref_sha1(struct ref_lock *lock,
                unsigned char head_sha1[20];
                int head_flag;
                const char *head_ref;
-               head_ref = resolve_ref("HEAD", head_sha1, 1, &head_flag);
+               head_ref = resolve_ref_unsafe("HEAD", head_sha1, 1, &head_flag);
                if (head_ref && (head_flag & REF_ISSYMREF) &&
                    !strcmp(head_ref, lock->ref_name))
                        log_ref_write("HEAD", lock->old_sha1, sha1, logmsg);
@@ -1596,7 +1751,9 @@ static char *ref_msg(const char *line, const char *endp)
        return xmemdupz(line, ep - line);
 }
 
-int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
+int read_ref_at(const char *refname, unsigned long at_time, int cnt,
+               unsigned char *sha1, char **msg,
+               unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
 {
        const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
        char *tz_c;
@@ -1607,7 +1764,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        void *log_mapped;
        size_t mapsz;
 
-       logfile = git_path("logs/%s", ref);
+       logfile = git_path("logs/%s", refname);
        logfd = open(logfile, O_RDONLY, 0);
        if (logfd < 0)
                die_errno("Unable to read log '%s'", logfile);
@@ -1700,14 +1857,14 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        return 1;
 }
 
-int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data)
+int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long ofs, void *cb_data)
 {
        const char *logfile;
        FILE *logfp;
        struct strbuf sb = STRBUF_INIT;
        int ret = 0;
 
-       logfile = git_path("logs/%s", ref);
+       logfile = git_path("logs/%s", refname);
        logfp = fopen(logfile, "r");
        if (!logfp)
                return -1;
@@ -1758,9 +1915,9 @@ int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs,
        return ret;
 }
 
-int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data)
 {
-       return for_each_recent_reflog_ent(ref, fn, 0, cb_data);
+       return for_each_recent_reflog_ent(refname, fn, 0, cb_data);
 }
 
 static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
@@ -1795,7 +1952,7 @@ static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
                                retval = do_for_each_reflog(log, fn, cb_data);
                        } else {
                                unsigned char sha1[20];
-                               if (!resolve_ref(log, sha1, 0, NULL))
+                               if (read_ref_full(log, sha1, 0, NULL))
                                        retval = error("bad ref for %s", log);
                                else
                                        retval = fn(log, sha1, 0, cb_data);
@@ -1843,10 +2000,10 @@ int update_ref(const char *action, const char *refname,
        return 0;
 }
 
-int ref_exists(char *refname)
+int ref_exists(const char *refname)
 {
        unsigned char sha1[20];
-       return !!resolve_ref(refname, sha1, 1, NULL);
+       return !!resolve_ref_unsafe(refname, sha1, 1, NULL);
 }
 
 struct ref *find_ref_by_name(const struct ref *list, const char *name)
@@ -1880,7 +2037,7 @@ static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
        return;
 }
 
-char *shorten_unambiguous_ref(const char *ref, int strict)
+char *shorten_unambiguous_ref(const char *refname, int strict)
 {
        int i;
        static char **scanf_fmts;
@@ -1909,10 +2066,10 @@ char *shorten_unambiguous_ref(const char *ref, int strict)
 
        /* bail out if there are no rules */
        if (!nr_rules)
-               return xstrdup(ref);
+               return xstrdup(refname);
 
-       /* buffer for scanf result, at most ref must fit */
-       short_name = xstrdup(ref);
+       /* buffer for scanf result, at most refname must fit */
+       short_name = xstrdup(refname);
 
        /* skip first rule, it will always match */
        for (i = nr_rules - 1; i > 0 ; --i) {
@@ -1920,7 +2077,7 @@ char *shorten_unambiguous_ref(const char *ref, int strict)
                int rules_to_fail = i;
                int short_name_len;
 
-               if (1 != sscanf(ref, scanf_fmts[i], short_name))
+               if (1 != sscanf(refname, scanf_fmts[i], short_name))
                        continue;
 
                short_name_len = strlen(short_name);
@@ -1938,7 +2095,6 @@ char *shorten_unambiguous_ref(const char *ref, int strict)
                 */
                for (j = 0; j < rules_to_fail; j++) {
                        const char *rule = ref_rev_parse_rules[j];
-                       unsigned char short_objectname[20];
                        char refname[PATH_MAX];
 
                        /* skip matched rule */
@@ -1952,7 +2108,7 @@ char *shorten_unambiguous_ref(const char *ref, int strict)
                         */
                        mksnpath(refname, sizeof(refname),
                                 rule, short_name_len, short_name);
-                       if (!read_ref(refname, short_objectname))
+                       if (ref_exists(refname))
                                break;
                }
 
@@ -1965,5 +2121,5 @@ char *shorten_unambiguous_ref(const char *ref, int strict)
        }
 
        free(short_name);
-       return xstrdup(ref);
+       return xstrdup(refname);
 }
diff --git a/refs.h b/refs.h
index dfb086e93346aa3fb6f600b2ed07b5736716bf9c..d4982915c58c7c17229e4a6621d02337e2550e13 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -10,8 +10,9 @@ struct ref_lock {
        int force_write;
 };
 
-#define REF_ISSYMREF 01
-#define REF_ISPACKED 02
+#define REF_ISSYMREF 0x01
+#define REF_ISPACKED 0x02
+#define REF_ISBROKEN 0x04
 
 /*
  * Calls the specified function for each ref file until it returns nonzero,
@@ -57,16 +58,18 @@ extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refn
  */
 extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags);
 extern void clear_extra_refs(void);
-extern int ref_exists(char *);
+extern int ref_exists(const char *);
 
-extern int peel_ref(const char *, unsigned char *);
+extern int peel_ref(const char *refname, unsigned char *sha1);
 
 /** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
-extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
+extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1);
 
 /** Locks any ref (for 'HEAD' type refs). */
 #define REF_NODEREF    0x01
-extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags);
+extern struct ref_lock *lock_any_ref_for_update(const char *refname,
+                                               const unsigned char *old_sha1,
+                                               int flags);
 
 /** Close the file descriptor owned by a lock and return the status */
 extern int close_ref(struct ref_lock *lock);
@@ -80,16 +83,26 @@ extern void unlock_ref(struct ref_lock *lock);
 /** Writes sha1 into the ref specified by the lock. **/
 extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
 
+/*
+ * Invalidate the reference cache for the specified submodule.  Use
+ * submodule=NULL to invalidate the cache for the main module.  This
+ * function must be called if references are changed via a mechanism
+ * other than the refs API.
+ */
+extern void invalidate_ref_cache(const char *submodule);
+
 /** Setup reflog before using. **/
 int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
 
 /** Reads log for the value of ref during at_time. **/
-extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
+extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,
+                      unsigned char *sha1, char **msg,
+                      unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
 
 /* iterate over reflog entries */
 typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
-int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
-int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, void *cb_data);
+int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
+int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long, void *cb_data);
 
 /*
  * Calls the specified function for each reflog file until it returns nonzero,
@@ -97,20 +110,35 @@ int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, voi
  */
 extern int for_each_reflog(each_ref_fn, void *);
 
-#define CHECK_REF_FORMAT_OK 0
-#define CHECK_REF_FORMAT_ERROR (-1)
-#define CHECK_REF_FORMAT_ONELEVEL (-2)
-#define CHECK_REF_FORMAT_WILDCARD (-3)
-extern int check_ref_format(const char *target);
+#define REFNAME_ALLOW_ONELEVEL 1
+#define REFNAME_REFSPEC_PATTERN 2
+#define REFNAME_DOT_COMPONENT 4
+
+/*
+ * Return 0 iff refname has the correct format for a refname according
+ * to the rules described in Documentation/git-check-ref-format.txt.
+ * If REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level
+ * reference names.  If REFNAME_REFSPEC_PATTERN is set in flags, then
+ * allow a "*" wildcard character in place of one of the name
+ * components.  No leading or repeated slashes are accepted.  If
+ * REFNAME_DOT_COMPONENT is set in flags, then allow refname
+ * components to start with "." (but not a whole component equal to
+ * "." or "..").
+ */
+extern int check_refname_format(const char *refname, int flags);
 
 extern const char *prettify_refname(const char *refname);
-extern char *shorten_unambiguous_ref(const char *ref, int strict);
+extern char *shorten_unambiguous_ref(const char *refname, int strict);
 
 /** rename ref, return 0 on success **/
 extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
 
-/** resolve ref in nested "gitlink" repository */
-extern int resolve_gitlink_ref(const char *name, const char *refname, unsigned char *result);
+/**
+ * Resolve refname in the nested "gitlink" repository that is located
+ * at path.  If the resolution is successful, return 0 and set sha1 to
+ * the name of the object; otherwise, return a non-zero value.
+ */
+extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1);
 
 /** lock a ref and then write its file */
 enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_ON_ERR };
index 0aa4bfed309d6c439fac4ff2a0df6a468307e7bf..d159fe7f3433ccf6e8c8908961736951e42b9c35 100644 (file)
@@ -115,7 +115,7 @@ static struct discovery* discover_refs(const char *service)
        http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);
 
        /* try again with "plain" url (no ? or & appended) */
-       if (http_ret != HTTP_OK) {
+       if (http_ret != HTTP_OK && http_ret != HTTP_NOAUTH) {
                free(refs_url);
                strbuf_reset(&buffer);
 
@@ -188,7 +188,7 @@ static int write_discovery(int in, int out, void *data)
        return err;
 }
 
-static struct ref *parse_git_refs(struct discovery *heads)
+static struct ref *parse_git_refs(struct discovery *heads, int for_push)
 {
        struct ref *list = NULL;
        struct async async;
@@ -200,7 +200,8 @@ static struct ref *parse_git_refs(struct discovery *heads)
 
        if (start_async(&async))
                die("cannot start thread to parse advertised refs");
-       get_remote_heads(async.out, &list, 0, NULL, 0, NULL);
+       get_remote_heads(async.out, &list,
+                       for_push ? REF_NORMAL : 0, NULL);
        close(async.out);
        if (finish_async(&async))
                die("ref parsing thread failed");
@@ -268,7 +269,7 @@ static struct ref *get_refs(int for_push)
                heads = discover_refs("git-upload-pack");
 
        if (heads->proto_git)
-               return parse_git_refs(heads);
+               return parse_git_refs(heads, for_push);
        return parse_info_refs(heads);
 }
 
@@ -769,7 +770,9 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
                argv[argc++] = "--thin";
        if (options.dry_run)
                argv[argc++] = "--dry-run";
-       if (options.verbosity > 1)
+       if (options.verbosity == 0)
+               argv[argc++] = "--quiet";
+       else if (options.verbosity > 1)
                argv[argc++] = "--verbose";
        argv[argc++] = url;
        for (i = 0; i < nr_spec; i++)
@@ -804,7 +807,7 @@ static int push(int nr_spec, char **specs)
 static void parse_push(struct strbuf *buf)
 {
        char **specs = NULL;
-       int alloc_spec = 0, nr_spec = 0, i;
+       int alloc_spec = 0, nr_spec = 0, i, ret;
 
        do {
                if (!prefixcmp(buf->buf, "push ")) {
@@ -821,12 +824,13 @@ static void parse_push(struct strbuf *buf)
                        break;
        } while (1);
 
-       if (push(nr_spec, specs))
-               exit(128); /* error already reported */
-
+       ret = push(nr_spec, specs);
        printf("\n");
        fflush(stdout);
 
+       if (ret)
+               exit(128); /* error already reported */
+
  free_specs:
        for (i = 0; i < nr_spec; i++)
                free(specs[i]);
@@ -859,7 +863,7 @@ int main(int argc, const char **argv)
 
        url = strbuf_detach(&buf, NULL);
 
-       http_init(remote);
+       http_init(remote, url, 0);
 
        do {
                if (strbuf_getline(&buf, stdin, '\n') == EOF) {
index 6ececc4a7703f6d54caf919b1162ed454b45a47e..af597b3a625824d05196c8ed4754fb4020026cc2 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -482,7 +482,7 @@ static void read_config(void)
                return;
        default_remote_name = xstrdup("origin");
        current_branch = NULL;
-       head_ref = resolve_ref("HEAD", sha1, 0, &flag);
+       head_ref = resolve_ref_unsafe("HEAD", sha1, 0, &flag);
        if (head_ref && (flag & REF_ISSYMREF) &&
            !prefixcmp(head_ref, "refs/heads/")) {
                current_branch =
@@ -492,23 +492,6 @@ static void read_config(void)
        alias_all_urls();
 }
 
-/*
- * We need to make sure the remote-tracking branches are well formed, but a
- * wildcard refspec in "struct refspec" must have a trailing slash. We
- * temporarily drop the trailing '/' while calling check_ref_format(),
- * and put it back.  The caller knows that a CHECK_REF_FORMAT_ONELEVEL
- * error return is Ok for a wildcard refspec.
- */
-static int verify_refname(char *name, int is_glob)
-{
-       int result;
-
-       result = check_ref_format(name);
-       if (is_glob && result == CHECK_REF_FORMAT_WILDCARD)
-               result = CHECK_REF_FORMAT_OK;
-       return result;
-}
-
 /*
  * This function frees a refspec array.
  * Warning: code paths should be checked to ensure that the src
@@ -532,13 +515,13 @@ static void free_refspecs(struct refspec *refspec, int nr_refspec)
 static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify)
 {
        int i;
-       int st;
        struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec);
 
        for (i = 0; i < nr_refspec; i++) {
                size_t llen;
                int is_glob;
                const char *lhs, *rhs;
+               int flags;
 
                is_glob = 0;
 
@@ -576,6 +559,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
 
                rs[i].pattern = is_glob;
                rs[i].src = xstrndup(lhs, llen);
+               flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
 
                if (fetch) {
                        /*
@@ -585,26 +569,20 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
                         */
                        if (!*rs[i].src)
                                ; /* empty is ok */
-                       else {
-                               st = verify_refname(rs[i].src, is_glob);
-                               if (st && st != CHECK_REF_FORMAT_ONELEVEL)
-                                       goto invalid;
-                       }
+                       else if (check_refname_format(rs[i].src, flags))
+                               goto invalid;
                        /*
                         * RHS
                         * - missing is ok, and is same as empty.
                         * - empty is ok; it means not to store.
                         * - otherwise it must be a valid looking ref.
                         */
-                       if (!rs[i].dst) {
+                       if (!rs[i].dst)
                                ; /* ok */
-                       } else if (!*rs[i].dst) {
+                       else if (!*rs[i].dst)
                                ; /* ok */
-                       } else {
-                               st = verify_refname(rs[i].dst, is_glob);
-                               if (st && st != CHECK_REF_FORMAT_ONELEVEL)
-                                       goto invalid;
-                       }
+                       else if (check_refname_format(rs[i].dst, flags))
+                               goto invalid;
                } else {
                        /*
                         * LHS
@@ -616,8 +594,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
                        if (!*rs[i].src)
                                ; /* empty is ok */
                        else if (is_glob) {
-                               st = verify_refname(rs[i].src, is_glob);
-                               if (st && st != CHECK_REF_FORMAT_ONELEVEL)
+                               if (check_refname_format(rs[i].src, flags))
                                        goto invalid;
                        }
                        else
@@ -630,14 +607,12 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
                         * - otherwise it must be a valid looking ref.
                         */
                        if (!rs[i].dst) {
-                               st = verify_refname(rs[i].src, is_glob);
-                               if (st && st != CHECK_REF_FORMAT_ONELEVEL)
+                               if (check_refname_format(rs[i].src, flags))
                                        goto invalid;
                        } else if (!*rs[i].dst) {
                                goto invalid;
                        } else {
-                               st = verify_refname(rs[i].dst, is_glob);
-                               if (st && st != CHECK_REF_FORMAT_ONELEVEL)
+                               if (check_refname_format(rs[i].dst, flags))
                                        goto invalid;
                        }
                }
@@ -1032,7 +1007,7 @@ static char *guess_ref(const char *name, struct ref *peer)
        struct strbuf buf = STRBUF_INIT;
        unsigned char sha1[20];
 
-       const char *r = resolve_ref(peer->name, sha1, 1, NULL);
+       const char *r = resolve_ref_unsafe(peer->name, sha1, 1, NULL);
        if (!r)
                return NULL;
 
@@ -1083,7 +1058,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
                unsigned char sha1[20];
                int flag;
 
-               dst_value = resolve_ref(matched_src->name, sha1, 1, &flag);
+               dst_value = resolve_ref_unsafe(matched_src->name, sha1, 1, &flag);
                if (!dst_value ||
                    ((flag & REF_ISSYMREF) &&
                     prefixcmp(dst_value, "refs/heads/")))
@@ -1167,12 +1142,15 @@ static struct ref **tail_ref(struct ref **head)
 }
 
 /*
- * Note. This is used only by "push"; refspec matching rules for
- * push and fetch are subtly different, so do not try to reuse it
- * without thinking.
+ * Given the set of refs the local repository has, the set of refs the
+ * remote repository has, and the refspec used for push, determine
+ * what remote refs we will update and with what value by setting
+ * peer_ref (which object is being pushed) and force (if the push is
+ * forced) in elements of "dst". The function may add new elements to
+ * dst (e.g. pushing to a new branch, done in match_explicit_refs).
  */
-int match_refs(struct ref *src, struct ref **dst,
-              int nr_refspec, const char **refspec, int flags)
+int match_push_refs(struct ref *src, struct ref **dst,
+                   int nr_refspec, const char **refspec, int flags)
 {
        struct refspec *rs;
        int send_all = flags & MATCH_REFS_ALL;
@@ -1424,8 +1402,8 @@ int get_fetch_map(const struct ref *remote_refs,
 
        for (rmp = &ref_map; *rmp; ) {
                if ((*rmp)->peer_ref) {
-                       int st = check_ref_format((*rmp)->peer_ref->name + 5);
-                       if (st && st != CHECK_REF_FORMAT_ONELEVEL) {
+                       if (check_refname_format((*rmp)->peer_ref->name + 5,
+                               REFNAME_ALLOW_ONELEVEL)) {
                                struct ref *ignore = *rmp;
                                error("* Ignoring funny ref '%s' locally",
                                      (*rmp)->peer_ref->name);
@@ -1529,13 +1507,13 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
         * nothing to report.
         */
        base = branch->merge[0]->dst;
-       if (!resolve_ref(base, sha1, 1, NULL))
+       if (read_ref(base, sha1))
                return 0;
        theirs = lookup_commit_reference(sha1);
        if (!theirs)
                return 0;
 
-       if (!resolve_ref(branch->refname, sha1, 1, NULL))
+       if (read_ref(branch->refname, sha1))
                return 0;
        ours = lookup_commit_reference(sha1);
        if (!ours)
@@ -1594,19 +1572,29 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb)
        base = branch->merge[0]->dst;
        base = shorten_unambiguous_ref(base, 0);
        if (!num_theirs)
-               strbuf_addf(sb, "Your branch is ahead of '%s' "
-                           "by %d commit%s.\n",
-                           base, num_ours, (num_ours == 1) ? "" : "s");
+               strbuf_addf(sb,
+                       Q_("Your branch is ahead of '%s' by %d commit.\n",
+                          "Your branch is ahead of '%s' by %d commits.\n",
+                          num_ours),
+                       base, num_ours);
        else if (!num_ours)
-               strbuf_addf(sb, "Your branch is behind '%s' "
-                           "by %d commit%s, "
-                           "and can be fast-forwarded.\n",
-                           base, num_theirs, (num_theirs == 1) ? "" : "s");
+               strbuf_addf(sb,
+                       Q_("Your branch is behind '%s' by %d commit, "
+                              "and can be fast-forwarded.\n",
+                          "Your branch is behind '%s' by %d commits, "
+                              "and can be fast-forwarded.\n",
+                          num_theirs),
+                       base, num_theirs);
        else
-               strbuf_addf(sb, "Your branch and '%s' have diverged,\n"
-                           "and have %d and %d different commit(s) each, "
-                           "respectively.\n",
-                           base, num_ours, num_theirs);
+               strbuf_addf(sb,
+                       Q_("Your branch and '%s' have diverged,\n"
+                              "and have %d and %d different commit each, "
+                              "respectively.\n",
+                          "Your branch and '%s' have diverged,\n"
+                              "and have %d and %d different commits each, "
+                              "respectively.\n",
+                          num_theirs),
+                       base, num_ours, num_theirs);
        return 1;
 }
 
@@ -1617,7 +1605,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1, int fla
        int len;
 
        /* we already know it starts with refs/ to get here */
-       if (check_ref_format(refname + 5))
+       if (check_refname_format(refname + 5, 0))
                return 0;
 
        len = strlen(refname) + 1;
index f2541b5a29a552fb9fab736f5b47a545c2eee383..b3955983ba5caea698a78868abcbb54451b6daa8 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -96,8 +96,8 @@ 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);
+int match_push_refs(struct ref *src, struct ref **dst,
+                   int nr_refspec, const char **refspec, int all);
 void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
        int force_update);
 
index 3d2deedaf8e50c6a71b9750dacf602bb5b9238b9..18be62b3169095b908cb521cf9be211a9339029d 100644 (file)
@@ -40,6 +40,47 @@ char *path_name(const struct name_path *path, const char *name)
        return n;
 }
 
+static int show_path_component_truncated(FILE *out, const char *name, int len)
+{
+       int cnt;
+       for (cnt = 0; cnt < len; cnt++) {
+               int ch = name[cnt];
+               if (!ch || ch == '\n')
+                       return -1;
+               fputc(ch, out);
+       }
+       return len;
+}
+
+static int show_path_truncated(FILE *out, const struct name_path *path)
+{
+       int emitted, ours;
+
+       if (!path)
+               return 0;
+       emitted = show_path_truncated(out, path->up);
+       if (emitted < 0)
+               return emitted;
+       if (emitted)
+               fputc('/', out);
+       ours = show_path_component_truncated(out, path->elem, path->elem_len);
+       if (ours < 0)
+               return ours;
+       return ours || emitted;
+}
+
+void show_object_with_name(FILE *out, struct object *obj, const struct name_path *path, const char *component)
+{
+       struct name_path leaf;
+       leaf.up = (struct name_path *)path;
+       leaf.elem = component;
+       leaf.elem_len = strlen(component);
+
+       fprintf(out, "%s ", sha1_to_hex(obj->sha1));
+       show_path_truncated(out, &leaf);
+       fputc('\n', out);
+}
+
 void add_object(struct object *obj,
                struct object_array *p,
                struct name_path *path,
@@ -185,6 +226,13 @@ static struct object *get_reference(struct rev_info *revs, const char *name, con
        return object;
 }
 
+void add_pending_sha1(struct rev_info *revs, const char *name,
+                     const unsigned char *sha1, unsigned int flags)
+{
+       struct object *object = get_reference(revs, name, sha1, flags);
+       add_pending_object(revs, object, name);
+}
+
 static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
 {
        unsigned long flags = object->flags;
@@ -368,7 +416,7 @@ static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)
 static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
 {
        struct commit_list **pp, *parent;
-       int tree_changed = 0, tree_same = 0;
+       int tree_changed = 0, tree_same = 0, nth_parent = 0;
 
        /*
         * If we don't do pruning, everything is interesting
@@ -396,6 +444,14 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
        while ((parent = *pp) != NULL) {
                struct commit *p = parent->item;
 
+               /*
+                * Do not compare with later parents when we care only about
+                * the first parent chain, in order to avoid derailing the
+                * traversal to follow a side branch that brought everything
+                * in the path we are limited to by the pathspec.
+                */
+               if (revs->first_parent_only && nth_parent++)
+                       break;
                if (parse_commit(p) < 0)
                        die("cannot simplify commit %s (because of %s)",
                            sha1_to_hex(commit->object.sha1),
@@ -856,7 +912,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag,
        struct object *object = get_reference(cb->all_revs, path, sha1,
                                              cb->all_flags);
        add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
-       add_pending_object(cb->all_revs, object, path);
+       add_pending_sha1(cb->all_revs, path, sha1, cb->all_flags);
        return 0;
 }
 
@@ -1377,6 +1433,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->tree_objects = 1;
                revs->blob_objects = 1;
                revs->edge_hint = 1;
+       } else if (!strcmp(arg, "--verify-objects")) {
+               revs->tag_objects = 1;
+               revs->tree_objects = 1;
+               revs->blob_objects = 1;
+               revs->verify_objects = 1;
        } else if (!strcmp(arg, "--unpacked")) {
                revs->unpacked = 1;
        } else if (!prefixcmp(arg, "--unpacked=")) {
@@ -1416,6 +1477,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->show_notes = 1;
                revs->show_notes_given = 1;
                revs->notes_opt.use_default_notes = 1;
+       } else if (!strcmp(arg, "--show-signature")) {
+               revs->show_signature = 1;
        } else if (!prefixcmp(arg, "--show-notes=") ||
                   !prefixcmp(arg, "--notes=")) {
                struct strbuf buf = STRBUF_INIT;
@@ -2004,7 +2067,8 @@ int prepare_revision_walk(struct rev_info *revs)
                }
                e++;
        }
-       free(list);
+       if (!revs->leak_pending)
+               free(list);
 
        if (revs->no_walk)
                return 0;
@@ -2072,7 +2136,6 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
        if (!opt->grep_filter.pattern_list && !opt->grep_filter.header_list)
                return 1;
        return grep_buffer(&opt->grep_filter,
-                          NULL, /* we say nothing, not even filename */
                           commit->buffer, strlen(commit->buffer));
 }
 
index 93d31556cb44d3e4c5162d21c8e92d572a36508e..b8e9223954a5d66e01bd1eb342a936495aa67ad1 100644 (file)
@@ -73,6 +73,7 @@ struct rev_info {
                        tag_objects:1,
                        tree_objects:1,
                        blob_objects:1,
+                       verify_objects:1,
                        edge_hint:1,
                        limited:1,
                        unpacked:1,
@@ -109,6 +110,7 @@ struct rev_info {
                        show_merge:1,
                        show_notes:1,
                        show_notes_given:1,
+                       show_signature:1,
                        pretty_given:1,
                        abbrev_commit:1,
                        abbrev_commit_given:1,
@@ -117,6 +119,7 @@ struct rev_info {
                        date_mode_explicit:1,
                        preserve_subject:1;
        unsigned int    disable_stdin:1;
+       unsigned int    leak_pending:1;
 
        enum date_mode date_mode;
 
@@ -205,12 +208,15 @@ struct name_path {
 
 char *path_name(const struct name_path *path, const char *name);
 
+extern void show_object_with_name(FILE *, struct object *, const struct name_path *, const char *);
+
 extern void add_object(struct object *obj,
                       struct object_array *p,
                       struct name_path *path,
                       const char *name);
 
 extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name);
+extern void add_pending_sha1(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags);
 
 extern void add_head_to_pending(struct rev_info *);
 
index 1c5104388429e50722769358d5d89948c55bb3aa..1db8abf9843516576f30f8105bbfdd66487db6e1 100644 (file)
@@ -1,8 +1,66 @@
 #include "cache.h"
 #include "run-command.h"
 #include "exec_cmd.h"
+#include "sigchain.h"
 #include "argv-array.h"
 
+struct child_to_clean {
+       pid_t pid;
+       struct child_to_clean *next;
+};
+static struct child_to_clean *children_to_clean;
+static int installed_child_cleanup_handler;
+
+static void cleanup_children(int sig)
+{
+       while (children_to_clean) {
+               struct child_to_clean *p = children_to_clean;
+               children_to_clean = p->next;
+               kill(p->pid, sig);
+               free(p);
+       }
+}
+
+static void cleanup_children_on_signal(int sig)
+{
+       cleanup_children(sig);
+       sigchain_pop(sig);
+       raise(sig);
+}
+
+static void cleanup_children_on_exit(void)
+{
+       cleanup_children(SIGTERM);
+}
+
+static void mark_child_for_cleanup(pid_t pid)
+{
+       struct child_to_clean *p = xmalloc(sizeof(*p));
+       p->pid = pid;
+       p->next = children_to_clean;
+       children_to_clean = p;
+
+       if (!installed_child_cleanup_handler) {
+               atexit(cleanup_children_on_exit);
+               sigchain_push_common(cleanup_children_on_signal);
+               installed_child_cleanup_handler = 1;
+       }
+}
+
+static void clear_child_for_cleanup(pid_t pid)
+{
+       struct child_to_clean **last, *p;
+
+       last = &children_to_clean;
+       for (p = children_to_clean; p; p = p->next) {
+               if (p->pid == pid) {
+                       *last = p->next;
+                       free(p);
+                       return;
+               }
+       }
+}
+
 static inline void close_pair(int fd[2])
 {
        close(fd[0]);
@@ -130,6 +188,9 @@ static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
        } else {
                error("waitpid is confused (%s)", argv0);
        }
+
+       clear_child_for_cleanup(pid);
+
        errno = failed_errno;
        return code;
 }
@@ -292,6 +353,8 @@ fail_pipe:
        if (cmd->pid < 0)
                error("cannot fork() for %s: %s", cmd->argv[0],
                        strerror(failed_errno = errno));
+       else if (cmd->clean_on_exit)
+               mark_child_for_cleanup(cmd->pid);
 
        /*
         * Wait for child's execvp. If the execvp succeeds (or if fork()
@@ -312,6 +375,7 @@ fail_pipe:
                cmd->pid = -1;
        }
        close(notify_pipe[0]);
+
 }
 #else
 {
@@ -356,6 +420,8 @@ fail_pipe:
        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->clean_on_exit && cmd->pid >= 0)
+               mark_child_for_cleanup(cmd->pid);
 
        if (cmd->env)
                free_environ(env);
@@ -431,6 +497,7 @@ static void prepare_run_command_v_opt(struct child_process *cmd,
        cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
        cmd->silent_exec_failure = opt & RUN_SILENT_EXEC_FAILURE ? 1 : 0;
        cmd->use_shell = opt & RUN_USING_SHELL ? 1 : 0;
+       cmd->clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;
 }
 
 int run_command_v_opt(const char **argv, int opt)
@@ -540,6 +607,8 @@ int start_async(struct async *async)
                exit(!!async->proc(proc_in, proc_out, async->data));
        }
 
+       mark_child_for_cleanup(async->pid);
+
        if (need_in)
                close(fdin[0]);
        else if (async->in)
index 56491b9f2344541c02bd0da2928a535f11193bd8..44f7d2bd42ddcf2d65722b7e54bea69645d167a2 100644 (file)
@@ -38,6 +38,7 @@ struct child_process {
        unsigned silent_exec_failure:1;
        unsigned stdout_to_stderr:1;
        unsigned use_shell:1;
+       unsigned clean_on_exit:1;
        void (*preexec_cb)(void);
 };
 
@@ -52,6 +53,7 @@ extern int run_hook(const char *index_file, const char *name, ...);
 #define RUN_COMMAND_STDOUT_TO_STDERR 4
 #define RUN_SILENT_EXEC_FAILURE 8
 #define RUN_USING_SHELL 16
+#define RUN_CLEAN_ON_EXIT 32
 int run_command_v_opt(const char **argv, int opt);
 
 /*
diff --git a/sequencer.c b/sequencer.c
new file mode 100644 (file)
index 0000000..d1f28a6
--- /dev/null
@@ -0,0 +1,13 @@
+#include "cache.h"
+#include "sequencer.h"
+#include "strbuf.h"
+#include "dir.h"
+
+void remove_sequencer_state(void)
+{
+       struct strbuf seq_dir = STRBUF_INIT;
+
+       strbuf_addf(&seq_dir, "%s", git_path(SEQ_DIR));
+       remove_dir_recursively(&seq_dir, 0);
+       strbuf_release(&seq_dir);
+}
diff --git a/sequencer.h b/sequencer.h
new file mode 100644 (file)
index 0000000..2d4528f
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef SEQUENCER_H
+#define SEQUENCER_H
+
+#define SEQ_DIR                "sequencer"
+#define SEQ_HEAD_FILE  "sequencer/head"
+#define SEQ_TODO_FILE  "sequencer/todo"
+#define SEQ_OPTS_FILE  "sequencer/opts"
+
+/* Removes SEQ_DIR. */
+extern void remove_sequencer_state(void);
+
+#endif
diff --git a/setup.c b/setup.c
index 27c1d4787a2c2efd8420a225b43e36b76464ce42..7a3618fab774c0c26a99c6e23b4fcf726e5dc1ad 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -236,38 +236,6 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
        return pathspec;
 }
 
-const char *pathspec_prefix(const char *prefix, const char **pathspec)
-{
-       const char **p, *n, *prev;
-       unsigned long max;
-
-       if (!pathspec)
-               return prefix ? xmemdupz(prefix, strlen(prefix)) : NULL;
-
-       prev = NULL;
-       max = PATH_MAX;
-       for (p = pathspec; (n = *p) != NULL; p++) {
-               int i, len = 0;
-               for (i = 0; i < max; i++) {
-                       char c = n[i];
-                       if (prev && prev[i] != c)
-                               break;
-                       if (!c || c == '*' || c == '?')
-                               break;
-                       if (c == '/')
-                               len = i+1;
-               }
-               prev = n;
-               if (len < max) {
-                       max = len;
-                       if (!max)
-                               break;
-               }
-       }
-
-       return max ? xmemdupz(prev, max) : NULL;
-}
-
 /*
  * Test if it looks like we're at a git directory.
  * We want to see:
@@ -279,7 +247,7 @@ const char *pathspec_prefix(const char *prefix, const char **pathspec)
  *    a proper "ref:", or a regular file HEAD that has a properly
  *    formatted sha1 object name.
  */
-static int is_git_directory(const char *suspect)
+int is_git_directory(const char *suspect)
 {
        char path[PATH_MAX];
        size_t len = strlen(suspect);
@@ -812,3 +780,10 @@ const char *setup_git_directory(void)
 {
        return setup_git_directory_gently(NULL);
 }
+
+const char *resolve_gitdir(const char *suspect)
+{
+       if (is_git_directory(suspect))
+               return suspect;
+       return read_gitfile(suspect);
+}
index e002056b85ce89c089dd09c1300461d60d81dffa..4f06a0e450359744528d3b125fb09eacebf1eb4a 100644 (file)
@@ -18,6 +18,7 @@
 #include "refs.h"
 #include "pack-revindex.h"
 #include "sha1-lookup.h"
+#include "bulk-checkin.h"
 
 #ifndef O_NOATIME
 #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@ -53,6 +54,8 @@ static struct cached_object empty_tree = {
        0
 };
 
+static struct packed_git *last_found_pack;
+
 static struct cached_object *find_cached_object(const unsigned char *sha1)
 {
        int i;
@@ -248,27 +251,30 @@ static int link_alt_odb_entry(const char * entry, int len, const char * relative
        const char *objdir = get_object_directory();
        struct alternate_object_database *ent;
        struct alternate_object_database *alt;
-       /* 43 = 40-byte + 2 '/' + terminating NUL */
-       int pfxlen = len;
-       int entlen = pfxlen + 43;
-       int base_len = -1;
+       int pfxlen, entlen;
+       struct strbuf pathbuf = STRBUF_INIT;
 
        if (!is_absolute_path(entry) && relative_base) {
-               /* Relative alt-odb */
-               if (base_len < 0)
-                       base_len = strlen(relative_base) + 1;
-               entlen += base_len;
-               pfxlen += base_len;
+               strbuf_addstr(&pathbuf, real_path(relative_base));
+               strbuf_addch(&pathbuf, '/');
        }
-       ent = xmalloc(sizeof(*ent) + entlen);
+       strbuf_add(&pathbuf, entry, len);
 
-       if (!is_absolute_path(entry) && relative_base) {
-               memcpy(ent->base, relative_base, base_len - 1);
-               ent->base[base_len - 1] = '/';
-               memcpy(ent->base + base_len, entry, len);
-       }
-       else
-               memcpy(ent->base, entry, pfxlen);
+       normalize_path_copy(pathbuf.buf, pathbuf.buf);
+
+       pfxlen = strlen(pathbuf.buf);
+
+       /*
+        * The trailing slash after the directory name is given by
+        * this function at the end. Remove duplicates.
+        */
+       while (pfxlen && pathbuf.buf[pfxlen-1] == '/')
+               pfxlen -= 1;
+
+       entlen = pfxlen + 43; /* '/' + 2 hex + '/' + 38 hex + NUL */
+       ent = xmalloc(sizeof(*ent) + entlen);
+       memcpy(ent->base, pathbuf.buf, pfxlen);
+       strbuf_release(&pathbuf);
 
        ent->name = ent->base + pfxlen + 1;
        ent->base[pfxlen + 3] = '/';
@@ -716,6 +722,8 @@ void free_pack_by_name(const char *pack_name)
                        close_pack_index(p);
                        free(p->bad_object_sha1);
                        *pp = p->next;
+                       if (last_found_pack == p)
+                               last_found_pack = NULL;
                        free(p);
                        return;
                }
@@ -1198,6 +1206,11 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
 
                if (!fstat(fd, &st)) {
                        *size = xsize_t(st.st_size);
+                       if (!*size) {
+                               /* mmap() is forbidden on empty files */
+                               error("object file %s is empty", sha1_file_name(sha1));
+                               return NULL;
+                       }
                        map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0);
                }
                close(fd);
@@ -1264,7 +1277,8 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
        while (c & 0x80) {
                if (len <= used || bitsizeof(long) <= shift) {
                        error("bad object header");
-                       return 0;
+                       size = used = 0;
+                       break;
                }
                c = buf[used++];
                size += (c & 0x7f) << shift;
@@ -1984,7 +1998,7 @@ off_t find_pack_entry_one(const unsigned char *sha1,
        return 0;
 }
 
-static int is_pack_valid(struct packed_git *p)
+int is_pack_valid(struct packed_git *p)
 {
        /* An already open pack is known to be valid. */
        if (p->pack_fd != -1)
@@ -2005,54 +2019,58 @@ static int is_pack_valid(struct packed_git *p)
        return !open_packed_git(p);
 }
 
+static int fill_pack_entry(const unsigned char *sha1,
+                          struct pack_entry *e,
+                          struct packed_git *p)
+{
+       off_t offset;
+
+       if (p->num_bad_objects) {
+               unsigned i;
+               for (i = 0; i < p->num_bad_objects; i++)
+                       if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
+                               return 0;
+       }
+
+       offset = find_pack_entry_one(sha1, p);
+       if (!offset)
+               return 0;
+
+       /*
+        * We are about to tell the caller where they can locate the
+        * requested object.  We better make sure the packfile is
+        * still here and can be accessed before supplying that
+        * answer, as it may have been deleted since the index was
+        * loaded!
+        */
+       if (!is_pack_valid(p)) {
+               warning("packfile %s cannot be accessed", p->pack_name);
+               return 0;
+       }
+       e->offset = offset;
+       e->p = p;
+       hashcpy(e->sha1, sha1);
+       return 1;
+}
+
 static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
 {
-       static struct packed_git *last_found = (void *)1;
        struct packed_git *p;
-       off_t offset;
 
        prepare_packed_git();
        if (!packed_git)
                return 0;
-       p = (last_found == (void *)1) ? packed_git : last_found;
 
-       do {
-               if (p->num_bad_objects) {
-                       unsigned i;
-                       for (i = 0; i < p->num_bad_objects; i++)
-                               if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
-                                       goto next;
-               }
+       if (last_found_pack && fill_pack_entry(sha1, e, last_found_pack))
+               return 1;
 
-               offset = find_pack_entry_one(sha1, p);
-               if (offset) {
-                       /*
-                        * We are about to tell the caller where they can
-                        * locate the requested object.  We better make
-                        * sure the packfile is still here and can be
-                        * accessed before supplying that answer, as
-                        * it may have been deleted since the index
-                        * was loaded!
-                        */
-                       if (!is_pack_valid(p)) {
-                               error("packfile %s cannot be accessed", p->pack_name);
-                               goto next;
-                       }
-                       e->offset = offset;
-                       e->p = p;
-                       hashcpy(e->sha1, sha1);
-                       last_found = p;
-                       return 1;
-               }
+       for (p = packed_git; p; p = p->next) {
+               if (p == last_found_pack || !fill_pack_entry(sha1, e, p))
+                       continue;
 
-               next:
-               if (p == last_found)
-                       p = packed_git;
-               else
-                       p = p->next;
-               if (p == last_found)
-                       p = p->next;
-       } while (p);
+               last_found_pack = p;
+               return 1;
+       }
        return 0;
 }
 
@@ -2447,15 +2465,15 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
        git_SHA_CTX c;
        unsigned char parano_sha1[20];
        char *filename;
-       static char tmpfile[PATH_MAX];
+       static char tmp_file[PATH_MAX];
 
        filename = sha1_file_name(sha1);
-       fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
+       fd = create_tmpfile(tmp_file, sizeof(tmp_file), filename);
        if (fd < 0) {
                if (errno == EACCES)
                        return error("insufficient permission for adding an object to repository database %s\n", get_object_directory());
                else
-                       return error("unable to create temporary sha1 filename %s: %s\n", tmpfile, strerror(errno));
+                       return error("unable to create temporary sha1 filename %s: %s\n", tmp_file, strerror(errno));
        }
 
        /* Set it up */
@@ -2500,12 +2518,12 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
                struct utimbuf utb;
                utb.actime = mtime;
                utb.modtime = mtime;
-               if (utime(tmpfile, &utb) < 0)
+               if (utime(tmp_file, &utb) < 0)
                        warning("failed utime() on %s: %s",
-                               tmpfile, strerror(errno));
+                               tmp_file, strerror(errno));
        }
 
-       return move_temp_to_file(tmpfile, filename);
+       return move_temp_to_file(tmp_file, filename);
 }
 
 int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
@@ -2613,7 +2631,7 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size,
        if ((type == OBJ_BLOB) && path) {
                struct strbuf nbuf = STRBUF_INIT;
                if (convert_to_git(path, buf, size, &nbuf,
-                                  write_object ? safe_crlf : 0)) {
+                                  write_object ? safe_crlf : SAFE_CRLF_FALSE)) {
                        buf = strbuf_detach(&nbuf, &size);
                        re_allocated = 1;
                }
@@ -2676,82 +2694,25 @@ static int index_core(unsigned char *sha1, int fd, size_t size,
 }
 
 /*
- * This creates one packfile per large blob, because the caller
- * immediately wants the result sha1, and fast-import can report the
- * object name via marks mechanism only by closing the created
- * packfile.
+ * This creates one packfile per large blob unless bulk-checkin
+ * machinery is "plugged".
  *
  * This also bypasses the usual "convert-to-git" dance, and that is on
  * purpose. We could write a streaming version of the converting
  * functions and insert that before feeding the data to fast-import
- * (or equivalent in-core API described above), but the primary
- * motivation for trying to stream from the working tree file and to
- * avoid mmaping it in core is to deal with large binary blobs, and
- * by definition they do _not_ want to get any conversion.
+ * (or equivalent in-core API described above). However, that is
+ * somewhat complicated, as we do not know the size of the filter
+ * result, which we need to know beforehand when writing a git object.
+ * Since the primary motivation for trying to stream from the working
+ * tree file and to avoid mmaping it in core is to deal with large
+ * binary blobs, they generally do not want to get any conversion, and
+ * callers should avoid this code path when filters are requested.
  */
 static int index_stream(unsigned char *sha1, int fd, size_t size,
                        enum object_type type, const char *path,
                        unsigned flags)
 {
-       struct child_process fast_import;
-       char export_marks[512];
-       const char *argv[] = { "fast-import", "--quiet", export_marks, NULL };
-       char tmpfile[512];
-       char fast_import_cmd[512];
-       char buf[512];
-       int len, tmpfd;
-
-       strcpy(tmpfile, git_path("hashstream_XXXXXX"));
-       tmpfd = git_mkstemp_mode(tmpfile, 0600);
-       if (tmpfd < 0)
-               die_errno("cannot create tempfile: %s", tmpfile);
-       if (close(tmpfd))
-               die_errno("cannot close tempfile: %s", tmpfile);
-       sprintf(export_marks, "--export-marks=%s", tmpfile);
-
-       memset(&fast_import, 0, sizeof(fast_import));
-       fast_import.in = -1;
-       fast_import.argv = argv;
-       fast_import.git_cmd = 1;
-       if (start_command(&fast_import))
-               die_errno("index-stream: git fast-import failed");
-
-       len = sprintf(fast_import_cmd, "blob\nmark :1\ndata %lu\n",
-                     (unsigned long) size);
-       write_or_whine(fast_import.in, fast_import_cmd, len,
-                      "index-stream: feeding fast-import");
-       while (size) {
-               char buf[10240];
-               size_t sz = size < sizeof(buf) ? size : sizeof(buf);
-               ssize_t actual;
-
-               actual = read_in_full(fd, buf, sz);
-               if (actual < 0)
-                       die_errno("index-stream: reading input");
-               if (write_in_full(fast_import.in, buf, actual) != actual)
-                       die_errno("index-stream: feeding fast-import");
-               size -= actual;
-       }
-       if (close(fast_import.in))
-               die_errno("index-stream: closing fast-import");
-       if (finish_command(&fast_import))
-               die_errno("index-stream: finishing fast-import");
-
-       tmpfd = open(tmpfile, O_RDONLY);
-       if (tmpfd < 0)
-               die_errno("index-stream: cannot open fast-import mark");
-       len = read(tmpfd, buf, sizeof(buf));
-       if (len < 0)
-               die_errno("index-stream: reading fast-import mark");
-       if (close(tmpfd) < 0)
-               die_errno("index-stream: closing fast-import mark");
-       if (unlink(tmpfile))
-               die_errno("index-stream: unlinking fast-import mark");
-       if (len != 44 ||
-           memcmp(":1 ", buf, 3) ||
-           get_sha1_hex(buf + 3, sha1))
-               die_errno("index-stream: unexpected fast-import mark: <%s>", buf);
-       return 0;
+       return index_bulk_checkin(sha1, fd, size, type, path, flags);
 }
 
 int index_fd(unsigned char *sha1, int fd, struct stat *st,
@@ -2762,7 +2723,8 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st,
 
        if (!S_ISREG(st->st_mode))
                ret = index_pipe(sha1, fd, type, path, flags);
-       else if (size <= big_file_threshold || type != OBJ_BLOB)
+       else if (size <= big_file_threshold || type != OBJ_BLOB ||
+                (path && would_convert_to_git(path, NULL, 0, 0)))
                ret = index_core(sha1, fd, size, type, path, flags);
        else
                ret = index_stream(sha1, fd, size, type, path, flags);
index ff5992acc971ac5a67fa3d4def1e0c064b90519f..03ffc2caaa6524a3361bc47a89e101ced2f0e987 100644 (file)
@@ -241,91 +241,6 @@ static int ambiguous_path(const char *path, int len)
        return slash;
 }
 
-/*
- * *string and *len will only be substituted, and *string returned (for
- * later free()ing) if the string passed in is a magic short-hand form
- * to name a branch.
- */
-static char *substitute_branch_name(const char **string, int *len)
-{
-       struct strbuf buf = STRBUF_INIT;
-       int ret = interpret_branch_name(*string, &buf);
-
-       if (ret == *len) {
-               size_t size;
-               *string = strbuf_detach(&buf, &size);
-               *len = size;
-               return (char *)*string;
-       }
-
-       return NULL;
-}
-
-int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
-{
-       char *last_branch = substitute_branch_name(&str, &len);
-       const char **p, *r;
-       int refs_found = 0;
-
-       *ref = NULL;
-       for (p = ref_rev_parse_rules; *p; p++) {
-               char fullref[PATH_MAX];
-               unsigned char sha1_from_ref[20];
-               unsigned char *this_result;
-               int flag;
-
-               this_result = refs_found ? sha1_from_ref : sha1;
-               mksnpath(fullref, sizeof(fullref), *p, len, str);
-               r = resolve_ref(fullref, this_result, 1, &flag);
-               if (r) {
-                       if (!refs_found++)
-                               *ref = xstrdup(r);
-                       if (!warn_ambiguous_refs)
-                               break;
-               } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD"))
-                       warning("ignoring dangling symref %s.", fullref);
-       }
-       free(last_branch);
-       return refs_found;
-}
-
-int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
-{
-       char *last_branch = substitute_branch_name(&str, &len);
-       const char **p;
-       int logs_found = 0;
-
-       *log = NULL;
-       for (p = ref_rev_parse_rules; *p; p++) {
-               struct stat st;
-               unsigned char hash[20];
-               char path[PATH_MAX];
-               const char *ref, *it;
-
-               mksnpath(path, sizeof(path), *p, len, str);
-               ref = resolve_ref(path, hash, 1, NULL);
-               if (!ref)
-                       continue;
-               if (!stat(git_path("logs/%s", path), &st) &&
-                   S_ISREG(st.st_mode))
-                       it = path;
-               else if (strcmp(ref, path) &&
-                        !stat(git_path("logs/%s", ref), &st) &&
-                        S_ISREG(st.st_mode))
-                       it = ref;
-               else
-                       continue;
-               if (!logs_found++) {
-                       *log = xstrdup(it);
-                       hashcpy(sha1, hash);
-               }
-               if (!warn_ambiguous_refs)
-                       break;
-       }
-       free(last_branch);
-       return logs_found;
-}
-
 static inline int upstream_mark(const char *string, int len)
 {
        const char *suffix[] = { "@{upstream}", "@{u}" };
@@ -501,12 +416,6 @@ struct object *peel_to_type(const char *name, int namelen,
 {
        if (name && !namelen)
                namelen = strlen(name);
-       if (!o) {
-               unsigned char sha1[20];
-               if (get_sha1_1(name, namelen, sha1))
-                       return NULL;
-               o = parse_object(sha1);
-       }
        while (1) {
                if (!o || (!o->parsed && !parse_object(o->sha1)))
                        return NULL;
@@ -972,9 +881,9 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
 {
        strbuf_branchname(sb, name);
        if (name[0] == '-')
-               return CHECK_REF_FORMAT_ERROR;
+               return -1;
        strbuf_splice(sb, 0, 0, "refs/heads/", 11);
-       return check_ref_format(sb->buf);
+       return check_refname_format(sb->buf, 0);
 }
 
 /*
diff --git a/shell.c b/shell.c
index abb862246ef01743424e4a447ee169f3fdbb9f51..84b237fef352e2f94f853897dbb5f2364d50962d 100644 (file)
--- a/shell.c
+++ b/shell.c
@@ -137,6 +137,8 @@ int main(int argc, char **argv)
        int devnull_fd;
        int count;
 
+       git_setup_gettext();
+
        git_extract_argv0_path(argv[0]);
 
        /*
index 63f9da53237d4233bede66a28e4bcf27d5b44af1..5a9eed7fd858b6f2e454421d68e884a21acb7a23 100644 (file)
@@ -11,6 +11,8 @@ int main(int argc, char **argv)
        unsigned int version;
        static unsigned int top_index[256];
 
+       git_setup_gettext();
+
        if (argc != 1)
                usage(show_index_usage);
        if (fread(top_index, 2 * 4, 1, stdin) != 1)
index 3ad2cc00160fbf24e1e4904bb37ce44e8c414ce5..5135d5950d9a5ea3ce8064e5491e53da17645da9 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -383,6 +383,22 @@ int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
        return 0;
 }
 
+int strbuf_getwholeline_fd(struct strbuf *sb, int fd, int term)
+{
+       strbuf_reset(sb);
+
+       while (1) {
+               char ch;
+               ssize_t len = xread(fd, &ch, 1);
+               if (len <= 0)
+                       return EOF;
+               strbuf_addch(sb, ch);
+               if (ch == term)
+                       break;
+       }
+       return 0;
+}
+
 int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint)
 {
        int fd, len;
@@ -397,3 +413,54 @@ int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint)
 
        return len;
 }
+
+void strbuf_add_lines(struct strbuf *out, const char *prefix,
+                     const char *buf, size_t size)
+{
+       while (size) {
+               const char *next = memchr(buf, '\n', size);
+               next = next ? (next + 1) : (buf + size);
+               strbuf_addstr(out, prefix);
+               strbuf_add(out, buf, next - buf);
+               size -= next - buf;
+               buf = next;
+       }
+       strbuf_complete_line(out);
+}
+
+static int is_rfc3986_reserved(char ch)
+{
+       switch (ch) {
+               case '!': case '*': case '\'': case '(': case ')': case ';':
+               case ':': case '@': case '&': case '=': case '+': case '$':
+               case ',': case '/': case '?': case '#': case '[': case ']':
+                       return 1;
+       }
+       return 0;
+}
+
+static int is_rfc3986_unreserved(char ch)
+{
+       return isalnum(ch) ||
+               ch == '-' || ch == '_' || ch == '.' || ch == '~';
+}
+
+void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len,
+                         int reserved)
+{
+       strbuf_grow(sb, len);
+       while (len--) {
+               char ch = *s++;
+               if (is_rfc3986_unreserved(ch) ||
+                   (!reserved && is_rfc3986_reserved(ch)))
+                       strbuf_addch(sb, ch);
+               else
+                       strbuf_addf(sb, "%%%02x", ch);
+       }
+}
+
+void strbuf_addstr_urlencode(struct strbuf *sb, const char *s,
+                            int reserved)
+{
+       strbuf_add_urlencode(sb, s, strlen(s), reserved);
+}
index 46a33f8c46985c4377071011d6ea48d6d3fe5331..3effaa86b68f7b600dfd854558b7ee6f5d6f2f5e 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -100,6 +100,14 @@ extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
 __attribute__((format (printf,2,0)))
 extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);
 
+extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *buf, size_t size);
+
+static inline void strbuf_complete_line(struct strbuf *sb)
+{
+       if (sb->len && sb->buf[sb->len - 1] != '\n')
+               strbuf_addch(sb, '\n');
+}
+
 extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);
 /* XXX: if read fails, any partial read is undone */
 extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);
@@ -108,6 +116,7 @@ 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 int strbuf_getwholeline_fd(struct strbuf *, int, int);
 
 extern void stripspace(struct strbuf *buf, int skip_comments);
 extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
@@ -115,4 +124,9 @@ extern int launch_editor(const char *path, struct strbuf *buffer, const char *co
 extern int strbuf_branchname(struct strbuf *sb, const char *name);
 extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name);
 
+extern void strbuf_add_urlencode(struct strbuf *, const char *, size_t,
+                                int reserved);
+extern void strbuf_addstr_urlencode(struct strbuf *, const char *,
+                                   int reserved);
+
 #endif /* STRBUF_H */
index 09a41b59befca75feb6fec95d6c98bb51e10b987..9a2806067954c55a27c068706b5bfe67a1189fd5 100644 (file)
@@ -371,27 +371,15 @@ static void collect_submodules_from_diff(struct diff_queue_struct *q,
 }
 
 
-static void commit_need_pushing(struct commit *commit, struct commit_list *parent, int *needs_pushing)
+static void commit_need_pushing(struct commit *commit, int *needs_pushing)
 {
-       const unsigned char (*parents)[20];
-       unsigned int i, n;
        struct rev_info rev;
 
-       n = commit_list_count(parent);
-       parents = xmalloc(n * sizeof(*parents));
-
-       for (i = 0; i < n; i++) {
-               hashcpy((unsigned char *)(parents + i), parent->item->object.sha1);
-               parent = parent->next;
-       }
-
        init_revisions(&rev, NULL);
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = collect_submodules_from_diff;
        rev.diffopt.format_callback_data = needs_pushing;
-       diff_tree_combined(commit->object.sha1, parents, n, 1, &rev);
-
-       free((void *)parents);
+       diff_tree_combined_merge(commit, 1, &rev);
 }
 
 int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name)
@@ -414,7 +402,7 @@ int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remote
                die("revision walk setup failed");
 
        while ((commit = get_revision(&rev)) && !needs_pushing)
-               commit_need_pushing(commit, commit->parents, &needs_pushing);
+               commit_need_pushing(commit, &needs_pushing);
 
        free(sha1_copy);
        strbuf_release(&remotes_arg);
@@ -689,7 +677,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
        cp.out = -1;
        cp.dir = path;
        if (start_command(&cp))
-               die("Could not run git status --porcelain");
+               die("Could not run 'git status --porcelain' in submodule %s", path);
 
        len = strbuf_read(&buf, cp.out, 1024);
        line = buf.buf;
@@ -714,7 +702,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
        close(cp.out);
 
        if (finish_command(&cp))
-               die("git status --porcelain failed");
+               die("'git status --porcelain' failed in submodule %s", path);
 
        strbuf_release(&buf);
        return dirty_submodule;
@@ -794,7 +782,7 @@ static void print_commit(struct commit *commit)
 
 int merge_submodule(unsigned char result[20], const char *path,
                    const unsigned char base[20], const unsigned char a[20],
-                   const unsigned char b[20])
+                   const unsigned char b[20], int search)
 {
        struct commit *commit_base, *commit_a, *commit_b;
        int parent_count;
@@ -849,6 +837,10 @@ int merge_submodule(unsigned char result[20], const char *path,
         * user needs to confirm the resolution.
         */
 
+       /* Skip the search if makes no sense to the calling context.  */
+       if (!search)
+               return 0;
+
        /* find commit which merges them */
        parent_count = find_first_merges(&merges, path, commit_a, commit_b);
        switch (parent_count) {
index 799c22d6c6a459756983420960bc099da20336b3..80e04f3c8cfe9a49865ef54f61efaa3bc67dca5c 100644 (file)
@@ -28,7 +28,7 @@ int fetch_populated_submodules(int num_options, const char **options,
                               int quiet);
 unsigned is_submodule_modified(const char *path, int ignore_untracked);
 int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
-                   const unsigned char a[20], const unsigned char b[20]);
+                   const unsigned char a[20], const unsigned char b[20], int search);
 int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name);
 
 #endif
index 9046ec98164f44811b67124e869353db4050ec06..b5048ab77b9d580c3cc3f97e98f9a83209f2edb9 100644 (file)
@@ -17,9 +17,9 @@ DEFAULT_TEST_TARGET ?= test
 # Shell quote;
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 
-T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
-TSVN = $(wildcard t91[0-9][0-9]-*.sh)
-TGITWEB = $(wildcard t95[0-9][0-9]-*.sh)
+T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh))
+TSVN = $(sort $(wildcard t91[0-9][0-9]-*.sh))
+TGITWEB = $(sort $(wildcard t95[0-9][0-9]-*.sh))
 
 all: $(DEFAULT_TEST_TARGET)
 
@@ -73,42 +73,4 @@ gitweb-test:
 valgrind:
        $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind"
 
-# Smoke testing targets
--include ../GIT-VERSION-FILE
-uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo unknown')
-uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo unknown')
-
-test-results:
-       mkdir -p test-results
-
-test-results/git-smoke.tar.gz: test-results
-       $(PERL_PATH) ./harness \
-               --archive="test-results/git-smoke.tar.gz" \
-               $(T)
-
-smoke: test-results/git-smoke.tar.gz
-
-SMOKE_UPLOAD_FLAGS =
-ifdef SMOKE_USERNAME
-       SMOKE_UPLOAD_FLAGS += -F username="$(SMOKE_USERNAME)" -F password="$(SMOKE_PASSWORD)"
-endif
-ifdef SMOKE_COMMENT
-       SMOKE_UPLOAD_FLAGS += -F comments="$(SMOKE_COMMENT)"
-endif
-ifdef SMOKE_TAGS
-       SMOKE_UPLOAD_FLAGS += -F tags="$(SMOKE_TAGS)"
-endif
-
-smoke_report: smoke
-       curl \
-               -H "Expect: " \
-               -F project=Git \
-               -F architecture="$(uname_M)" \
-               -F platform="$(uname_S)" \
-               -F revision="$(GIT_VERSION)" \
-               -F report_file=@test-results/git-smoke.tar.gz \
-               $(SMOKE_UPLOAD_FLAGS) \
-               http://smoke.git.nix.is/app/projects/process_add_report/1 \
-       | grep -v ^Redirecting
-
-.PHONY: pre-clean $(T) aggregate-results clean valgrind smoke smoke_report
+.PHONY: pre-clean $(T) aggregate-results clean valgrind
index c85abaffb3b8c2142e87f6e0525fa67c6b62c1a1..681e8b43200416fb44bdfc5dbee4dc369c92f12f 100644 (file)
--- a/t/README
+++ b/t/README
@@ -658,76 +658,3 @@ Then, at the top-level:
 That'll generate a detailed cover report in the "cover_db_html"
 directory, which you can then copy to a webserver, or inspect locally
 in a browser.
-
-Smoke testing
--------------
-
-The Git test suite has support for smoke testing. Smoke testing is
-when you submit the results of a test run to a central server for
-analysis and aggregation.
-
-Running a smoke tester is an easy and valuable way of contributing to
-Git development, particularly if you have access to an uncommon OS on
-obscure hardware.
-
-After building Git you can generate a smoke report like this in the
-"t" directory:
-
-    make clean smoke
-
-You can also pass arguments via the environment. This should make it
-faster:
-
-    GIT_TEST_OPTS='--root=/dev/shm' TEST_JOBS=10 make clean smoke
-
-The "smoke" target will run the Git test suite with Perl's
-"TAP::Harness" module, and package up the results in a .tar.gz archive
-with "TAP::Harness::Archive". The former is included with Perl v5.10.1
-or later, but you'll need to install the latter from the CPAN. See the
-"Test coverage" section above for how you might do that.
-
-Once the "smoke" target finishes you'll see a message like this:
-
-    TAP Archive created at <path to git>/t/test-results/git-smoke.tar.gz
-
-To upload the smoke report you need to have curl(1) installed, then
-do:
-
-    make smoke_report
-
-To upload the report anonymously. Hopefully that'll return something
-like "Reported #7 added.".
-
-If you're going to be uploading reports frequently please request a
-user account by E-Mailing gitsmoke@v.nix.is. Once you have a username
-and password you'll be able to do:
-
-    SMOKE_USERNAME=<username> SMOKE_PASSWORD=<password> make smoke_report
-
-You can also add an additional comment to attach to the report, and/or
-a comma separated list of tags:
-
-    SMOKE_USERNAME=<username> SMOKE_PASSWORD=<password> \
-        SMOKE_COMMENT=<comment> SMOKE_TAGS=<tags> \
-        make smoke_report
-
-Once the report is uploaded it'll be made available at
-http://smoke.git.nix.is, here's an overview of Recent Smoke Reports
-for Git:
-
-    http://smoke.git.nix.is/app/projects/smoke_reports/1
-
-The reports will also be mirrored to GitHub every few hours:
-
-    http://github.com/gitsmoke/smoke-reports
-
-The Smolder SQLite database is also mirrored and made available for
-download:
-
-    http://github.com/gitsmoke/smoke-database
-
-Note that the database includes hashed (with crypt()) user passwords
-and E-Mail addresses. Don't use a valuable password for the smoke
-service if you have an account, or an E-Mail address you don't want to
-be publicly known. The user accounts are just meant to be convenient
-labels, they're not meant to be secure.
index 292753f77c4daf5f3cb55de1c28269b13455544f..21d11d6c2d65982d94f933b2a673b744cbc2e28a 100644 (file)
@@ -16,6 +16,7 @@ our \$projectroot = "$safe_pwd";
 our \$project_maxdepth = 8;
 our \$home_link_str = 'projects';
 our \$site_name = '[localhost]';
+our \$site_html_head_string = '';
 our \$site_header = '';
 our \$site_footer = '';
 our \$home_text = 'indextext.html';
diff --git a/t/harness b/t/harness
deleted file mode 100755 (executable)
index f5c02f4..0000000
--- a/t/harness
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-use Getopt::Long ();
-use TAP::Harness::Archive;
-
-Getopt::Long::Parser->new(
-       config => [ qw/ pass_through / ],
-)->getoptions(
-       'jobs:1'    => \(my $jobs = $ENV{TEST_JOBS}),
-       'archive=s' => \my $archive,
-) or die "$0: Couldn't getoptions()";
-
-TAP::Harness::Archive->new({
-       jobs             => $jobs,
-       archive          => $archive,
-       ($ENV{GIT_TEST_OPTS}
-        ? (test_args    => [ split /\s+/, $ENV{GIT_TEST_OPTS} ])
-        : ()),
-       extra_properties => {},
-})->runtests(@ARGV);
diff --git a/t/lib-credential.sh b/t/lib-credential.sh
new file mode 100755 (executable)
index 0000000..4a37cd7
--- /dev/null
@@ -0,0 +1,254 @@
+#!/bin/sh
+
+# Try a set of credential helpers; the expected stdin,
+# stdout and stderr should be provided on stdin,
+# separated by "--".
+check() {
+       read_chunk >stdin &&
+       read_chunk >expect-stdout &&
+       read_chunk >expect-stderr &&
+       test-credential "$@" <stdin >stdout 2>stderr &&
+       test_cmp expect-stdout stdout &&
+       test_cmp expect-stderr stderr
+}
+
+read_chunk() {
+       while read line; do
+               case "$line" in
+               --) break ;;
+               *) echo "$line" ;;
+               esac
+       done
+}
+
+# Clear any residual data from previous tests. We only
+# need this when testing third-party helpers which read and
+# write outside of our trash-directory sandbox.
+#
+# Don't bother checking for success here, as it is
+# outside the scope of tests and represents a best effort to
+# clean up after ourselves.
+helper_test_clean() {
+       reject $1 https example.com store-user
+       reject $1 https example.com user1
+       reject $1 https example.com user2
+       reject $1 http path.tld user
+       reject $1 https timeout.tld user
+}
+
+reject() {
+       (
+               echo protocol=$2
+               echo host=$3
+               echo username=$4
+       ) | test-credential reject $1
+}
+
+helper_test() {
+       HELPER=$1
+
+       test_expect_success "helper ($HELPER) has no existing data" '
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               --
+               username=askpass-username
+               password=askpass-password
+               --
+               askpass: Username for '\''https://example.com'\'':
+               askpass: Password for '\''https://askpass-username@example.com'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) stores password" '
+               check approve $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               username=store-user
+               password=store-pass
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) can retrieve password" '
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               --
+               username=store-user
+               password=store-pass
+               --
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) requires matching protocol" '
+               check fill $HELPER <<-\EOF
+               protocol=http
+               host=example.com
+               --
+               username=askpass-username
+               password=askpass-password
+               --
+               askpass: Username for '\''http://example.com'\'':
+               askpass: Password for '\''http://askpass-username@example.com'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) requires matching host" '
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=other.tld
+               --
+               username=askpass-username
+               password=askpass-password
+               --
+               askpass: Username for '\''https://other.tld'\'':
+               askpass: Password for '\''https://askpass-username@other.tld'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) requires matching username" '
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               username=other
+               --
+               username=other
+               password=askpass-password
+               --
+               askpass: Password for '\''https://other@example.com'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) requires matching path" '
+               test_config credential.usehttppath true &&
+               check approve $HELPER <<-\EOF &&
+               protocol=http
+               host=path.tld
+               path=foo.git
+               username=user
+               password=pass
+               EOF
+               check fill $HELPER <<-\EOF
+               protocol=http
+               host=path.tld
+               path=bar.git
+               --
+               username=askpass-username
+               password=askpass-password
+               --
+               askpass: Username for '\''http://path.tld/bar.git'\'':
+               askpass: Password for '\''http://askpass-username@path.tld/bar.git'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) can forget host" '
+               check reject $HELPER <<-\EOF &&
+               protocol=https
+               host=example.com
+               EOF
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               --
+               username=askpass-username
+               password=askpass-password
+               --
+               askpass: Username for '\''https://example.com'\'':
+               askpass: Password for '\''https://askpass-username@example.com'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) can store multiple users" '
+               check approve $HELPER <<-\EOF &&
+               protocol=https
+               host=example.com
+               username=user1
+               password=pass1
+               EOF
+               check approve $HELPER <<-\EOF &&
+               protocol=https
+               host=example.com
+               username=user2
+               password=pass2
+               EOF
+               check fill $HELPER <<-\EOF &&
+               protocol=https
+               host=example.com
+               username=user1
+               --
+               username=user1
+               password=pass1
+               EOF
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               username=user2
+               --
+               username=user2
+               password=pass2
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) can forget user" '
+               check reject $HELPER <<-\EOF &&
+               protocol=https
+               host=example.com
+               username=user1
+               EOF
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               username=user1
+               --
+               username=user1
+               password=askpass-password
+               --
+               askpass: Password for '\''https://user1@example.com'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) remembers other user" '
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               username=user2
+               --
+               username=user2
+               password=pass2
+               EOF
+       '
+}
+
+helper_test_timeout() {
+       HELPER="$*"
+
+       test_expect_success "helper ($HELPER) times out" '
+               check approve "$HELPER" <<-\EOF &&
+               protocol=https
+               host=timeout.tld
+               username=user
+               password=pass
+               EOF
+               sleep 2 &&
+               check fill "$HELPER" <<-\EOF
+               protocol=https
+               host=timeout.tld
+               --
+               username=askpass-username
+               password=askpass-password
+               --
+               askpass: Username for '\''https://timeout.tld'\'':
+               askpass: Password for '\''https://askpass-username@timeout.tld'\'':
+               EOF
+       '
+}
+
+cat >askpass <<\EOF
+#!/bin/sh
+echo >&2 askpass: $*
+what=`echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z`
+echo "askpass-$what"
+EOF
+chmod +x askpass
+GIT_ASKPASS="$PWD/askpass"
+export GIT_ASKPASS
diff --git a/t/lib-gettext.sh b/t/lib-gettext.sh
new file mode 100644 (file)
index 0000000..0f76f6c
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_BUILD_DIR/po/build/locale"
+GIT_PO_PATH="$GIT_BUILD_DIR/po"
+export GIT_TEXTDOMAINDIR GIT_PO_PATH
+
+. "$GIT_BUILD_DIR"/git-sh-i18n
+
+if test_have_prereq GETTEXT && ! test_have_prereq GETTEXT_POISON
+then
+       # is_IS.UTF-8 on Solaris and FreeBSD, is_IS.utf8 on Debian
+       is_IS_locale=$(locale -a | sed -n '/^is_IS\.[uU][tT][fF]-*8$/{
+               p
+               q
+       }')
+       # is_IS.ISO8859-1 on Solaris and FreeBSD, is_IS.iso88591 on Debian
+       is_IS_iso_locale=$(locale -a | sed -n '/^is_IS\.[iI][sS][oO]8859-*1$/{
+               p
+               q
+       }')
+
+       # Export them as an environment variable so the t0202/test.pl Perl
+       # test can use it too
+       export is_IS_locale is_IS_iso_locale
+
+       if test -n "$is_IS_locale" &&
+               test $GIT_INTERNAL_GETTEXT_SH_SCHEME != "fallthrough"
+       then
+               # Some of the tests need the reference Icelandic locale
+               test_set_prereq GETTEXT_LOCALE
+
+               # Exporting for t0202/test.pl
+               GETTEXT_LOCALE=1
+               export GETTEXT_LOCALE
+               say "# lib-gettext: Found '$is_IS_locale' as an is_IS UTF-8 locale"
+       else
+               say "# lib-gettext: No is_IS UTF-8 locale available"
+       fi
+
+       if test -n "$is_IS_iso_locale" &&
+               test $GIT_INTERNAL_GETTEXT_SH_SCHEME != "fallthrough"
+       then
+               # Some of the tests need the reference Icelandic locale
+               test_set_prereq GETTEXT_ISO_LOCALE
+
+               say "# lib-gettext: Found '$is_IS_iso_locale' as an is_IS ISO-8859-1 locale"
+       else
+               say "# lib-gettext: No is_IS ISO-8859-1 locale available"
+       fi
+fi
diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh
new file mode 100644 (file)
index 0000000..a870f9a
--- /dev/null
@@ -0,0 +1,74 @@
+#
+# Library code for git-p4 tests
+#
+
+. ./test-lib.sh
+
+if ! test_have_prereq PYTHON; then
+       skip_all='skipping git-p4 tests; python not available'
+       test_done
+fi
+( p4 -h && p4d -h ) >/dev/null 2>&1 || {
+       skip_all='skipping git-p4 tests; no p4 or p4d'
+       test_done
+}
+
+GITP4="$GIT_BUILD_DIR/contrib/fast-import/git-p4"
+
+# Try to pick a unique port: guess a large number, then hope
+# no more than one of each test is running.
+#
+# This does not handle the case where somebody else is running the
+# same tests and has chosen the same ports.
+testid=${this_test#t}
+git_p4_test_start=9800
+P4DPORT=$((10669 + ($testid - $git_p4_test_start)))
+
+export P4PORT=localhost:$P4DPORT
+export P4CLIENT=client
+
+db="$TRASH_DIRECTORY/db"
+cli="$TRASH_DIRECTORY/cli"
+git="$TRASH_DIRECTORY/git"
+pidfile="$TRASH_DIRECTORY/p4d.pid"
+
+start_p4d() {
+       mkdir -p "$db" "$cli" "$git" &&
+       (
+               p4d -q -r "$db" -p $P4DPORT &
+               echo $! >"$pidfile"
+       ) &&
+       for i in 1 2 3 4 5 ; do
+               p4 info >/dev/null 2>&1 && break || true &&
+               echo waiting for p4d to start &&
+               sleep 1
+       done &&
+       # complain if it never started
+       p4 info >/dev/null &&
+       (
+               cd "$cli" &&
+               p4 client -i <<-EOF
+               Client: client
+               Description: client
+               Root: $cli
+               View: //depot/... //client/...
+               EOF
+       )
+}
+
+kill_p4d() {
+       pid=$(cat "$pidfile")
+       # it had better exist for the first kill
+       kill $pid &&
+       for i in 1 2 3 4 5 ; do
+               kill $pid >/dev/null 2>&1 || break
+               sleep 1
+       done &&
+       # complain if it would not die
+       test_must_fail kill $pid >/dev/null 2>&1 &&
+       rm -rf "$db" "$cli" "$pidfile"
+}
+
+cleanup_git() {
+       rm -rf "$git"
+}
diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh
new file mode 100755 (executable)
index 0000000..05824fa
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+gpg_version=`gpg --version 2>&1`
+if test $? = 127; then
+       say "You do not seem to have gpg installed"
+else
+       # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
+       # the gpg version 1.0.6 didn't parse trust packets correctly, so for
+       # that version, creation of signed tags using the generated key fails.
+       case "$gpg_version" in
+       'gpg (GnuPG) 1.0.6'*)
+               say "Your version of gpg (1.0.6) is too buggy for testing"
+               ;;
+       *)
+               # key generation info: gpg --homedir t/lib-gpg --gen-key
+               # Type DSA and Elgamal, size 2048 bits, no expiration date.
+               # Name and email: C O Mitter <committer@example.com>
+               # No password given, to enable non-interactive operation.
+               cp -R "$TEST_DIRECTORY"/lib-gpg ./gpghome
+               chmod 0700 gpghome
+               GNUPGHOME="$(pwd)/gpghome"
+               export GNUPGHOME
+               test_set_prereq GPG
+               ;;
+       esac
+fi
+
+sanitize_pgp() {
+       perl -ne '
+               /^-----END PGP/ and $in_pgp = 0;
+               print unless $in_pgp;
+               /^-----BEGIN PGP/ and $in_pgp = 1;
+       '
+}
diff --git a/t/lib-gpg/pubring.gpg b/t/lib-gpg/pubring.gpg
new file mode 100644 (file)
index 0000000..83855fa
Binary files /dev/null and b/t/lib-gpg/pubring.gpg differ
diff --git a/t/lib-gpg/random_seed b/t/lib-gpg/random_seed
new file mode 100644 (file)
index 0000000..8fed133
Binary files /dev/null and b/t/lib-gpg/random_seed differ
diff --git a/t/lib-gpg/secring.gpg b/t/lib-gpg/secring.gpg
new file mode 100644 (file)
index 0000000..d831cd9
Binary files /dev/null and b/t/lib-gpg/secring.gpg differ
diff --git a/t/lib-gpg/trustdb.gpg b/t/lib-gpg/trustdb.gpg
new file mode 100644 (file)
index 0000000..abace96
Binary files /dev/null and b/t/lib-gpg/trustdb.gpg differ
index b8996a373a7444b54823f36159ea1a8634e4aeee..f7dc0781d5d0ec5afd7f1d0898bffa17a9b1b00e 100644 (file)
@@ -81,8 +81,7 @@ prepare_httpd() {
 
        if test -n "$LIB_HTTPD_SSL"
        then
-               HTTPD_URL=https://127.0.0.1:$LIB_HTTPD_PORT
-               AUTH_HTTPD_URL=https://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT
+               HTTPD_PROTO=https
 
                RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \
                        -config "$TEST_PATH/ssl.cnf" \
@@ -93,9 +92,12 @@ prepare_httpd() {
                export GIT_SSL_NO_VERIFY
                HTTPD_PARA="$HTTPD_PARA -DSSL"
        else
-               HTTPD_URL=http://127.0.0.1:$LIB_HTTPD_PORT
-               AUTH_HTTPD_URL=http://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT
+               HTTPD_PROTO=http
        fi
+       HTTPD_DEST=127.0.0.1:$LIB_HTTPD_PORT
+       HTTPD_URL=$HTTPD_PROTO://$HTTPD_DEST
+       HTTPD_URL_USER=$HTTPD_PROTO://user%40host@$HTTPD_DEST
+       HTTPD_URL_USER_PASS=$HTTPD_PROTO://user%40host:user%40host@$HTTPD_DEST
 
        if test -n "$LIB_HTTPD_DAV" -o -n "$LIB_HTTPD_SVN"
        then
index 0a4cdfa93ece7d8a4177835b5569583c22303564..3c12b05d60849b4f3063527338140c717b720c5d 100644 (file)
@@ -92,6 +92,9 @@ SSLEngine On
        <Location /dumb/>
                Dav on
        </Location>
+       <Location /auth/dumb>
+               Dav on
+       </Location>
 </IfDefine>
 
 <IfDefine SVN>
index 19265c77cf0b1df828eea188d5f880cbb7497c63..51f3045ba4bde549b06ea669b1877f0e2db7c401 100755 (executable)
@@ -5,20 +5,16 @@ test_description=gitattributes
 . ./test-lib.sh
 
 attr_check () {
-
-       path="$1"
-       expect="$2"
+       path="$1" expect="$2"
 
        git $3 check-attr test -- "$path" >actual 2>err &&
        echo "$path: test: $2" >expect &&
        test_cmp expect actual &&
        test_line_count = 0 err
-
 }
 
 
 test_expect_success 'setup' '
-
        mkdir -p a/b/d a/c b &&
        (
                echo "[attr]notest !test"
@@ -41,29 +37,27 @@ test_expect_success 'setup' '
        (
                echo "global test=global"
        ) >"$HOME"/global-gitattributes &&
-       cat <<EOF >expect-all
-f: test: f
-a/f: test: f
-a/c/f: test: f
-a/g: test: a/g
-a/b/g: test: a/b/g
-b/g: test: unspecified
-a/b/h: test: a/b/h
-a/b/d/g: test: a/b/d/*
-onoff: test: unset
-offon: test: set
-no: notest: set
-no: test: unspecified
-a/b/d/no: notest: set
-a/b/d/no: test: a/b/d/*
-a/b/d/yes: notest: set
-a/b/d/yes: test: unspecified
-EOF
-
+       cat <<-EOF >expect-all
+       f: test: f
+       a/f: test: f
+       a/c/f: test: f
+       a/g: test: a/g
+       a/b/g: test: a/b/g
+       b/g: test: unspecified
+       a/b/h: test: a/b/h
+       a/b/d/g: test: a/b/d/*
+       onoff: test: unset
+       offon: test: set
+       no: notest: set
+       no: test: unspecified
+       a/b/d/no: notest: set
+       a/b/d/no: test: a/b/d/*
+       a/b/d/yes: notest: set
+       a/b/d/yes: test: unspecified
+       EOF
 '
 
 test_expect_success 'command line checks' '
-
        test_must_fail git check-attr &&
        test_must_fail git check-attr -- &&
        test_must_fail git check-attr test &&
@@ -73,11 +67,9 @@ test_expect_success 'command line checks' '
        echo "f" | test_must_fail git check-attr --stdin -- f &&
        echo "f" | test_must_fail git check-attr --stdin test -- f &&
        test_must_fail git check-attr "" -- f
-
 '
 
 test_expect_success 'attribute test' '
-
        attr_check f f &&
        attr_check a/f f &&
        attr_check a/c/f f &&
@@ -91,7 +83,6 @@ test_expect_success 'attribute test' '
        attr_check no unspecified &&
        attr_check a/b/d/no "a/b/d/*" &&
        attr_check a/b/d/yes unspecified
-
 '
 
 test_expect_success 'attribute matching is case sensitive when core.ignorecase=0' '
@@ -151,16 +142,13 @@ test_expect_success CASE_INSENSITIVE_FS 'additional case insensitivity tests' '
 '
 
 test_expect_success 'unnormalized paths' '
-
        attr_check ./f f &&
        attr_check ./a/g a/g &&
        attr_check a/./g a/g &&
        attr_check a/c/../b/g a/b/g
-
 '
 
 test_expect_success 'relative paths' '
-
        (cd a && attr_check ../f f) &&
        (cd a && attr_check f f) &&
        (cd a && attr_check i a/i) &&
@@ -169,7 +157,6 @@ test_expect_success 'relative paths' '
        (cd b && attr_check ../a/f f) &&
        (cd b && attr_check ../a/g a/g) &&
        (cd b && attr_check ../a/b/g a/b/g)
-
 '
 
 test_expect_success 'prefixes are not confused with leading directories' '
@@ -188,41 +175,43 @@ test_expect_success 'core.attributesfile' '
        attr_check global global &&
        git config core.attributesfile "~/global-gitattributes" &&
        attr_check global global &&
-       echo "global test=precedence" >> .gitattributes &&
+       echo "global test=precedence" >>.gitattributes &&
        attr_check global precedence
 '
 
 test_expect_success 'attribute test: read paths from stdin' '
-
-       grep -v notest < expect-all > expect &&
-       sed -e "s/:.*//" < expect | git check-attr --stdin test > actual &&
+       grep -v notest <expect-all >expect &&
+       sed -e "s/:.*//" <expect | git check-attr --stdin test >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'attribute test: --all option' '
+       grep -v unspecified <expect-all | sort >specified-all &&
+       sed -e "s/:.*//" <expect-all | uniq >stdin-all &&
+       git check-attr --stdin --all <stdin-all | sort >actual &&
+       test_cmp specified-all actual
+'
 
-       grep -v unspecified < expect-all | sort > expect &&
-       sed -e "s/:.*//" < expect-all | uniq |
-               git check-attr --stdin --all | sort > actual &&
-       test_cmp expect actual
+test_expect_success 'attribute test: --cached option' '
+       : >empty &&
+       git check-attr --cached --stdin --all <stdin-all | sort >actual &&
+       test_cmp empty actual &&
+       git add .gitattributes a/.gitattributes a/b/.gitattributes &&
+       git check-attr --cached --stdin --all <stdin-all | sort >actual &&
+       test_cmp specified-all actual
 '
 
 test_expect_success 'root subdir attribute test' '
-
        attr_check a/i a/i &&
        attr_check subdir/a/i unspecified
-
 '
 
 test_expect_success 'setup bare' '
-
        git clone --bare . bare.git &&
        cd bare.git
-
 '
 
 test_expect_success 'bare repository: check that .gitattribute is ignored' '
-
        (
                echo "f test=f"
                echo "a/i test=a/i"
@@ -232,11 +221,16 @@ test_expect_success 'bare repository: check that .gitattribute is ignored' '
        attr_check a/c/f unspecified &&
        attr_check a/i unspecified &&
        attr_check subdir/a/i unspecified
+'
 
+test_expect_success 'bare repository: check that --cached honors index' '
+       GIT_INDEX_FILE=../.git/index \
+       git check-attr --cached --stdin --all <../stdin-all |
+       sort >actual &&
+       test_cmp ../specified-all actual
 '
 
 test_expect_success 'bare repository: test info/attributes' '
-
        (
                echo "f test=f"
                echo "a/i test=a/i"
@@ -246,7 +240,6 @@ test_expect_success 'bare repository: test info/attributes' '
        attr_check a/c/f f &&
        attr_check a/i a/i &&
        attr_check subdir/a/i unspecified
-
 '
 
 test_done
index 007f39d5e1dca6eff7e01969282c4cf2dc1bb57a..a1e4616febe6c67f67ab2b4380610abc92468ba5 100755 (executable)
@@ -87,7 +87,7 @@ EOF
 test_expect_success 'long options' '
        test-parse-options --boolean --integer 1729 --boolean --string2=321 \
                --verbose --verbose --no-dry-run --abbrev=10 --file fi.le\
-               > output 2> output.err &&
+               --obsolete > output 2> output.err &&
        test ! -s output.err &&
        test_cmp expect output
 '
diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh
new file mode 100755 (executable)
index 0000000..6c33e28
--- /dev/null
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+test_description="Test whether cache-tree is properly updated
+
+Tests whether various commands properly update and/or rewrite the
+cache-tree extension.
+"
+ . ./test-lib.sh
+
+cmp_cache_tree () {
+       test-dump-cache-tree >actual &&
+       sed "s/$_x40/SHA/" <actual >filtered &&
+       test_cmp "$1" filtered
+}
+
+# We don't bother with actually checking the SHA1:
+# test-dump-cache-tree already verifies that all existing data is
+# correct.
+test_shallow_cache_tree () {
+       printf "SHA  (%d entries, 0 subtrees)\n" $(git ls-files|wc -l) >expect &&
+       cmp_cache_tree expect
+}
+
+test_invalid_cache_tree () {
+       echo "invalid                                   (0 subtrees)" >expect &&
+       printf "SHA #(ref)  (%d entries, 0 subtrees)\n" $(git ls-files|wc -l) >>expect &&
+       cmp_cache_tree expect
+}
+
+test_no_cache_tree () {
+       : >expect &&
+       cmp_cache_tree expect
+}
+
+test_expect_failure 'initial commit has cache-tree' '
+       test_commit foo &&
+       test_shallow_cache_tree
+'
+
+test_expect_success 'read-tree HEAD establishes cache-tree' '
+       git read-tree HEAD &&
+       test_shallow_cache_tree
+'
+
+test_expect_success 'git-add invalidates cache-tree' '
+       test_when_finished "git reset --hard; git read-tree HEAD" &&
+       echo "I changed this file" > foo &&
+       git add foo &&
+       test_invalid_cache_tree
+'
+
+test_expect_success 'update-index invalidates cache-tree' '
+       test_when_finished "git reset --hard; git read-tree HEAD" &&
+       echo "I changed this file" > foo &&
+       git update-index --add foo &&
+       test_invalid_cache_tree
+'
+
+test_expect_success 'write-tree establishes cache-tree' '
+       test-scrap-cache-tree &&
+       git write-tree &&
+       test_shallow_cache_tree
+'
+
+test_expect_success 'test-scrap-cache-tree works' '
+       git read-tree HEAD &&
+       test-scrap-cache-tree &&
+       test_no_cache_tree
+'
+
+test_expect_success 'second commit has cache-tree' '
+       test_commit bar &&
+       test_shallow_cache_tree
+'
+
+test_expect_success 'reset --hard gives cache-tree' '
+       test-scrap-cache-tree &&
+       git reset --hard &&
+       test_shallow_cache_tree
+'
+
+test_expect_success 'reset --hard without index gives cache-tree' '
+       rm -f .git/index &&
+       git reset --hard &&
+       test_shallow_cache_tree
+'
+
+test_expect_failure 'checkout gives cache-tree' '
+       git checkout HEAD^ &&
+       test_shallow_cache_tree
+'
+
+test_done
diff --git a/t/t0200-gettext-basic.sh b/t/t0200-gettext-basic.sh
new file mode 100755 (executable)
index 0000000..8853d8a
--- /dev/null
@@ -0,0 +1,108 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description='Gettext support for Git'
+
+. ./lib-gettext.sh
+
+test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+    test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success 'sanity: $TEXTDOMAIN is git' '
+    test $TEXTDOMAIN = "git"
+'
+
+test_expect_success 'xgettext sanity: Perl _() strings are not extracted' '
+    ! grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
+'
+
+test_expect_success 'xgettext sanity: Comment extraction with --add-comments' '
+    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l >expect &&
+    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext sanity: Comment extraction with --add-comments stops at statements' '
+    ! grep "This is a phony" "$GIT_PO_PATH"/is.po &&
+    ! grep "the above comment" "$GIT_PO_PATH"/is.po
+'
+
+test_expect_success GETTEXT 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' '
+    test -d "$TEXTDOMAINDIR" &&
+    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
+'
+
+test_expect_success GETTEXT 'sanity: Icelandic locale was compiled' '
+    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
+'
+
+# TODO: When we have more locales, generalize this to test them
+# all. Maybe we'll need a dir->locale map for that.
+test_expect_success GETTEXT_LOCALE 'sanity: gettext("") metadata is OK' '
+    # Return value may be non-zero
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "" >zero-expect &&
+    grep "Project-Id-Version: Git" zero-expect &&
+    grep "Git Mailing List <git@vger.kernel.org>" zero-expect &&
+    grep "Content-Type: text/plain; charset=UTF-8" zero-expect &&
+    grep "Content-Transfer-Encoding: 8bit" zero-expect
+'
+
+test_expect_success GETTEXT_LOCALE 'sanity: gettext(unknown) is passed through' '
+    printf "This is not a translation string"  >expect &&
+    gettext "This is not a translation string" >actual &&
+    eval_gettext "This is not a translation string" >actual &&
+    test_cmp expect actual
+'
+
+# xgettext from C
+test_expect_success GETTEXT_LOCALE 'xgettext: C extraction of _() and N_() strings' '
+    printf "TILRAUN: C tilraunastrengur" >expect &&
+    printf "\n" >>expect &&
+    printf "Sjá '\''git help SKIPUN'\'' til að sjá hjálp fyrir tiltekna skipun." >>expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A C test string" >actual &&
+    printf "\n" >>actual &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "See '\''git help COMMAND'\'' for more information on a specific command." >>actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'xgettext: C extraction with %s' '
+    printf "TILRAUN: C tilraunastrengur %%s" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A C test string %s" >actual &&
+    test_cmp expect actual
+'
+
+# xgettext from Shell
+test_expect_success GETTEXT_LOCALE 'xgettext: Shell extraction' '
+    printf "TILRAUN: Skeljartilraunastrengur" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Shell test string" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'xgettext: Shell extraction with $variable' '
+    printf "TILRAUN: Skeljartilraunastrengur með breytunni a var i able" >x-expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" variable="a var i able" eval_gettext "TEST: A Shell test \$variable" >x-actual &&
+    test_cmp x-expect x-actual
+'
+
+# xgettext from Perl
+test_expect_success GETTEXT_LOCALE 'xgettext: Perl extraction' '
+    printf "TILRAUN: Perl tilraunastrengur" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Perl test string" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'xgettext: Perl extraction with %s' '
+    printf "TILRAUN: Perl tilraunastrengur með breytunni %%s" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Perl test variable %s" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'sanity: Some gettext("") data for real locale' '
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "" >real-locale &&
+    test -s real-locale
+'
+
+test_done
diff --git a/t/t0200/test.c b/t/t0200/test.c
new file mode 100644 (file)
index 0000000..584d45c
--- /dev/null
@@ -0,0 +1,23 @@
+/* This is a phony C program that's only here to test xgettext message extraction */
+
+const char help[] =
+       /* TRANSLATORS: This is a test. You don't need to translate it. */
+       N_("See 'git help COMMAND' for more information on a specific command.");
+
+int main(void)
+{
+       /* TRANSLATORS: This is a test. You don't need to translate it. */
+       puts(_("TEST: A C test string"));
+
+       /* TRANSLATORS: This is a test. You don't need to translate it. */
+       printf(_("TEST: A C test string %s"), "variable");
+
+       /* TRANSLATORS: This is a test. You don't need to translate it. */
+       printf(_("TEST: Hello World!"));
+
+       /* TRANSLATORS: This is a test. You don't need to translate it. */
+       printf(_("TEST: Old English Runes"));
+
+       /* TRANSLATORS: This is a test. You don't need to translate it. */
+       printf(_("TEST: ‘single’ and “double” quotes"));
+}
diff --git a/t/t0200/test.perl b/t/t0200/test.perl
new file mode 100644 (file)
index 0000000..36fba34
--- /dev/null
@@ -0,0 +1,14 @@
+# This is a phony Perl program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+1;
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+print __("TEST: A Perl test string");
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+printf __("TEST: A Perl test variable %s"), "moo";
+
+# TRANSLATORS: If you see this, Git has a bug
+print _"TEST: A Perl string xgettext will not get";
diff --git a/t/t0200/test.sh b/t/t0200/test.sh
new file mode 100644 (file)
index 0000000..022d607
--- /dev/null
@@ -0,0 +1,14 @@
+# This is a phony Shell program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+echo
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+gettext "TEST: A Shell test string"
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+eval_gettext "TEST: A Shell test \$variable"
+
+# TRANSLATORS: If you see this, Git has a bug
+_("TEST: A Shell string xgettext won't get")
index 54d98b9b109441025b9b61c2964e29895eb360b7..52b1c27c2c9d3c6111447df4fa241b724f0e6ee1 100755 (executable)
@@ -5,8 +5,24 @@
 
 test_description='Gettext Shell fallbacks'
 
-. ./test-lib.sh
-. "$GIT_BUILD_DIR"/git-sh-i18n
+GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease
+export GIT_INTERNAL_GETTEXT_TEST_FALLBACKS
+
+. ./lib-gettext.sh
+
+test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+    test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_TEST_FALLBACKS is set' '
+    test -n "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
+'
+
+test_expect_success C_LOCALE_OUTPUT 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is fallthrough' '
+    echo fallthrough >expect &&
+    echo $GIT_INTERNAL_GETTEXT_SH_SCHEME >actual &&
+    test_cmp expect actual
+'
 
 test_expect_success 'gettext: our gettext() fallback has pass-through semantics' '
     printf "test" >expect &&
diff --git a/t/t0202-gettext-perl.sh b/t/t0202-gettext-perl.sh
new file mode 100755 (executable)
index 0000000..428ebb0
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description='Perl gettext interface (Git::I18N)'
+
+. ./lib-gettext.sh
+
+if ! test_have_prereq PERL; then
+       skip_all='skipping perl interface tests, perl not available'
+       test_done
+fi
+
+"$PERL_PATH" -MTest::More -e 0 2>/dev/null || {
+       skip_all="Perl Test::More unavailable, skipping test"
+       test_done
+}
+
+# The external test will outputs its own plan
+test_external_has_tap=1
+
+test_external_without_stderr \
+    'Perl Git::I18N API' \
+    "$PERL_PATH" "$TEST_DIRECTORY"/t0202/test.pl
+
+test_done
diff --git a/t/t0202/test.pl b/t/t0202/test.pl
new file mode 100644 (file)
index 0000000..2c10cb4
--- /dev/null
@@ -0,0 +1,110 @@
+#!/usr/bin/perl
+use 5.008;
+use lib (split(/:/, $ENV{GITPERLLIB}));
+use strict;
+use warnings;
+use POSIX qw(:locale_h);
+use Test::More tests => 8;
+use Git::I18N;
+
+my $has_gettext_library = $Git::I18N::__HAS_LIBRARY;
+
+ok(1, "Testing Git::I18N with " .
+        ($has_gettext_library
+         ? (defined $Locale::Messages::VERSION
+                ? "Locale::Messages version $Locale::Messages::VERSION"
+                # Versions of Locale::Messages before 1.17 didn't have a
+                # $VERSION variable.
+                : "Locale::Messages version <1.17")
+         : "NO Perl gettext library"));
+ok(1, "Git::I18N is located at $INC{'Git/I18N.pm'}");
+
+{
+       my $exports = @Git::I18N::EXPORT;
+       ok($exports, "sanity: Git::I18N has $exports export(s)");
+}
+is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N exports everything by default");
+
+# prototypes
+{
+       # Add prototypes here when modifying the public interface to add
+       # more gettext wrapper functions.
+       my %prototypes = (qw(
+               __      $
+       ));
+       while (my ($sub, $proto) = each %prototypes) {
+               is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype");
+       }
+}
+
+# Test basic passthrough in the C locale
+{
+       local $ENV{LANGUAGE} = 'C';
+       local $ENV{LC_ALL}   = 'C';
+       local $ENV{LANG}     = 'C';
+
+       my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+       is(__($got), $expect, "Passing a string through __() in the C locale works");
+}
+
+# Test a basic message on different locales
+SKIP: {
+       unless ($ENV{GETTEXT_LOCALE}) {
+               # Can't reliably test __() with a non-C locales because the
+               # required locales may not be installed on the system.
+               #
+               # We test for these anyway as part of the shell
+               # tests. Skipping these here will eliminate failures on odd
+               # platforms with incomplete locale data.
+
+               skip "GETTEXT_LOCALE must be set by lib-gettext.sh for exhaustive Git::I18N tests", 2;
+       }
+
+       # The is_IS UTF-8 locale passed from lib-gettext.sh
+       my $is_IS_locale = $ENV{is_IS_locale};
+
+       my $test = sub {
+               my ($got, $expect, $msg, $locale) = @_;
+               # Maybe this system doesn't have the locale we're trying to
+               # test.
+               my $locale_ok = setlocale(LC_ALL, $locale);
+               is(__($got), $expect, "$msg a gettext library + <$locale> locale <$got> turns into <$expect>");
+       };
+
+       my $env_C = sub {
+               $ENV{LANGUAGE} = 'C';
+               $ENV{LC_ALL}   = 'C';
+       };
+
+       my $env_is = sub {
+               $ENV{LANGUAGE} = 'is';
+               $ENV{LC_ALL}   = $is_IS_locale;
+       };
+
+       # Translation's the same as the original
+       my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+       if ($has_gettext_library) {
+               {
+                       local %ENV; $env_C->();
+                       $test->($got, $expect, "With", 'C');
+               }
+
+               {
+                       my ($got, $expect) = ($got, 'TILRAUN: Perl tilraunastrengur');
+                       local %ENV; $env_is->();
+                       $test->($got, $expect, "With", $is_IS_locale);
+               }
+       } else {
+               {
+                       local %ENV; $env_C->();
+                       $test->($got, $expect, "Without", 'C');
+               }
+
+               {
+                       local %ENV; $env_is->();
+                       $test->($got, $expect, "Without", 'is');
+               }
+       }
+}
diff --git a/t/t0203-gettext-setlocale-sanity.sh b/t/t0203-gettext-setlocale-sanity.sh
new file mode 100755 (executable)
index 0000000..a212460
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description="The Git C functions aren't broken by setlocale(3)"
+
+. ./lib-gettext.sh
+
+test_expect_success 'git show a ISO-8859-1 commit under C locale' '
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+       test_commit "iso-c-commit" iso-under-c &&
+       git show >out 2>err &&
+       ! test -s err &&
+       grep -q "iso-c-commit" out
+'
+
+test_expect_success GETTEXT_LOCALE 'git show a ISO-8859-1 commit under a UTF-8 locale' '
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+       test_commit "iso-utf8-commit" iso-under-utf8 &&
+       LANGUAGE=is LC_ALL="$is_IS_locale" git show >out 2>err &&
+       ! test -s err &&
+       grep -q "iso-utf8-commit" out
+'
+
+test_done
diff --git a/t/t0204-gettext-reencode-sanity.sh b/t/t0204-gettext-reencode-sanity.sh
new file mode 100755 (executable)
index 0000000..189af90
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description="Gettext reencoding of our *.po/*.mo files works"
+
+. ./lib-gettext.sh
+
+
+test_expect_success GETTEXT_LOCALE 'gettext: Emitting UTF-8 from our UTF-8 *.mo files / Icelandic' '
+    printf "TILRAUN: Halló Heimur!" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: Hello World!" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'gettext: Emitting UTF-8 from our UTF-8 *.mo files / Runes' '
+    printf "TILRAUN: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: Old English Runes" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_ISO_LOCALE 'gettext: Emitting ISO-8859-1 from our UTF-8 *.mo files / Icelandic' '
+    printf "TILRAUN: Halló Heimur!" | iconv -f UTF-8 -t ISO8859-1 >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: Hello World!" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_ISO_LOCALE 'gettext: Emitting ISO-8859-1 from our UTF-8 *.mo files / Runes' '
+    LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: Old English Runes" >runes &&
+
+       if grep "^TEST: Old English Runes$" runes
+       then
+               say "Your system can not handle this complexity and returns the string as-is"
+       else
+               # Both Solaris and GNU libintl will return this stream of
+               # question marks, so it is s probably portable enough
+               printf "TILRAUN: ?? ???? ??? ?? ???? ?? ??? ????? ??????????? ??? ?? ????" >runes-expect &&
+               test_cmp runes-expect runes
+       fi
+'
+
+test_expect_success GETTEXT_LOCALE 'gettext: Fetching a UTF-8 msgid -> UTF-8' '
+    printf "TILRAUN: ‚einfaldar‘ og „tvöfaldar“ gæsalappir" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: ‘single’ and “double” quotes" >actual &&
+    test_cmp expect actual
+'
+
+# How these quotes get transliterated depends on the gettext implementation:
+#
+#   Debian:  ,einfaldar' og ,,tvöfaldar" [GNU libintl]
+#   FreeBSD: `einfaldar` og "tvöfaldar"  [GNU libintl]
+#   Solaris: ?einfaldar? og ?tvöfaldar?  [Solaris libintl]
+#
+# Just make sure the contents are transliterated, and don't use grep -q
+# so that these differences are emitted under --verbose for curious
+# eyes.
+test_expect_success GETTEXT_ISO_LOCALE 'gettext: Fetching a UTF-8 msgid -> ISO-8859-1' '
+    LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: ‘single’ and “double” quotes" >actual &&
+    grep "einfaldar" actual &&
+    grep "$(echo tvöfaldar | iconv -f UTF-8 -t ISO8859-1)" actual
+'
+
+test_expect_success GETTEXT_LOCALE 'gettext.c: git init UTF-8 -> UTF-8' '
+    printf "Bjó til tóma Git lind" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" git init repo >actual &&
+    test_when_finished "rm -rf repo" &&
+    grep "^$(cat expect) " actual
+'
+
+test_expect_success GETTEXT_ISO_LOCALE 'gettext.c: git init UTF-8 -> ISO-8859-1' '
+    printf "Bjó til tóma Git lind" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_iso_locale" git init repo >actual &&
+    test_when_finished "rm -rf repo" &&
+    grep "^$(cat expect | iconv -f UTF-8 -t ISO8859-1) " actual
+'
+
+test_done
diff --git a/t/t0205-gettext-poison.sh b/t/t0205-gettext-poison.sh
new file mode 100755 (executable)
index 0000000..2361590
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description='Gettext Shell poison'
+
+. ./lib-gettext.sh
+
+test_expect_success GETTEXT_POISON "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+    test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success GETTEXT_POISON 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is poison' '
+    test "$GIT_INTERNAL_GETTEXT_SH_SCHEME" = "poison"
+'
+
+test_expect_success GETTEXT_POISON 'gettext: our gettext() fallback has poison semantics' '
+    printf "# GETTEXT POISON #" >expect &&
+    gettext "test" >actual &&
+    test_cmp expect actual &&
+    printf "# GETTEXT POISON #" >expect &&
+    gettext "test more words" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_POISON 'eval_gettext: our eval_gettext() fallback has poison semantics' '
+    printf "# GETTEXT POISON #" >expect &&
+    eval_gettext "test" >actual &&
+    test_cmp expect actual &&
+    printf "# GETTEXT POISON #" >expect &&
+    eval_gettext "test more words" >actual &&
+    test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
new file mode 100755 (executable)
index 0000000..c1c8108
--- /dev/null
@@ -0,0 +1,282 @@
+#!/bin/sh
+
+test_description='basic credential helper tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
+
+test_expect_success 'setup helper scripts' '
+       cat >dump <<-\EOF &&
+       whoami=`echo $0 | sed s/.*git-credential-//`
+       echo >&2 "$whoami: $*"
+       OIFS=$IFS
+       IFS==
+       while read key value; do
+               echo >&2 "$whoami: $key=$value"
+               eval "$key=$value"
+       done
+       IFS=$OIFS
+       EOF
+
+       cat >git-credential-useless <<-\EOF &&
+       #!/bin/sh
+       . ./dump
+       exit 0
+       EOF
+       chmod +x git-credential-useless &&
+
+       cat >git-credential-verbatim <<-\EOF &&
+       #!/bin/sh
+       user=$1; shift
+       pass=$1; shift
+       . ./dump
+       test -z "$user" || echo username=$user
+       test -z "$pass" || echo password=$pass
+       EOF
+       chmod +x git-credential-verbatim &&
+
+       PATH="$PWD:$PATH"
+'
+
+test_expect_success 'credential_fill invokes helper' '
+       check fill "verbatim foo bar" <<-\EOF
+       --
+       username=foo
+       password=bar
+       --
+       verbatim: get
+       EOF
+'
+
+test_expect_success 'credential_fill invokes multiple helpers' '
+       check fill useless "verbatim foo bar" <<-\EOF
+       --
+       username=foo
+       password=bar
+       --
+       useless: get
+       verbatim: get
+       EOF
+'
+
+test_expect_success 'credential_fill stops when we get a full response' '
+       check fill "verbatim one two" "verbatim three four" <<-\EOF
+       --
+       username=one
+       password=two
+       --
+       verbatim: get
+       EOF
+'
+
+test_expect_success 'credential_fill continues through partial response' '
+       check fill "verbatim one \"\"" "verbatim two three" <<-\EOF
+       --
+       username=two
+       password=three
+       --
+       verbatim: get
+       verbatim: get
+       verbatim: username=one
+       EOF
+'
+
+test_expect_success 'credential_fill passes along metadata' '
+       check fill "verbatim one two" <<-\EOF
+       protocol=ftp
+       host=example.com
+       path=foo.git
+       --
+       username=one
+       password=two
+       --
+       verbatim: get
+       verbatim: protocol=ftp
+       verbatim: host=example.com
+       verbatim: path=foo.git
+       EOF
+'
+
+test_expect_success 'credential_approve calls all helpers' '
+       check approve useless "verbatim one two" <<-\EOF
+       username=foo
+       password=bar
+       --
+       --
+       useless: store
+       useless: username=foo
+       useless: password=bar
+       verbatim: store
+       verbatim: username=foo
+       verbatim: password=bar
+       EOF
+'
+
+test_expect_success 'do not bother storing password-less credential' '
+       check approve useless <<-\EOF
+       username=foo
+       --
+       --
+       EOF
+'
+
+
+test_expect_success 'credential_reject calls all helpers' '
+       check reject useless "verbatim one two" <<-\EOF
+       username=foo
+       password=bar
+       --
+       --
+       useless: erase
+       useless: username=foo
+       useless: password=bar
+       verbatim: erase
+       verbatim: username=foo
+       verbatim: password=bar
+       EOF
+'
+
+test_expect_success 'usernames can be preserved' '
+       check fill "verbatim \"\" three" <<-\EOF
+       username=one
+       --
+       username=one
+       password=three
+       --
+       verbatim: get
+       verbatim: username=one
+       EOF
+'
+
+test_expect_success 'usernames can be overridden' '
+       check fill "verbatim two three" <<-\EOF
+       username=one
+       --
+       username=two
+       password=three
+       --
+       verbatim: get
+       verbatim: username=one
+       EOF
+'
+
+test_expect_success 'do not bother completing already-full credential' '
+       check fill "verbatim three four" <<-\EOF
+       username=one
+       password=two
+       --
+       username=one
+       password=two
+       --
+       EOF
+'
+
+# We can't test the basic terminal password prompt here because
+# getpass() tries too hard to find the real terminal. But if our
+# askpass helper is run, we know the internal getpass is working.
+test_expect_success 'empty helper list falls back to internal getpass' '
+       check fill <<-\EOF
+       --
+       username=askpass-username
+       password=askpass-password
+       --
+       askpass: Username:
+       askpass: Password:
+       EOF
+'
+
+test_expect_success 'internal getpass does not ask for known username' '
+       check fill <<-\EOF
+       username=foo
+       --
+       username=foo
+       password=askpass-password
+       --
+       askpass: Password:
+       EOF
+'
+
+HELPER="!f() {
+               cat >/dev/null
+               echo username=foo
+               echo password=bar
+       }; f"
+test_expect_success 'respect configured credentials' '
+       test_config credential.helper "$HELPER" &&
+       check fill <<-\EOF
+       --
+       username=foo
+       password=bar
+       --
+       EOF
+'
+
+test_expect_success 'match configured credential' '
+       test_config credential.https://example.com.helper "$HELPER" &&
+       check fill <<-\EOF
+       protocol=https
+       host=example.com
+       path=repo.git
+       --
+       username=foo
+       password=bar
+       --
+       EOF
+'
+
+test_expect_success 'do not match configured credential' '
+       test_config credential.https://foo.helper "$HELPER" &&
+       check fill <<-\EOF
+       protocol=https
+       host=bar
+       --
+       username=askpass-username
+       password=askpass-password
+       --
+       askpass: Username for '\''https://bar'\'':
+       askpass: Password for '\''https://askpass-username@bar'\'':
+       EOF
+'
+
+test_expect_success 'pull username from config' '
+       test_config credential.https://example.com.username foo &&
+       check fill <<-\EOF
+       protocol=https
+       host=example.com
+       --
+       username=foo
+       password=askpass-password
+       --
+       askpass: Password for '\''https://foo@example.com'\'':
+       EOF
+'
+
+test_expect_success 'http paths can be part of context' '
+       check fill "verbatim foo bar" <<-\EOF &&
+       protocol=https
+       host=example.com
+       path=foo.git
+       --
+       username=foo
+       password=bar
+       --
+       verbatim: get
+       verbatim: protocol=https
+       verbatim: host=example.com
+       EOF
+       test_config credential.https://example.com.useHttpPath true &&
+       check fill "verbatim foo bar" <<-\EOF
+       protocol=https
+       host=example.com
+       path=foo.git
+       --
+       username=foo
+       password=bar
+       --
+       verbatim: get
+       verbatim: protocol=https
+       verbatim: host=example.com
+       verbatim: path=foo.git
+       EOF
+'
+
+test_done
diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh
new file mode 100755 (executable)
index 0000000..82c8411
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test_description='credential-cache tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
+
+test -z "$NO_UNIX_SOCKETS" || {
+       skip_all='skipping credential-cache tests, unix sockets not available'
+       test_done
+}
+
+# don't leave a stale daemon running
+trap 'code=$?; git credential-cache exit; (exit $code); die' EXIT
+
+helper_test cache
+helper_test_timeout cache --timeout=1
+
+# we can't rely on our "trap" above working after test_done,
+# as test_done will delete the trash directory containing
+# our socket, leaving us with no way to access the daemon.
+git credential-cache exit
+
+test_done
diff --git a/t/t0302-credential-store.sh b/t/t0302-credential-store.sh
new file mode 100755 (executable)
index 0000000..f61b40c
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+test_description='credential-store tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
+
+helper_test store
+
+test_done
diff --git a/t/t0303-credential-external.sh b/t/t0303-credential-external.sh
new file mode 100755 (executable)
index 0000000..267f4c8
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+test_description='external credential helper tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
+
+pre_test() {
+       test -z "$GIT_TEST_CREDENTIAL_HELPER_SETUP" ||
+       eval "$GIT_TEST_CREDENTIAL_HELPER_SETUP"
+
+       # clean before the test in case there is cruft left
+       # over from a previous run that would impact results
+       helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
+}
+
+post_test() {
+       # clean afterwards so that we are good citizens
+       # and don't leave cruft in the helper's storage, which
+       # might be long-term system storage
+       helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
+}
+
+if test -z "$GIT_TEST_CREDENTIAL_HELPER"; then
+       say "# skipping external helper tests (set GIT_TEST_CREDENTIAL_HELPER)"
+else
+       pre_test
+       helper_test "$GIT_TEST_CREDENTIAL_HELPER"
+       post_test
+fi
+
+if test -z "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"; then
+       say "# skipping external helper timeout tests"
+else
+       pre_test
+       helper_test_timeout "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"
+       post_test
+fi
+
+test_done
index 6d52b824b115964c5551aaf4284205b98b885ce3..f83df8eb8b143086df00e163a30ad96e43404001 100755 (executable)
@@ -189,7 +189,7 @@ for args in "-w --stdin-paths" "--stdin-paths -w"; do
 done
 
 test_expect_success 'corrupt tree' '
-       echo abc >malformed-tree
+       echo abc >malformed-tree &&
        test_must_fail git hash-object -t tree malformed-tree
 '
 
index 018c3546b6f05228a20dfb5ec9364d0a1575e4ff..5c0053a20bf2fbf1dd466ace5dc933e38ce3544d 100755 (executable)
@@ -234,4 +234,20 @@ test_expect_success 'read-tree --reset removes outside worktree' '
        test_cmp empty result
 '
 
+test_expect_success 'print errors when failed to update worktree' '
+       echo sub >.git/info/sparse-checkout &&
+       git checkout -f init &&
+       mkdir sub &&
+       touch sub/added sub/addedtoo &&
+       test_must_fail git checkout top 2>actual &&
+       cat >expected <<\EOF &&
+error: The following untracked working tree files would be overwritten by checkout:
+       sub/added
+       sub/addedtoo
+Please move or remove them before you can switch branches.
+Aborting
+EOF
+       test_cmp expected actual
+'
+
 test_done
index 0a9cedd374012f6cdb5b2658639c4168a8cdf159..fbf5f2fc0070b8f17e8d6b10ea9993eec6f35982 100755 (executable)
@@ -34,7 +34,7 @@ assert_blob_equals() {
 }
 
 test_expect_success setup '
-       cp -R "$TEST_DIRECTORY/t1013/objects" .git/
+       cp -R "$TEST_DIRECTORY/t1013/objects" .git/ &&
        git --version
 '
 
index 3b1b985996e9a6b52b032ef45c5be9b1c57b60f6..e23ac0e69da8d3dba32f0d256a249a987356e825 100755 (executable)
@@ -118,7 +118,7 @@ test_expect_success 'alias expansion' '
        )
 '
 
-test_expect_success '!alias expansion' '
+test_expect_success NOT_MINGW '!alias expansion' '
        pwd >expect &&
        (
                git config alias.test !pwd &&
index deba111bd7c2d26f3bd38dbfa086cec209f81874..29d6024b7f1b55c09cbd7e9ed682a3e745c550d6 100755 (executable)
@@ -7,21 +7,97 @@ test_description='adding and checking out large blobs'
 
 test_expect_success setup '
        git config core.bigfilethreshold 200k &&
-       echo X | dd of=large bs=1k seek=2000
+       echo X | dd of=large1 bs=1k seek=2000 &&
+       echo X | dd of=large2 bs=1k seek=2000 &&
+       echo X | dd of=large3 bs=1k seek=2000 &&
+       echo Y | dd of=huge bs=1k seek=2500
 '
 
-test_expect_success 'add a large file' '
-       git add large &&
-       # make sure we got a packfile and no loose objects
-       test -f .git/objects/pack/pack-*.pack &&
-       test ! -f .git/objects/??/??????????????????????????????????????
+test_expect_success 'add a large file or two' '
+       git add large1 huge large2 &&
+       # make sure we got a single packfile and no loose objects
+       bad= count=0 idx= &&
+       for p in .git/objects/pack/pack-*.pack
+       do
+               count=$(( $count + 1 ))
+               if test -f "$p" && idx=${p%.pack}.idx && test -f "$idx"
+               then
+                       continue
+               fi
+               bad=t
+       done &&
+       test -z "$bad" &&
+       test $count = 1 &&
+       cnt=$(git show-index <"$idx" | wc -l) &&
+       test $cnt = 2 &&
+       for l in .git/objects/??/??????????????????????????????????????
+       do
+               test -f "$l" || continue
+               bad=t
+       done &&
+       test -z "$bad" &&
+
+       # attempt to add another copy of the same
+       git add large3 &&
+       bad= count=0 &&
+       for p in .git/objects/pack/pack-*.pack
+       do
+               count=$(( $count + 1 ))
+               if test -f "$p" && idx=${p%.pack}.idx && test -f "$idx"
+               then
+                       continue
+               fi
+               bad=t
+       done &&
+       test -z "$bad" &&
+       test $count = 1
 '
 
 test_expect_success 'checkout a large file' '
-       large=$(git rev-parse :large) &&
-       git update-index --add --cacheinfo 100644 $large another &&
+       large1=$(git rev-parse :large1) &&
+       git update-index --add --cacheinfo 100644 $large1 another &&
        git checkout another &&
-       cmp large another ;# this must not be test_cmp
+       cmp large1 another ;# this must not be test_cmp
+'
+
+test_expect_success 'packsize limit' '
+       test_create_repo mid &&
+       (
+               cd mid &&
+               git config core.bigfilethreshold 64k &&
+               git config pack.packsizelimit 256k &&
+
+               # mid1 and mid2 will fit within 256k limit but
+               # appending mid3 will bust the limit and will
+               # result in a separate packfile.
+               test-genrandom "a" $(( 66 * 1024 )) >mid1 &&
+               test-genrandom "b" $(( 80 * 1024 )) >mid2 &&
+               test-genrandom "c" $(( 128 * 1024 )) >mid3 &&
+               git add mid1 mid2 mid3 &&
+
+               count=0
+               for pi in .git/objects/pack/pack-*.idx
+               do
+                       test -f "$pi" && count=$(( $count + 1 ))
+               done &&
+               test $count = 2 &&
+
+               (
+                       git hash-object --stdin <mid1
+                       git hash-object --stdin <mid2
+                       git hash-object --stdin <mid3
+               ) |
+               sort >expect &&
+
+               for pi in .git/objects/pack/pack-*.idx
+               do
+                       git show-index <"$pi"
+               done |
+               sed -e "s/^[0-9]* \([0-9a-f]*\) .*/\1/" |
+               sort >actual &&
+
+               test_cmp expect actual
+       )
 '
 
 test_done
diff --git a/t/t1051-large-conversion.sh b/t/t1051-large-conversion.sh
new file mode 100755 (executable)
index 0000000..8b7640b
--- /dev/null
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+test_description='test conversion filters on large files'
+. ./test-lib.sh
+
+set_attr() {
+       test_when_finished 'rm -f .gitattributes' &&
+       echo "* $*" >.gitattributes
+}
+
+check_input() {
+       git read-tree --empty &&
+       git add small large &&
+       git cat-file blob :small >small.index &&
+       git cat-file blob :large | head -n 1 >large.index &&
+       test_cmp small.index large.index
+}
+
+check_output() {
+       rm -f small large &&
+       git checkout small large &&
+       head -n 1 large >large.head &&
+       test_cmp small large.head
+}
+
+test_expect_success 'setup input tests' '
+       printf "\$Id: foo\$\\r\\n" >small &&
+       cat small small >large &&
+       git config core.bigfilethreshold 20 &&
+       git config filter.test.clean "sed s/.*/CLEAN/"
+'
+
+test_expect_success 'autocrlf=true converts on input' '
+       test_config core.autocrlf true &&
+       check_input
+'
+
+test_expect_success 'eol=crlf converts on input' '
+       set_attr eol=crlf &&
+       check_input
+'
+
+test_expect_success 'ident converts on input' '
+       set_attr ident &&
+       check_input
+'
+
+test_expect_success 'user-defined filters convert on input' '
+       set_attr filter=test &&
+       check_input
+'
+
+test_expect_success 'setup output tests' '
+       echo "\$Id\$" >small &&
+       cat small small >large &&
+       git add small large &&
+       git config core.bigfilethreshold 7 &&
+       git config filter.test.smudge "sed s/.*/SMUDGE/"
+'
+
+test_expect_success 'autocrlf=true converts on output' '
+       test_config core.autocrlf true &&
+       check_output
+'
+
+test_expect_success 'eol=crlf converts on output' '
+       set_attr eol=crlf &&
+       check_output
+'
+
+test_expect_success 'user-defined filters convert on output' '
+       set_attr filter=test &&
+       check_output
+'
+
+test_expect_success 'ident converts on output' '
+       set_attr ident &&
+       rm -f small large &&
+       git checkout small large &&
+       sed -n "s/Id: .*/Id: SHA/p" <small >small.clean &&
+       head -n 1 large >large.head &&
+       sed -n "s/Id: .*/Id: SHA/p" <large.head >large.clean &&
+       test_cmp small.clean large.clean
+'
+
+test_done
index 5e29e13782ffad74915128adf163d57eef16f4e8..9356beaf4be45c32d10209c5063b7a9c5aa06d42 100755 (executable)
@@ -156,7 +156,7 @@ Updating VARIABLE..VARIABLE
 FASTFORWARD (no commit created; -m option ignored)
  example |    1 +
  hello   |    1 +
- 2 files changed, 2 insertions(+), 0 deletions(-)
+ 2 files changed, 2 insertions(+)
 EOF
 
 test_expect_success 'git resolve' '
index 23c8711d714eefa34f2bfc14b92818953aeafa49..728a965669e7a69014b950b8abbc672702caca27 100755 (executable)
@@ -7,28 +7,28 @@ test_description='Test git config in different settings'
 
 . ./test-lib.sh
 
-test -f .git/config && rm .git/config
-
-git config core.penguin "little blue"
+test_expect_success 'clear default config' '
+       rm -f .git/config
+'
 
 cat > expect << EOF
 [core]
        penguin = little blue
 EOF
-
-test_expect_success 'initial' 'cmp .git/config expect'
-
-git config Core.Movie BadPhysics
+test_expect_success 'initial' '
+       git config core.penguin "little blue" &&
+       test_cmp expect .git/config
+'
 
 cat > expect << EOF
 [core]
        penguin = little blue
        Movie = BadPhysics
 EOF
-
-test_expect_success 'mixed case' 'cmp .git/config expect'
-
-git config Cores.WhatEver Second
+test_expect_success 'mixed case' '
+       git config Core.Movie BadPhysics &&
+       test_cmp expect .git/config
+'
 
 cat > expect << EOF
 [core]
@@ -37,10 +37,10 @@ cat > expect << EOF
 [Cores]
        WhatEver = Second
 EOF
-
-test_expect_success 'similar section' 'cmp .git/config expect'
-
-git config CORE.UPPERCASE true
+test_expect_success 'similar section' '
+       git config Cores.WhatEver Second &&
+       test_cmp expect .git/config
+'
 
 cat > expect << EOF
 [core]
@@ -50,8 +50,10 @@ cat > expect << EOF
 [Cores]
        WhatEver = Second
 EOF
-
-test_expect_success 'similar section' 'cmp .git/config expect'
+test_expect_success 'uppercase section' '
+       git config CORE.UPPERCASE true &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'replace with non-match' \
        'git config core.penguin kingpin !blue'
@@ -69,7 +71,34 @@ cat > expect << EOF
        WhatEver = Second
 EOF
 
-test_expect_success 'non-match result' 'cmp .git/config expect'
+test_expect_success 'non-match result' 'test_cmp expect .git/config'
+
+test_expect_success 'find mixed-case key by canonical name' '
+       echo Second >expect &&
+       git config cores.whatever >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'find mixed-case key by non-canonical name' '
+       echo Second >expect &&
+       git config CoReS.WhAtEvEr >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'subsections are not canonicalized by git-config' '
+       cat >>.git/config <<-\EOF &&
+       [section.SubSection]
+       key = one
+       [section "SubSection"]
+       key = two
+       EOF
+       echo one >expect &&
+       git config section.subsection.key >actual &&
+       test_cmp expect actual &&
+       echo two >expect &&
+       git config section.SubSection.key >actual &&
+       test_cmp expect actual
+'
 
 cat > .git/config <<\EOF
 [alpha]
@@ -88,7 +117,7 @@ bar = foo
 [beta]
 EOF
 
-test_expect_success 'unset with cont. lines is correct' 'cmp .git/config expect'
+test_expect_success 'unset with cont. lines is correct' 'test_cmp expect .git/config'
 
 cat > .git/config << EOF
 [beta] ; silly comment # another comment
@@ -116,7 +145,7 @@ noIndent= sillyValue ; 'nother silly comment
 [nextSection] noNewline = ouch
 EOF
 
-test_expect_success 'multiple unset is correct' 'cmp .git/config expect'
+test_expect_success 'multiple unset is correct' 'test_cmp expect .git/config'
 
 cp .git/config2 .git/config
 
@@ -140,9 +169,7 @@ noIndent= sillyValue ; 'nother silly comment
 [nextSection] noNewline = ouch
 EOF
 
-test_expect_success 'all replaced' 'cmp .git/config expect'
-
-git config beta.haha alpha
+test_expect_success 'all replaced' 'test_cmp expect .git/config'
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -153,10 +180,10 @@ noIndent= sillyValue ; 'nother silly comment
        haha = alpha
 [nextSection] noNewline = ouch
 EOF
-
-test_expect_success 'really mean test' 'cmp .git/config expect'
-
-git config nextsection.nonewline wow
+test_expect_success 'really mean test' '
+       git config beta.haha alpha &&
+       test_cmp expect .git/config
+'
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -168,11 +195,12 @@ noIndent= sillyValue ; 'nother silly comment
 [nextSection]
        nonewline = wow
 EOF
-
-test_expect_success 'really really mean test' 'cmp .git/config expect'
+test_expect_success 'really really mean test' '
+       git config nextsection.nonewline wow &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'get value' 'test alpha = $(git config beta.haha)'
-git config --unset beta.haha
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -183,10 +211,10 @@ noIndent= sillyValue ; 'nother silly comment
 [nextSection]
        nonewline = wow
 EOF
-
-test_expect_success 'unset' 'cmp .git/config expect'
-
-git config nextsection.NoNewLine "wow2 for me" "for me$"
+test_expect_success 'unset' '
+       git config --unset beta.haha &&
+       test_cmp expect .git/config
+'
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -198,8 +226,10 @@ noIndent= sillyValue ; 'nother silly comment
        nonewline = wow
        NoNewLine = wow2 for me
 EOF
-
-test_expect_success 'multivar' 'cmp .git/config expect'
+test_expect_success 'multivar' '
+       git config nextsection.NoNewLine "wow2 for me" "for me$" &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'non-match' \
        'git config --get nextsection.nonewline !for'
@@ -214,8 +244,6 @@ test_expect_success 'ambiguous get' '
 test_expect_success 'get multivar' \
        'git config --get-all nextsection.nonewline'
 
-git config nextsection.nonewline "wow3" "wow$"
-
 cat > expect << EOF
 [beta] ; silly comment # another comment
 noIndent= sillyValue ; 'nother silly comment
@@ -226,8 +254,10 @@ noIndent= sillyValue ; 'nother silly comment
        nonewline = wow3
        NoNewLine = wow2 for me
 EOF
-
-test_expect_success 'multivar replace' 'cmp .git/config expect'
+test_expect_success 'multivar replace' '
+       git config nextsection.nonewline "wow3" "wow$" &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'ambiguous value' '
        test_must_fail git config nextsection.nonewline
@@ -241,8 +271,6 @@ test_expect_success 'invalid unset' '
        test_must_fail git config --unset somesection.nonewline
 '
 
-git config --unset nextsection.nonewline "wow3$"
-
 cat > expect << EOF
 [beta] ; silly comment # another comment
 noIndent= sillyValue ; 'nother silly comment
@@ -253,7 +281,10 @@ noIndent= sillyValue ; 'nother silly comment
        NoNewLine = wow2 for me
 EOF
 
-test_expect_success 'multivar unset' 'cmp .git/config expect'
+test_expect_success 'multivar unset' '
+       git config --unset nextsection.nonewline "wow3$" &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'invalid key' 'test_must_fail git config inval.2key blabla'
 
@@ -276,7 +307,7 @@ noIndent= sillyValue ; 'nother silly comment
        Alpha = beta
 EOF
 
-test_expect_success 'hierarchical section value' 'cmp .git/config expect'
+test_expect_success 'hierarchical section value' 'test_cmp expect .git/config'
 
 cat > expect << EOF
 beta.noindent=sillyValue
@@ -304,15 +335,16 @@ EOF
 test_expect_success '--get-regexp' \
        'git config --get-regexp in > output && cmp output expect'
 
-git config --add nextsection.nonewline "wow4 for you"
-
 cat > expect << EOF
 wow2 for me
 wow4 for you
 EOF
 
-test_expect_success '--add' \
-       'git config --get-all nextsection.nonewline > output && cmp output expect'
+test_expect_success '--add' '
+       git config --add nextsection.nonewline "wow4 for you" &&
+       git config --get-all nextsection.nonewline > output &&
+       test_cmp expect output
+'
 
 cat > .git/config << EOF
 [novalue]
@@ -367,8 +399,6 @@ cat > .git/config << EOF
        c = d
 EOF
 
-git config a.x y
-
 cat > expect << EOF
 [a.b]
        c = d
@@ -376,10 +406,10 @@ cat > expect << EOF
        x = y
 EOF
 
-test_expect_success 'new section is partial match of another' 'cmp .git/config expect'
-
-git config b.x y
-git config a.b c
+test_expect_success 'new section is partial match of another' '
+       git config a.x y &&
+       test_cmp expect .git/config
+'
 
 cat > expect << EOF
 [a.b]
@@ -391,7 +421,11 @@ cat > expect << EOF
        x = y
 EOF
 
-test_expect_success 'new variable inserts into proper section' 'cmp .git/config expect'
+test_expect_success 'new variable inserts into proper section' '
+       git config b.x y &&
+       git config a.b c &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'alternative GIT_CONFIG (non-existing file should fail)' \
        'test_must_fail git config --file non-existing-config -l'
@@ -405,9 +439,10 @@ cat > expect << EOF
 ein.bahn=strasse
 EOF
 
-GIT_CONFIG=other-config git config -l > output
-
-test_expect_success 'alternative GIT_CONFIG' 'cmp output expect'
+test_expect_success 'alternative GIT_CONFIG' '
+       GIT_CONFIG=other-config git config -l >output &&
+       test_cmp expect output
+'
 
 test_expect_success 'alternative GIT_CONFIG (--file)' \
        'git config --file other-config -l > output && cmp output expect'
@@ -423,8 +458,6 @@ test_expect_success 'refer config from subdirectory' '
 
 '
 
-GIT_CONFIG=other-config git config anwohner.park ausweis
-
 cat > expect << EOF
 [ein]
        bahn = strasse
@@ -432,7 +465,10 @@ cat > expect << EOF
        park = ausweis
 EOF
 
-test_expect_success '--set in alternative GIT_CONFIG' 'cmp other-config expect'
+test_expect_success '--set in alternative GIT_CONFIG' '
+       GIT_CONFIG=other-config git config anwohner.park ausweis &&
+       test_cmp expect other-config
+'
 
 cat > .git/config << EOF
 # Hallo
@@ -522,8 +558,6 @@ EOF
 test_expect_success "section was removed properly" \
        "test_cmp expect .git/config"
 
-rm .git/config
-
 cat > expect << EOF
 [gitcvs]
        enabled = true
@@ -534,10 +568,11 @@ EOF
 
 test_expect_success 'section ending' '
 
+       rm -f .git/config &&
        git config gitcvs.enabled true &&
        git config gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite &&
        git config gitcvs.dbname %Ggitcvs2.%a.%m.sqlite &&
-       cmp .git/config expect
+       test_cmp expect .git/config
 
 '
 
@@ -606,8 +641,6 @@ test_expect_success 'invalid bool (set)' '
 
        test_must_fail git config --bool bool.nobool foobar'
 
-rm .git/config
-
 cat > expect <<\EOF
 [bool]
        true1 = true
@@ -622,6 +655,7 @@ EOF
 
 test_expect_success 'set --bool' '
 
+       rm -f .git/config &&
        git config --bool bool.true1 01 &&
        git config --bool bool.true2 -1 &&
        git config --bool bool.true3 YeS &&
@@ -632,8 +666,6 @@ test_expect_success 'set --bool' '
        git config --bool bool.false4 FALSE &&
        cmp expect .git/config'
 
-rm .git/config
-
 cat > expect <<\EOF
 [int]
        val1 = 1
@@ -643,13 +675,12 @@ EOF
 
 test_expect_success 'set --int' '
 
+       rm -f .git/config &&
        git config --int int.val1 01 &&
        git config --int int.val2 -1 &&
        git config --int int.val3 5m &&
        cmp expect .git/config'
 
-rm .git/config
-
 cat >expect <<\EOF
 [bool]
        true1 = true
@@ -663,6 +694,7 @@ cat >expect <<\EOF
 EOF
 
 test_expect_success 'get --bool-or-int' '
+       rm -f .git/config &&
        (
                echo "[bool]"
                echo true1
@@ -682,7 +714,6 @@ test_expect_success 'get --bool-or-int' '
 
 '
 
-rm .git/config
 cat >expect <<\EOF
 [bool]
        true1 = true
@@ -696,6 +727,7 @@ cat >expect <<\EOF
 EOF
 
 test_expect_success 'set --bool-or-int' '
+       rm -f .git/config &&
        git config --bool-or-int bool.true1 true &&
        git config --bool-or-int bool.false1 false &&
        git config --bool-or-int bool.true2 yes &&
@@ -706,8 +738,6 @@ test_expect_success 'set --bool-or-int' '
        test_cmp expect .git/config
 '
 
-rm .git/config
-
 cat >expect <<\EOF
 [path]
        home = ~/
@@ -716,6 +746,7 @@ cat >expect <<\EOF
 EOF
 
 test_expect_success NOT_MINGW 'set --path' '
+       rm -f .git/config &&
        git config --path path.home "~/" &&
        git config --path path.normal "/dev/null" &&
        git config --path path.trailingtilde "foo~" &&
@@ -756,13 +787,6 @@ test_expect_success NOT_MINGW 'get --path copes with unset $HOME' '
        test_cmp expect result
 '
 
-rm .git/config
-
-git config quote.leading " test"
-git config quote.ending "test "
-git config quote.semicolon "test;test"
-git config quote.hash "test#test"
-
 cat > expect << EOF
 [quote]
        leading = " test"
@@ -770,8 +794,14 @@ cat > expect << EOF
        semicolon = "test;test"
        hash = "test#test"
 EOF
-
-test_expect_success 'quoting' 'cmp .git/config expect'
+test_expect_success 'quoting' '
+       rm -f .git/config &&
+       git config quote.leading " test" &&
+       git config quote.ending "test " &&
+       git config quote.semicolon "test;test" &&
+       git config quote.hash "test#test" &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'key with newline' '
        test_must_fail git config "key.with
@@ -796,9 +826,10 @@ section.noncont=not continued
 section.quotecont=cont;inued
 EOF
 
-git config --list > result
-
-test_expect_success 'value continued on next line' 'cmp result expect'
+test_expect_success 'value continued on next line' '
+       git config --list > result &&
+       cmp result expect
+'
 
 cat > .git/config <<\EOF
 [section "sub=section"]
@@ -819,16 +850,17 @@ barQsection.sub=section.val3
 Qsection.sub=section.val4
 Qsection.sub=section.val5Q
 EOF
+test_expect_success '--null --list' '
+       git config --null --list | nul_to_q >result &&
+       echo >>result &&
+       test_cmp expect result
+'
 
-git config --null --list | perl -pe 'y/\000/Q/' > result
-echo >>result
-
-test_expect_success '--null --list' 'cmp result expect'
-
-git config --null --get-regexp 'val[0-9]' | perl -pe 'y/\000/Q/' > result
-echo >>result
-
-test_expect_success '--null --get-regexp' 'cmp result expect'
+test_expect_success '--null --get-regexp' '
+       git config --null --get-regexp "val[0-9]" | nul_to_q >result &&
+       echo >>result &&
+       test_cmp expect result
+'
 
 test_expect_success 'inner whitespace kept verbatim' '
        git config section.val "foo       bar" &&
index ed4275afe3100491ea57025632665182127f33cf..1ae4d87c929ec55a66d44bb23aecea8a2aea4346 100755 (executable)
@@ -5,34 +5,126 @@ 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'"
+       prereq=
+       case $1 in
+       [A-Z]*)
+               prereq=$1
+               shift
+       esac
+       test_expect_success $prereq "ref name '$1' is valid${2:+ with options $2}" "
+               git check-ref-format $2 '$1'
+       "
 }
 invalid_ref() {
-       test_expect_success "ref name '$1' is not valid" \
-               "test_must_fail git check-ref-format '$1'"
+       prereq=
+       case $1 in
+       [A-Z]*)
+               prereq=$1
+               shift
+       esac
+       test_expect_success $prereq "ref name '$1' is invalid${2:+ with options $2}" "
+               test_must_fail git check-ref-format $2 '$1'
+       "
 }
 
-valid_ref 'heads/foo'
-invalid_ref 'foo'
+invalid_ref ''
+invalid_ref NOT_MINGW '/'
+invalid_ref NOT_MINGW '/' --allow-onelevel
+invalid_ref NOT_MINGW '/' --normalize
+invalid_ref NOT_MINGW '/' '--allow-onelevel --normalize'
 valid_ref 'foo/bar/baz'
-valid_ref 'refs///heads/foo'
+valid_ref 'foo/bar/baz' --normalize
+invalid_ref 'refs///heads/foo'
+valid_ref 'refs///heads/foo' --normalize
 invalid_ref 'heads/foo/'
-valid_ref '/heads/foo'
-valid_ref '///heads/foo'
-invalid_ref '/foo'
+invalid_ref NOT_MINGW '/heads/foo'
+valid_ref NOT_MINGW '/heads/foo' --normalize
+invalid_ref '///heads/foo'
+valid_ref '///heads/foo' --normalize
 invalid_ref './foo'
+invalid_ref './foo/bar'
+invalid_ref 'foo/./bar'
+invalid_ref 'foo/bar/.'
 invalid_ref '.refs/foo'
 invalid_ref 'heads/foo..bar'
 invalid_ref 'heads/foo?bar'
 valid_ref 'foo./bar'
 invalid_ref 'heads/foo.lock'
+invalid_ref 'heads///foo.lock'
+invalid_ref 'foo.lock/bar'
+invalid_ref 'foo.lock///bar'
 valid_ref 'heads/foo@bar'
 invalid_ref 'heads/v@{ation'
 invalid_ref 'heads/foo\bar'
 invalid_ref "$(printf 'heads/foo\t')"
 invalid_ref "$(printf 'heads/foo\177')"
 valid_ref "$(printf 'heads/fu\303\237')"
+invalid_ref 'heads/*foo/bar' --refspec-pattern
+invalid_ref 'heads/foo*/bar' --refspec-pattern
+invalid_ref 'heads/f*o/bar' --refspec-pattern
+
+ref='foo'
+invalid_ref "$ref"
+valid_ref "$ref" --allow-onelevel
+invalid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+invalid_ref "$ref" --normalize
+valid_ref "$ref" '--allow-onelevel --normalize'
+
+ref='foo/bar'
+valid_ref "$ref"
+valid_ref "$ref" --allow-onelevel
+valid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+valid_ref "$ref" --normalize
+
+ref='foo/*'
+invalid_ref "$ref"
+invalid_ref "$ref" --allow-onelevel
+valid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='*/foo'
+invalid_ref "$ref"
+invalid_ref "$ref" --allow-onelevel
+valid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+invalid_ref "$ref" --normalize
+valid_ref "$ref" '--refspec-pattern --normalize'
+
+ref='foo/*/bar'
+invalid_ref "$ref"
+invalid_ref "$ref" --allow-onelevel
+valid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='*'
+invalid_ref "$ref"
+invalid_ref "$ref" --allow-onelevel
+invalid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='foo/*/*'
+invalid_ref "$ref" --refspec-pattern
+invalid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='*/foo/*'
+invalid_ref "$ref" --refspec-pattern
+invalid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='*/*/foo'
+invalid_ref "$ref" --refspec-pattern
+invalid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='/foo'
+invalid_ref NOT_MINGW "$ref"
+invalid_ref NOT_MINGW "$ref" --allow-onelevel
+invalid_ref NOT_MINGW "$ref" --refspec-pattern
+invalid_ref NOT_MINGW "$ref" '--refspec-pattern --allow-onelevel'
+invalid_ref NOT_MINGW "$ref" --normalize
+valid_ref NOT_MINGW "$ref" '--allow-onelevel --normalize'
+invalid_ref NOT_MINGW "$ref" '--refspec-pattern --normalize'
+valid_ref NOT_MINGW "$ref" '--refspec-pattern --allow-onelevel --normalize'
 
 test_expect_success "check-ref-format --branch @{-1}" '
        T=$(git write-tree) &&
@@ -65,23 +157,41 @@ test_expect_success 'check-ref-format --branch from subdir' '
 '
 
 valid_ref_normalized() {
-       test_expect_success "ref name '$1' simplifies to '$2'" "
-               refname=\$(git check-ref-format --print '$1') &&
-               test \"\$refname\" = '$2'"
+       prereq=
+       case $1 in
+       [A-Z]*)
+               prereq=$1
+               shift
+       esac
+       test_expect_success $prereq "ref name '$1' simplifies to '$2'" "
+               refname=\$(git check-ref-format --normalize '$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'"
+       prereq=
+       case $1 in
+       [A-Z]*)
+               prereq=$1
+               shift
+       esac
+       test_expect_success $prereq "check-ref-format --normalize rejects '$1'" "
+               test_must_fail git check-ref-format --normalize '$1'
+       "
 }
 
 valid_ref_normalized 'heads/foo' 'heads/foo'
 valid_ref_normalized 'refs///heads/foo' 'refs/heads/foo'
-valid_ref_normalized '/heads/foo' 'heads/foo'
+valid_ref_normalized NOT_MINGW '/heads/foo' 'heads/foo'
 valid_ref_normalized '///heads/foo' 'heads/foo'
 invalid_ref_normalized 'foo'
-invalid_ref_normalized '/foo'
+invalid_ref_normalized NOT_MINGW '/foo'
 invalid_ref_normalized 'heads/foo/../bar'
 invalid_ref_normalized 'heads/./foo'
 invalid_ref_normalized 'heads\foo'
+invalid_ref_normalized 'heads/foo.lock'
+invalid_ref_normalized 'heads///foo.lock'
+invalid_ref_normalized 'foo.lock/bar'
+invalid_ref_normalized 'foo.lock///bar'
 
 test_done
index 647d888507a4b74b82ae4016c2f30f7d171e98ca..3acd895afb7fde7f02a446b7508966e20734c4c5 100755 (executable)
@@ -20,7 +20,7 @@ test_expect_success 'setup reflog with alternating commits' '
 '
 
 test_expect_success 'reflog shows all entries' '
-       cat >expect <<-\EOF
+       cat >expect <<-\EOF &&
                topic@{0} reset: moving to two
                topic@{1} reset: moving to one
                topic@{2} reset: moving to two
index 523ce9c45b75d85a129015a56004473a5fccf926..5b8ebd805378dc79e449ec1eecd1e97e333a7902 100755 (executable)
@@ -191,4 +191,30 @@ test_expect_success 'cleaned up' '
        test_cmp empty actual
 '
 
+test_expect_success 'rev-list --verify-objects' '
+       git rev-list --verify-objects --all >/dev/null 2>out &&
+       test_cmp empty out
+'
+
+test_expect_success 'rev-list --verify-objects with bad sha1' '
+       sha=$(echo blob | git hash-object -w --stdin) &&
+       old=$(echo $sha | sed "s+^..+&/+") &&
+       new=$(dirname $old)/ffffffffffffffffffffffffffffffffffffff &&
+       sha="$(dirname $new)$(basename $new)" &&
+       mv .git/objects/$old .git/objects/$new &&
+       test_when_finished "remove_object $sha" &&
+       git update-index --add --cacheinfo 100644 $sha foo &&
+       test_when_finished "git read-tree -u --reset HEAD" &&
+       tree=$(git write-tree) &&
+       test_when_finished "remove_object $tree" &&
+       cmt=$(echo bogus | git commit-tree $tree) &&
+       test_when_finished "remove_object $cmt" &&
+       git update-ref refs/heads/bogus $cmt &&
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+
+       test_might_fail git rev-list --verify-objects refs/heads/bogus >/dev/null 2>out &&
+       cat out &&
+       grep -q "error: sha1 mismatch 63ffffffffffffffffffffffffffffffffffffff" out
+'
+
 test_done
index 63849836c8b088eb495dc565ff909c367c868301..e661147c573fa7312e8f533a4a4c8ea1eda7763f 100755 (executable)
@@ -48,7 +48,7 @@ test_expect_success 'setup: helper for testing rev-parse' '
 '
 
 test_expect_success 'setup: core.worktree = relative path' '
-       unset GIT_WORK_TREE;
+       sane_unset GIT_WORK_TREE &&
        GIT_DIR=repo.git &&
        GIT_CONFIG="$(pwd)"/$GIT_DIR/config &&
        export GIT_DIR GIT_CONFIG &&
@@ -89,7 +89,7 @@ test_expect_success 'subdir of work tree' '
 '
 
 test_expect_success 'setup: core.worktree = absolute path' '
-       unset GIT_WORK_TREE;
+       sane_unset GIT_WORK_TREE &&
        GIT_DIR=$(pwd)/repo.git &&
        GIT_CONFIG=$GIT_DIR/config &&
        export GIT_DIR GIT_CONFIG &&
@@ -334,7 +334,7 @@ test_expect_success 'absolute pathspec should fail gracefully' '
 '
 
 test_expect_success 'make_relative_path handles double slashes in GIT_DIR' '
-       >dummy_file
+       >dummy_file &&
        echo git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file &&
        git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file
 '
index ec50a9ad70450cb80074a6002c66bf2efb8e7833..80aedfca8c15bea1104b4985023f338059ae751c 100755 (executable)
@@ -603,7 +603,7 @@ test_expect_success '#22a: core.worktree = GIT_DIR = .git dir' '
        # like case #6.
 
        setup_repo 22a "$here/22a/.git" "" unset &&
-       setup_repo 22ab . "" unset
+       setup_repo 22ab . "" unset &&
        mkdir -p 22a/.git/sub 22a/sub &&
        mkdir -p 22ab/.git/sub 22ab/sub &&
        try_case 22a/.git unset . \
@@ -742,7 +742,7 @@ test_expect_success '#28: core.worktree and core.bare conflict (gitfile case)' '
 # Case #29: GIT_WORK_TREE(+core.worktree) overrides core.bare (gitfile case).
 test_expect_success '#29: setup' '
        setup_repo 29 non-existent gitfile true &&
-       mkdir -p 29/sub/sub 29/wt/sub
+       mkdir -p 29/sub/sub 29/wt/sub &&
        (
                cd 29 &&
                GIT_WORK_TREE="$here/29" &&
index e043cb7c64958ede89eb640f121d2daabf5f9503..eaefc777bd98aeb4cae2ba34eec0e5fe2c2fbd72 100755 (executable)
@@ -6,7 +6,7 @@ test_description='tests for ref^{stuff}'
 
 test_expect_success 'setup' '
        echo blob >a-blob &&
-       git tag -a -m blob blob-tag `git hash-object -w a-blob`
+       git tag -a -m blob blob-tag `git hash-object -w a-blob` &&
        mkdir a-tree &&
        echo moreblobs >a-tree/another-blob &&
        git add . &&
index c551d39a66c19514e260a7c938b840ce2870d45a..6352b74e2e54e5e08941d8d5d90ac30b202b56c1 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='checkout from unborn branch protects contents'
+test_description='checkout from unborn branch'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
@@ -37,4 +37,13 @@ test_expect_success 'checkout from unborn merges identical index contents' '
        git checkout -b new origin
 '
 
+test_expect_success 'checking out another branch from unborn state' '
+       git checkout --orphan newroot &&
+       git checkout -b anothername &&
+       test_must_fail git show-ref --verify refs/heads/newroot &&
+       git symbolic-ref HEAD >actual &&
+       echo refs/heads/anothername >expect &&
+       test_cmp expect actual
+'
+
 test_done
index 75874e85dfbcae8ea9634693a93524841b741559..2741262369e40a05cdc6732e4c9e6f04acb63bba 100755 (executable)
@@ -189,12 +189,13 @@ test_expect_success 'checkout -b <describe>' '
        test_cmp expect actual
 '
 
-test_expect_success 'checkout -B to the current branch fails before merging' '
+test_expect_success 'checkout -B to the current branch works' '
        git checkout branch1 &&
+       git checkout -B branch1-scratch &&
+
        setup_dirty_mergeable &&
-       git commit -mfooble &&
-       test_must_fail git checkout -B branch1 initial &&
-       test_must_fail test_dirty_mergeable
+       git checkout -B branch1-scratch initial &&
+       test_dirty_mergeable
 '
 
 test_done
index 2366f0f4141656666db9cdcd8975335df57b6a8f..068fba4c8e8706aa615d0401da1da47901113156 100755 (executable)
@@ -12,11 +12,14 @@ check_not_detached () {
 }
 
 ORPHAN_WARNING='you are leaving .* commit.*behind'
+PREV_HEAD_DESC='Previous HEAD position was'
 check_orphan_warning() {
-       test_i18ngrep "$ORPHAN_WARNING" "$1"
+       test_i18ngrep "$ORPHAN_WARNING" "$1" &&
+       test_i18ngrep ! "$PREV_HEAD_DESC" "$1"
 }
 check_no_orphan_warning() {
-       test_i18ngrep ! "$ORPHAN_WARNING" "$1"
+       test_i18ngrep ! "$ORPHAN_WARNING" "$1" &&
+       test_i18ngrep "$PREV_HEAD_DESC" "$1"
 }
 
 reset () {
diff --git a/t/t2023-checkout-m.sh b/t/t2023-checkout-m.sh
new file mode 100755 (executable)
index 0000000..7e18985
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+test_description='checkout -m -- <conflicted path>
+
+Ensures that checkout -m on a resolved file restores the conflicted file'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       test_tick &&
+       test_commit both.txt both.txt initial &&
+       git branch topic &&
+       test_commit modified_in_master both.txt in_master &&
+       test_commit added_in_master each.txt in_master &&
+       git checkout topic &&
+       test_commit modified_in_topic both.txt in_topic &&
+       test_commit added_in_topic each.txt in_topic
+'
+
+test_expect_success 'git merge master' '
+    test_must_fail git merge master
+'
+
+clean_branchnames () {
+       # Remove branch names after conflict lines
+       sed 's/^\([<>]\{5,\}\) .*$/\1/'
+}
+
+test_expect_success '-m restores 2-way conflicted+resolved file' '
+       cp each.txt each.txt.conflicted &&
+       echo resolved >each.txt &&
+       git add each.txt &&
+       git checkout -m -- each.txt &&
+       clean_branchnames <each.txt >each.txt.cleaned &&
+       clean_branchnames <each.txt.conflicted >each.txt.conflicted.cleaned &&
+       test_cmp each.txt.conflicted.cleaned each.txt.cleaned
+'
+
+test_expect_success '-m restores 3-way conflicted+resolved file' '
+       cp both.txt both.txt.conflicted &&
+       echo resolved >both.txt &&
+       git add both.txt &&
+       git checkout -m -- both.txt &&
+       clean_branchnames <both.txt >both.txt.cleaned &&
+       clean_branchnames <both.txt.conflicted >both.txt.conflicted.cleaned &&
+       test_cmp both.txt.conflicted.cleaned both.txt.cleaned
+'
+
+test_done
index 25435290a71ae8031762a3e63537fd3a23a8fc90..ec35409f9cbe9bcdd3d21a6a5a15996b165d2fff 100755 (executable)
@@ -32,7 +32,7 @@ test_expect_success 'intent to add does not clobber existing paths' '
        ! grep "$empty" actual
 '
 
-test_expect_success 'cannot commit with i-t-a entry' '
+test_expect_success 'i-t-a entry is simply ignored' '
        test_tick &&
        git commit -a -m initial &&
        git reset --hard &&
@@ -41,12 +41,14 @@ test_expect_success 'cannot commit with i-t-a entry' '
        echo frotz >nitfol &&
        git add rezrov &&
        git add -N nitfol &&
-       test_must_fail git commit -m initial
+       git commit -m second &&
+       test $(git ls-tree HEAD -- nitfol | wc -l) = 0 &&
+       test $(git diff --name-only HEAD -- nitfol | wc -l) = 1
 '
 
 test_expect_success 'can commit with an unrelated i-t-a entry in index' '
        git reset --hard &&
-       echo xyzzy >rezrov &&
+       echo bozbar >rezrov &&
        echo frotz >nitfol &&
        git add rezrov &&
        git add -N nitfol &&
index 2eec0118c4235c0aa9d85cb7112e1f72b49c5c5f..88be904c09214586b18f867306f7a8dcf1170cb7 100755 (executable)
@@ -65,4 +65,23 @@ test_expect_success '--no-empty-directory hides empty directory' '
        test_cmp expected3 output
 '
 
+test_expect_success SYMLINKS 'ls-files --others with symlinked submodule' '
+       git init super &&
+       git init sub &&
+       (
+               cd sub &&
+               >a &&
+               git add a &&
+               git commit -m sub &&
+               git pack-refs --all
+       ) &&
+       (
+               cd super &&
+               "$SHELL_PATH" "$TEST_DIRECTORY/../contrib/workdir/git-new-workdir" ../sub sub
+               git ls-files --others --exclude-standard >../actual
+       ) &&
+       echo sub/ >expect &&
+       test_cmp expect actual
+'
+
 test_done
index 55ef1895d7fd15348c47a5dc4a7f93541a1d38c1..a5e3da7e419e6f13ea0960722b4c1c712a995112 100755 (executable)
@@ -285,17 +285,7 @@ test_expect_success 'merge-recursive simple' '
        rm -fr [abcd] &&
        git checkout -f "$c2" &&
 
-       git merge-recursive "$c0" -- "$c2" "$c1"
-       status=$?
-       case "$status" in
-       1)
-               : happy
-               ;;
-       *)
-               echo >&2 "why status $status!!!"
-               false
-               ;;
-       esac
+       test_expect_code 1 git merge-recursive "$c0" -- "$c2" "$c1"
 '
 
 test_expect_success 'merge-recursive result' '
@@ -334,17 +324,7 @@ test_expect_success 'merge-recursive remove conflict' '
        rm -fr [abcd] &&
        git checkout -f "$c1" &&
 
-       git merge-recursive "$c0" -- "$c1" "$c5"
-       status=$?
-       case "$status" in
-       1)
-               : happy
-               ;;
-       *)
-               echo >&2 "why status $status!!!"
-               false
-               ;;
-       esac
+       test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c5"
 '
 
 test_expect_success 'merge-recursive remove conflict' '
@@ -388,17 +368,7 @@ test_expect_success 'merge-recursive d/f conflict' '
        git reset --hard &&
        git checkout -f "$c1" &&
 
-       git merge-recursive "$c0" -- "$c1" "$c4"
-       status=$?
-       case "$status" in
-       1)
-               : happy
-               ;;
-       *)
-               echo >&2 "why status $status!!!"
-               false
-               ;;
-       esac
+       test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c4"
 '
 
 test_expect_success 'merge-recursive d/f conflict result' '
@@ -422,17 +392,7 @@ test_expect_success 'merge-recursive d/f conflict the other way' '
        git reset --hard &&
        git checkout -f "$c4" &&
 
-       git merge-recursive "$c0" -- "$c4" "$c1"
-       status=$?
-       case "$status" in
-       1)
-               : happy
-               ;;
-       *)
-               echo >&2 "why status $status!!!"
-               false
-               ;;
-       esac
+       test_expect_code 1 git merge-recursive "$c0" -- "$c4" "$c1"
 '
 
 test_expect_success 'merge-recursive d/f conflict result the other way' '
@@ -456,17 +416,7 @@ test_expect_success 'merge-recursive d/f conflict' '
        git reset --hard &&
        git checkout -f "$c1" &&
 
-       git merge-recursive "$c0" -- "$c1" "$c6"
-       status=$?
-       case "$status" in
-       1)
-               : happy
-               ;;
-       *)
-               echo >&2 "why status $status!!!"
-               false
-               ;;
-       esac
+       test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c6"
 '
 
 test_expect_success 'merge-recursive d/f conflict result' '
@@ -490,17 +440,7 @@ test_expect_success 'merge-recursive d/f conflict' '
        git reset --hard &&
        git checkout -f "$c6" &&
 
-       git merge-recursive "$c0" -- "$c6" "$c1"
-       status=$?
-       case "$status" in
-       1)
-               : happy
-               ;;
-       *)
-               echo >&2 "why status $status!!!"
-               false
-               ;;
-       esac
+       test_expect_code 1 git merge-recursive "$c0" -- "$c6" "$c1"
 '
 
 test_expect_success 'merge-recursive d/f conflict result' '
index f6973e96a59916c6048222bfa0064aec5dea3746..0a4ff6d824a0a3bf44ba8d93c2a8dffdb9c1af65 100755 (executable)
@@ -3,81 +3,81 @@
 test_description='Basic subproject functionality'
 . ./test-lib.sh
 
-test_expect_success 'Super project creation' \
-    ': >Makefile &&
-    git add Makefile &&
-    git commit -m "Superproject created"'
-
-
-cat >expected <<EOF
-:000000 160000 00000... A      sub1
-:000000 160000 00000... A      sub2
-EOF
-test_expect_success 'create subprojects' \
-    'mkdir sub1 &&
-    ( cd sub1 && git init && : >Makefile && git add * &&
-    git commit -q -m "subproject 1" ) &&
-    mkdir sub2 &&
-    ( cd sub2 && git init && : >Makefile && git add * &&
-    git commit -q -m "subproject 2" ) &&
-    git update-index --add sub1 &&
-    git add sub2 &&
-    git commit -q -m "subprojects added" &&
-    git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current &&
-    test_cmp expected current'
-
-git branch save HEAD
-
-test_expect_success 'check if fsck ignores the subprojects' \
-    'git fsck --full'
-
-test_expect_success 'check if commit in a subproject detected' \
-    '( cd sub1 &&
-    echo "all:" >>Makefile &&
-    echo "     true" >>Makefile &&
-    git commit -q -a -m "make all" ) && {
-        git diff-files --exit-code
-       test $? = 1
-    }'
-
-test_expect_success 'check if a changed subproject HEAD can be committed' \
-    'git commit -q -a -m "sub1 changed" && {
-       git diff-tree --exit-code HEAD^ HEAD
-       test $? = 1
-    }'
-
-test_expect_success 'check if diff-index works for subproject elements' \
-    'git diff-index --exit-code --cached save -- sub1
-    test $? = 1'
-
-test_expect_success 'check if diff-tree works for subproject elements' \
-    'git diff-tree --exit-code HEAD^ HEAD -- sub1
-    test $? = 1'
-
-test_expect_success 'check if git diff works for subproject elements' \
-    'git diff --exit-code HEAD^ HEAD
-    test $? = 1'
-
-test_expect_success 'check if clone works' \
-    'git ls-files -s >expected &&
-    git clone -l -s . cloned &&
-    ( cd cloned && git ls-files -s ) >current &&
-    test_cmp expected current'
-
-test_expect_success 'removing and adding subproject' \
-    'git update-index --force-remove -- sub2 &&
-    mv sub2 sub3 &&
-    git add sub3 &&
-    git commit -q -m "renaming a subproject" && {
-       git diff -M --name-status --exit-code HEAD^ HEAD
-       test $? = 1
-    }'
+test_expect_success 'setup: create superproject' '
+       : >Makefile &&
+       git add Makefile &&
+       git commit -m "Superproject created"
+'
+
+test_expect_success 'setup: create subprojects' '
+       mkdir sub1 &&
+       ( cd sub1 && git init && : >Makefile && git add * &&
+       git commit -q -m "subproject 1" ) &&
+       mkdir sub2 &&
+       ( cd sub2 && git init && : >Makefile && git add * &&
+       git commit -q -m "subproject 2" ) &&
+       git update-index --add sub1 &&
+       git add sub2 &&
+       git commit -q -m "subprojects added" &&
+       git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current &&
+       git branch save HEAD &&
+       cat >expected <<-\EOF &&
+       :000000 160000 00000... A       sub1
+       :000000 160000 00000... A       sub2
+       EOF
+       test_cmp expected current
+'
+
+test_expect_success 'check if fsck ignores the subprojects' '
+       git fsck --full
+'
+
+test_expect_success 'check if commit in a subproject detected' '
+       ( cd sub1 &&
+       echo "all:" >>Makefile &&
+       echo "  true" >>Makefile &&
+       git commit -q -a -m "make all" ) &&
+       test_expect_code 1 git diff-files --exit-code
+'
+
+test_expect_success 'check if a changed subproject HEAD can be committed' '
+       git commit -q -a -m "sub1 changed" &&
+       test_expect_code 1 git diff-tree --exit-code HEAD^ HEAD
+'
+
+test_expect_success 'check if diff-index works for subproject elements' '
+       test_expect_code 1 git diff-index --exit-code --cached save -- sub1
+'
+
+test_expect_success 'check if diff-tree works for subproject elements' '
+       test_expect_code 1 git diff-tree --exit-code HEAD^ HEAD -- sub1
+'
+
+test_expect_success 'check if git diff works for subproject elements' '
+       test_expect_code 1 git diff --exit-code HEAD^ HEAD
+'
+
+test_expect_success 'check if clone works' '
+       git ls-files -s >expected &&
+       git clone -l -s . cloned &&
+       ( cd cloned && git ls-files -s ) >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'removing and adding subproject' '
+       git update-index --force-remove -- sub2 &&
+       mv sub2 sub3 &&
+       git add sub3 &&
+       git commit -q -m "renaming a subproject" &&
+       test_expect_code 1 git diff -M --name-status --exit-code HEAD^ HEAD
+'
 
 # the index must contain the object name the HEAD of the
 # subproject sub1 was at the point "save"
-test_expect_success 'checkout in superproject' \
-    'git checkout save &&
-    git diff-index --exit-code --raw --cached save -- sub1'
+test_expect_success 'checkout in superproject' '
+       git checkout save &&
+       git diff-index --exit-code --raw --cached save -- sub1
+'
 
 # just interesting what happened...
 # git diff --name-status -M save master
index 7633930bb472e1c9aebf2799f3eaf3bc53c7263c..9fe1d8feab419e1a8065b2ea5881f991edc68855 100755 (executable)
@@ -3,11 +3,8 @@
 # Copyright (c) 2005 Amos Waterland
 #
 
-test_description='git branch --foo should not create bogus branch
+test_description='git branch assorted tests'
 
-This test runs git branch --help and checks that the argument is properly
-handled.  Specifically, that a bogus branch is not created.
-'
 . ./test-lib.sh
 
 test_expect_success \
@@ -22,8 +19,8 @@ test_expect_success \
 
 test_expect_success \
     'git branch --help should not have created a bogus branch' '
-     git branch --help </dev/null >/dev/null 2>/dev/null;
-     ! test -f .git/refs/heads/--help
+     test_might_fail git branch --help </dev/null >/dev/null 2>/dev/null &&
+     test_path_is_missing .git/refs/heads/--help
 '
 
 test_expect_success 'branch -h in broken repository' '
@@ -39,11 +36,11 @@ test_expect_success 'branch -h in broken repository' '
 
 test_expect_success \
     'git branch abc should create a branch' \
-    'git branch abc && test -f .git/refs/heads/abc'
+    'git branch abc && test_path_is_file .git/refs/heads/abc'
 
 test_expect_success \
     'git branch a/b/c should create a branch' \
-    'git branch a/b/c && test -f .git/refs/heads/a/b/c'
+    'git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c'
 
 cat >expect <<EOF
 $_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000        branch: Created from master
@@ -52,15 +49,15 @@ test_expect_success \
     'git branch -l d/e/f should create a branch and a log' \
        'GIT_COMMITTER_DATE="2005-05-26 23:30" \
      git branch -l d/e/f &&
-        test -f .git/refs/heads/d/e/f &&
-        test -f .git/logs/refs/heads/d/e/f &&
+        test_path_is_file .git/refs/heads/d/e/f &&
+        test_path_is_file .git/logs/refs/heads/d/e/f &&
         test_cmp expect .git/logs/refs/heads/d/e/f'
 
 test_expect_success \
     'git branch -d d/e/f should delete a branch and a log' \
        'git branch -d d/e/f &&
-        test ! -f .git/refs/heads/d/e/f &&
-        test ! -f .git/logs/refs/heads/d/e/f'
+        test_path_is_missing .git/refs/heads/d/e/f &&
+        test_path_is_missing .git/logs/refs/heads/d/e/f'
 
 test_expect_success \
     'git branch j/k should work after branch j has been deleted' \
@@ -74,17 +71,22 @@ test_expect_success \
         git branch -d l/m &&
         git branch l'
 
+test_expect_success \
+    'git branch -m dumps usage' \
+       'test_expect_code 129 git branch -m 2>err &&
+       grep "[Uu]sage: git branch" err'
+
 test_expect_success \
     'git branch -m m m/m should work' \
        'git branch -l m &&
         git branch -m m m/m &&
-        test -f .git/logs/refs/heads/m/m'
+       test_path_is_file .git/logs/refs/heads/m/m'
 
 test_expect_success \
     'git branch -m n/n n should work' \
        'git branch -l n/n &&
-        git branch -m n/n n
-        test -f .git/logs/refs/heads/n'
+       git branch -m n/n n &&
+       test_path_is_file .git/logs/refs/heads/n'
 
 test_expect_success 'git branch -m o/o o should fail when o/p exists' '
        git branch o/o &&
@@ -110,6 +112,54 @@ test_expect_success 'git branch -M baz bam should succeed when baz is checked ou
        git branch -M baz bam
 '
 
+test_expect_success 'git branch -M master should work when master is checked out' '
+       git checkout master &&
+       git branch -M master
+'
+
+test_expect_success 'git branch -M master master should work when master is checked out' '
+       git checkout master &&
+       git branch -M master master
+'
+
+test_expect_success 'git branch -M master2 master2 should work when master is checked out' '
+       git checkout master &&
+       git branch master2 &&
+       git branch -M master2 master2
+'
+
+test_expect_success 'git branch -v -d t should work' '
+       git branch t &&
+       test_path_is_file .git/refs/heads/t &&
+       git branch -v -d t &&
+       test_path_is_missing .git/refs/heads/t
+'
+
+test_expect_success 'git branch -v -m t s should work' '
+       git branch t &&
+       test_path_is_file .git/refs/heads/t &&
+       git branch -v -m t s &&
+       test_path_is_missing .git/refs/heads/t &&
+       test_path_is_file .git/refs/heads/s &&
+       git branch -d s
+'
+
+test_expect_success 'git branch -m -d t s should fail' '
+       git branch t &&
+       test_path_is_file .git/refs/heads/t &&
+       test_must_fail git branch -m -d t s &&
+       git branch -d t &&
+       test_path_is_missing .git/refs/heads/t
+'
+
+test_expect_success 'git branch --list -d t should fail' '
+       git branch t &&
+       test_path_is_file .git/refs/heads/t &&
+       test_must_fail git branch --list -d t &&
+       git branch -d t &&
+       test_path_is_missing .git/refs/heads/t
+'
+
 mv .git/config .git/config-saved
 
 test_expect_success 'git branch -m q q2 without config should succeed' '
@@ -124,12 +174,12 @@ git config branch.s/s.dummy Hello
 test_expect_success \
     'git branch -m s/s s should work when s/t is deleted' \
        'git branch -l s/s &&
-        test -f .git/logs/refs/heads/s/s &&
+       test_path_is_file .git/logs/refs/heads/s/s &&
         git branch -l s/t &&
-        test -f .git/logs/refs/heads/s/t &&
+       test_path_is_file .git/logs/refs/heads/s/t &&
         git branch -d s/t &&
         git branch -m s/s s &&
-        test -f .git/logs/refs/heads/s'
+       test_path_is_file .git/logs/refs/heads/s'
 
 test_expect_success 'config information was renamed, too' \
        "test $(git config branch.s.dummy) = Hello &&
@@ -140,8 +190,8 @@ test_expect_success 'renaming a symref is not allowed' \
        git symbolic-ref refs/heads/master2 refs/heads/master &&
        test_must_fail git branch -m master2 master3 &&
        git symbolic-ref refs/heads/master2 &&
-       test -f .git/refs/heads/master &&
-       ! test -f .git/refs/heads/master3
+       test_path_is_file .git/refs/heads/master &&
+       test_path_is_missing .git/refs/heads/master3
 '
 
 test_expect_success SYMLINKS \
@@ -250,8 +300,8 @@ test_expect_success \
     'git checkout -b g/h/i -l should create a branch and a log' \
        'GIT_COMMITTER_DATE="2005-05-26 23:30" \
      git checkout -b g/h/i -l master &&
-        test -f .git/refs/heads/g/h/i &&
-        test -f .git/logs/refs/heads/g/h/i &&
+        test_path_is_file .git/refs/heads/g/h/i &&
+        test_path_is_file .git/logs/refs/heads/g/h/i &&
         test_cmp expect .git/logs/refs/heads/g/h/i'
 
 test_expect_success 'checkout -b makes reflog by default' '
@@ -567,4 +617,44 @@ test_expect_success 'use set-upstream on the current branch' '
 
 '
 
+test_expect_success 'use --edit-description' '
+       write_script editor <<-\EOF &&
+               echo "New contents" >"$1"
+       EOF
+       EDITOR=./editor git branch --edit-description &&
+               write_script editor <<-\EOF &&
+               git stripspace -s <"$1" >"EDITOR_OUTPUT"
+       EOF
+       EDITOR=./editor git branch --edit-description &&
+       echo "New contents" >expect &&
+       test_cmp EDITOR_OUTPUT expect
+'
+
+test_expect_success 'detect typo in branch name when using --edit-description' '
+       write_script editor <<-\EOF &&
+               echo "New contents" >"$1"
+       EOF
+       (
+               EDITOR=./editor &&
+               export EDITOR &&
+               test_must_fail git branch --edit-description no-such-branch
+       )
+'
+
+test_expect_success 'refuse --edit-description on unborn branch for now' '
+       write_script editor <<-\EOF &&
+               echo "New contents" >"$1"
+       EOF
+       git checkout --orphan unborn &&
+       (
+               EDITOR=./editor &&
+               export EDITOR &&
+               test_must_fail git branch --edit-description
+       )
+'
+
+test_expect_success '--merged catches invalid object names' '
+       test_must_fail git branch --merged 0000000000000000000000000000000000000000
+'
+
 test_done
index 6b7c118e4fcbf3855318b2e9e3b721d07950fd25..76fe7e0060c20507cb6eb317451b69a2a0c9146d 100755 (executable)
@@ -32,6 +32,20 @@ test_expect_success 'git branch shows local branches' '
        test_cmp expect actual
 '
 
+test_expect_success 'git branch --list shows local branches' '
+       git branch --list >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+  branch-one
+  branch-two
+EOF
+test_expect_success 'git branch --list pattern shows matching local branches' '
+       git branch --list branch* >actual &&
+       test_cmp expect actual
+'
+
 cat >expect <<'EOF'
   origin/HEAD -> origin/branch-one
   origin/branch-one
@@ -66,6 +80,20 @@ test_expect_success 'git branch -v shows branch summaries' '
        test_cmp expect actual
 '
 
+cat >expect <<'EOF'
+two
+one
+EOF
+test_expect_success 'git branch --list -v pattern shows branch summaries' '
+       git branch --list -v branch* >tmp &&
+       awk "{print \$NF}" <tmp >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git branch -v pattern does not show branch summaries' '
+       test_must_fail git branch -v branch*
+'
+
 cat >expect <<'EOF'
 * (no branch)
   branch-one
index 5e29a052599bc28c0edaf56dfedb5f069b5ff01e..9f00ada5f7776b2377993cc9392b89e5312a513f 100755 (executable)
@@ -167,7 +167,7 @@ test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
 test_expect_success TABS_IN_FILENAMES 'setup expect' '
 cat >expected <<\EOF
  "tabs\t,\" (dq) and spaces"
- 1 files changed, 0 insertions(+), 0 deletions(-)
+ 1 file changed, 0 insertions(+), 0 deletions(-)
 EOF
 '
 
index 4ec4d11450e0cee83ed0b3639341a1e1a0ea6f7f..436719795376f78e3a32a441e9e7e0a4606ac2f5 100755 (executable)
@@ -389,7 +389,7 @@ test_expect_success 'abort notes merge' '
        test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
        test_cmp /dev/null output &&
        # m has not moved (still == y)
-       test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+       test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" &&
        # Verify that other notes refs has not changed (w, x, y and z)
        verify_notes w &&
        verify_notes x &&
@@ -525,9 +525,9 @@ EOF
        test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
        test -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
        # Refs are unchanged
-       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
-       test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)"
-       test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)"
+       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
+       test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&
+       test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&
        # Mention refs/notes/m, and its current and expected value in output
        grep -q "refs/notes/m" output &&
        grep -q "$(git rev-parse refs/notes/m)" output &&
@@ -545,7 +545,7 @@ test_expect_success 'resolve situation by aborting the notes merge' '
        test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
        test_cmp /dev/null output &&
        # m has not moved (still == w)
-       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
        # Verify that other notes refs has not changed (w, x, y and z)
        verify_notes w &&
        verify_notes x &&
index 6eaecec906c49749237b243f772f2e33eb0efedd..e647272a01f3ac89e1d08e6b1ffa36a3a543e655 100755 (executable)
@@ -172,8 +172,8 @@ test_expect_success 'fail when upstream arg is missing and not configured' '
 
 test_expect_success 'default to @{upstream} when upstream arg is missing' '
        git checkout -b default topic &&
-       git config branch.default.remote .
-       git config branch.default.merge refs/heads/master
+       git config branch.default.remote . &&
+       git config branch.default.merge refs/heads/master &&
        git rebase &&
        test "$(git rev-parse default~1)" = "$(git rev-parse master)"
 '
@@ -218,4 +218,27 @@ test_expect_success 'rebase -m can copy notes' '
        test "a note" = "$(git notes show HEAD)"
 '
 
+test_expect_success 'rebase commit with an ancient timestamp' '
+       git reset --hard &&
+
+       >old.one && git add old.one && test_tick &&
+       git commit --date="@12345 +0400" -m "Old one" &&
+       >old.two && git add old.two && test_tick &&
+       git commit --date="@23456 +0500" -m "Old two" &&
+       >old.three && git add old.three && test_tick &&
+       git commit --date="@34567 +0600" -m "Old three" &&
+
+       git cat-file commit HEAD^^ >actual &&
+       grep "author .* 12345 +0400$" actual &&
+       git cat-file commit HEAD^ >actual &&
+       grep "author .* 23456 +0500$" actual &&
+       git cat-file commit HEAD >actual &&
+       grep "author .* 34567 +0600$" actual &&
+
+       git rebase --onto HEAD^^ HEAD^ &&
+
+       git cat-file commit HEAD >actual &&
+       grep "author .* 34567 +0600$" actual
+'
+
 test_done
index aea6685984b9f0e132d34842c3ac99d7ea044905..7ba17974c585d005fb4f1c757b76377f0c0518a5 100755 (executable)
@@ -11,51 +11,35 @@ local branch.
 '
 . ./test-lib.sh
 
-test_expect_success \
-    'prepare repository with topic branch' \
-    'echo First > A &&
-     git update-index --add A &&
-     git commit -m "Add A." &&
-
-     git checkout -b my-topic-branch &&
-
-     echo Second > B &&
-     git update-index --add B &&
-     git commit -m "Add B." &&
-
-     echo AnotherSecond > C &&
-     git update-index --add C &&
-     git commit -m "Add C." &&
-
-     git checkout -f master &&
-
-     echo Third >> A &&
-     git update-index A &&
-     git commit -m "Modify A."
+test_expect_success 'prepare repository with topic branch' '
+       test_commit A &&
+       git checkout -b my-topic-branch &&
+       test_commit B &&
+       test_commit C &&
+       git checkout -f master &&
+       test_commit A2 A.t
 '
 
-test_expect_success \
-    'pick top patch from topic branch into master' \
-    'git cherry-pick my-topic-branch^0 &&
-     git checkout -f my-topic-branch &&
-     git branch master-merge master &&
-     git branch my-topic-branch-merge my-topic-branch
+test_expect_success 'pick top patch from topic branch into master' '
+       git cherry-pick C &&
+       git checkout -f my-topic-branch
 '
 
-test_debug \
-    'git cherry master &&
-     git format-patch -k --stdout --full-index master >/dev/null &&
-     gitk --all & sleep 1
+test_debug '
+       git cherry master &&
+       git format-patch -k --stdout --full-index master >/dev/null &&
+       gitk --all & sleep 1
 '
 
-test_expect_success \
-    'rebase topic branch against new master and check git am did not get halted' \
-    'git rebase master && test ! -d .git/rebase-apply'
+test_expect_success 'rebase topic branch against new master and check git am did not get halted' '
+       git rebase master &&
+       test_path_is_missing .git/rebase-apply
+'
 
-test_expect_success \
-       'rebase --merge topic branch that was partially merged upstream' \
-       'git checkout -f my-topic-branch-merge &&
-        git rebase --merge master-merge &&
-        test ! -d .git/rebase-merge'
+test_expect_success 'rebase --merge topic branch that was partially merged upstream' '
+       git reset --hard C &&
+       git rebase --merge master &&
+       test_path_is_missing .git/rebase-merge
+'
 
 test_done
index 1e855cdae55ff46cbb878b724328b57cdb8069ce..2680375628207d0d46ee4a9c8335840609461e0a 100755 (executable)
@@ -51,7 +51,7 @@ test_expect_success 'rebase --continue remembers merge strategy and options' '
        test_commit "commit-new-file-F3-on-topic-branch" F3 32 &&
        test_when_finished "rm -fr test-bin funny.was.run" &&
        mkdir test-bin &&
-       cat >test-bin/git-merge-funny <<-EOF
+       cat >test-bin/git-merge-funny <<-EOF &&
        #!$SHELL_PATH
        case "\$1" in --opt) ;; *) exit 2 ;; esac
        shift &&
@@ -77,7 +77,7 @@ test_expect_success 'rebase --continue remembers merge strategy and options' '
 test_expect_success 'rebase --continue remembers --rerere-autoupdate' '
        rm -fr .git/rebase-* &&
        git reset --hard commit-new-file-F3-on-topic-branch &&
-       git checkout master
+       git checkout master &&
        test_commit "commit-new-file-F3" F3 3 &&
        git config rerere.enabled true &&
        test_must_fail git rebase -m master topic &&
index bd8efaf005a9708f153ea873850acca994c64f29..e70ac10a0cdbcd112b7b3c3e74aa89b09d46d1df 100755 (executable)
@@ -39,7 +39,7 @@ run()
 }
 
 test_expect_success 'setup' '
-       git commit --allow-empty -m initial
+       git commit --allow-empty -m initial &&
        git tag root
 '
 
index 0ab52da902c8d602e9c4d64660aa4a7e8e35544f..e37547f41a21ebda150aad6797012c9df3742382 100755 (executable)
@@ -35,7 +35,7 @@ test_expect_success 'cherry-pick a non-merge with -m should fail' '
 
        git reset --hard &&
        git checkout a^0 &&
-       test_must_fail git cherry-pick -m 1 b &&
+       test_expect_code 128 git cherry-pick -m 1 b &&
        git diff --exit-code a --
 
 '
index 212ec54aaf0d805bbdecd93f3311d248a57d5b08..ee1659c17810d2c9ce7836582a80fd3d0d89ca24 100755 (executable)
@@ -77,6 +77,21 @@ test_expect_success 'cherry-pick --no-commit does not set CHERRY_PICK_HEAD' '
        test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
 '
 
+test_expect_success 'cherry-pick w/dirty tree does not set CHERRY_PICK_HEAD' '
+       pristine_detach initial &&
+       echo foo > foo &&
+       test_must_fail git cherry-pick base &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
+test_expect_success \
+       'cherry-pick --strategy=resolve w/dirty tree does not set CHERRY_PICK_HEAD' '
+       pristine_detach initial &&
+       echo foo > foo &&
+       test_must_fail git cherry-pick --strategy=resolve base &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
 test_expect_success 'GIT_CHERRY_PICK_HELP suppresses CHERRY_PICK_HEAD' '
        pristine_detach initial &&
        (
@@ -238,6 +253,60 @@ test_expect_success 'revert also handles conflicts sanely' '
        test_cmp expected actual
 '
 
+test_expect_success 'failed revert sets REVERT_HEAD' '
+       pristine_detach initial &&
+       test_must_fail git revert picked &&
+       test_cmp_rev picked REVERT_HEAD
+'
+
+test_expect_success 'successful revert does not set REVERT_HEAD' '
+       pristine_detach base &&
+       git revert base &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+       test_must_fail git rev-parse --verify REVERT_HEAD
+'
+
+test_expect_success 'revert --no-commit sets REVERT_HEAD' '
+       pristine_detach base &&
+       git revert --no-commit base &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+       test_cmp_rev base REVERT_HEAD
+'
+
+test_expect_success 'revert w/dirty tree does not set REVERT_HEAD' '
+       pristine_detach base &&
+       echo foo > foo &&
+       test_must_fail git revert base &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+       test_must_fail git rev-parse --verify REVERT_HEAD
+'
+
+test_expect_success 'GIT_CHERRY_PICK_HELP does not suppress REVERT_HEAD' '
+       pristine_detach initial &&
+       (
+               GIT_CHERRY_PICK_HELP="and then do something else" &&
+               GIT_REVERT_HELP="and then do something else, again" &&
+               export GIT_CHERRY_PICK_HELP GIT_REVERT_HELP &&
+               test_must_fail git revert picked
+       ) &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+       test_cmp_rev picked REVERT_HEAD
+'
+
+test_expect_success 'git reset clears REVERT_HEAD' '
+       pristine_detach initial &&
+       test_must_fail git revert picked &&
+       git reset &&
+       test_must_fail git rev-parse --verify REVERT_HEAD
+'
+
+test_expect_success 'failed commit does not clear REVERT_HEAD' '
+       pristine_detach initial &&
+       test_must_fail git revert picked &&
+       test_must_fail git commit &&
+       test_cmp_rev picked REVERT_HEAD
+'
+
 test_expect_success 'revert conflict, diff3 -m style' '
        pristine_detach initial &&
        git config merge.conflictstyle diff3 &&
index 8e09fd0319c95cbd4d30c461f00fee5f52e27cbd..1b3a344158aa8077c1e5b47f9ab8bd6394e153ed 100755 (executable)
@@ -38,13 +38,13 @@ test_expect_success 'cherry-pick first..fourth works' '
        cat <<-\EOF >expected &&
        [master OBJID] second
         Author: A U Thor <author@example.com>
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        [master OBJID] third
         Author: A U Thor <author@example.com>
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        [master OBJID] fourth
         Author: A U Thor <author@example.com>
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        EOF
 
        git checkout -f master &&
@@ -64,15 +64,15 @@ test_expect_success 'cherry-pick --strategy resolve first..fourth works' '
        Trying simple merge.
        [master OBJID] second
         Author: A U Thor <author@example.com>
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        Trying simple merge.
        [master OBJID] third
         Author: A U Thor <author@example.com>
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        Trying simple merge.
        [master OBJID] fourth
         Author: A U Thor <author@example.com>
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        EOF
 
        git checkout -f master &&
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
new file mode 100755 (executable)
index 0000000..97f3710
--- /dev/null
@@ -0,0 +1,520 @@
+#!/bin/sh
+
+test_description='Test cherry-pick continuation features
+
+ +  conflicting: rewrites unrelated to conflicting
+  + yetanotherpick: rewrites foo to e
+  + anotherpick: rewrites foo to d
+  + picked: rewrites foo to c
+  + unrelatedpick: rewrites unrelated to reallyunrelated
+  + base: rewrites foo to b
+  + initial: writes foo as a, unrelated as unrelated
+
+'
+
+. ./test-lib.sh
+
+# Repeat first match 10 times
+_r10='\1\1\1\1\1\1\1\1\1\1'
+
+pristine_detach () {
+       git cherry-pick --quit &&
+       git checkout -f "$1^0" &&
+       git read-tree -u --reset HEAD &&
+       git clean -d -f -f -q -x
+}
+
+test_cmp_rev () {
+       git rev-parse --verify "$1" >expect.rev &&
+       git rev-parse --verify "$2" >actual.rev &&
+       test_cmp expect.rev actual.rev
+}
+
+test_expect_success setup '
+       git config advice.detachedhead false
+       echo unrelated >unrelated &&
+       git add unrelated &&
+       test_commit initial foo a &&
+       test_commit base foo b &&
+       test_commit unrelatedpick unrelated reallyunrelated &&
+       test_commit picked foo c &&
+       test_commit anotherpick foo d &&
+       test_commit yetanotherpick foo e &&
+       pristine_detach initial &&
+       test_commit conflicting unrelated
+'
+
+test_expect_success 'cherry-pick persists data on failure' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick -s base..anotherpick &&
+       test_path_is_dir .git/sequencer &&
+       test_path_is_file .git/sequencer/head &&
+       test_path_is_file .git/sequencer/todo &&
+       test_path_is_file .git/sequencer/opts
+'
+
+test_expect_success 'cherry-pick mid-cherry-pick-sequence' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..anotherpick &&
+       test_cmp_rev picked CHERRY_PICK_HEAD &&
+       # "oops, I forgot that these patches rely on the change from base"
+       git checkout HEAD foo &&
+       git cherry-pick base &&
+       git cherry-pick picked &&
+       git cherry-pick --continue &&
+       git diff --exit-code anotherpick
+'
+
+test_expect_success 'cherry-pick persists opts correctly' '
+       pristine_detach initial &&
+       test_expect_code 128 git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours initial..anotherpick &&
+       test_path_is_dir .git/sequencer &&
+       test_path_is_file .git/sequencer/head &&
+       test_path_is_file .git/sequencer/todo &&
+       test_path_is_file .git/sequencer/opts &&
+       echo "true" >expect &&
+       git config --file=.git/sequencer/opts --get-all options.signoff >actual &&
+       test_cmp expect actual &&
+       echo "1" >expect &&
+       git config --file=.git/sequencer/opts --get-all options.mainline >actual &&
+       test_cmp expect actual &&
+       echo "recursive" >expect &&
+       git config --file=.git/sequencer/opts --get-all options.strategy >actual &&
+       test_cmp expect actual &&
+       cat >expect <<-\EOF &&
+       patience
+       ours
+       EOF
+       git config --file=.git/sequencer/opts --get-all options.strategy-option >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick cleans up sequencer state upon success' '
+       pristine_detach initial &&
+       git cherry-pick initial..picked &&
+       test_path_is_missing .git/sequencer
+'
+
+test_expect_success '--quit does not complain when no cherry-pick is in progress' '
+       pristine_detach initial &&
+       git cherry-pick --quit
+'
+
+test_expect_success '--abort requires cherry-pick in progress' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick --abort
+'
+
+test_expect_success '--quit cleans up sequencer state' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick base..picked &&
+       git cherry-pick --quit &&
+       test_path_is_missing .git/sequencer
+'
+
+test_expect_success '--quit keeps HEAD and conflicted index intact' '
+       pristine_detach initial &&
+       cat >expect <<-\EOF &&
+       OBJID
+       :100644 100644 OBJID OBJID M    unrelated
+       OBJID
+       :000000 100644 OBJID OBJID A    foo
+       :000000 100644 OBJID OBJID A    unrelated
+       EOF
+       test_expect_code 1 git cherry-pick base..picked &&
+       git cherry-pick --quit &&
+       test_path_is_missing .git/sequencer &&
+       test_must_fail git update-index --refresh &&
+       {
+               git rev-list HEAD |
+               git diff-tree --root --stdin |
+               sed "s/$_x40/OBJID/g"
+       } >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--abort to cancel multiple cherry-pick' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick base..anotherpick &&
+       git cherry-pick --abort &&
+       test_path_is_missing .git/sequencer &&
+       test_cmp_rev initial HEAD &&
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD
+'
+
+test_expect_success '--abort to cancel single cherry-pick' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick picked &&
+       git cherry-pick --abort &&
+       test_path_is_missing .git/sequencer &&
+       test_cmp_rev initial HEAD &&
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD
+'
+
+test_expect_success 'cherry-pick --abort to cancel multiple revert' '
+       pristine_detach anotherpick &&
+       test_expect_code 1 git revert base..picked &&
+       git cherry-pick --abort &&
+       test_path_is_missing .git/sequencer &&
+       test_cmp_rev anotherpick HEAD &&
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD
+'
+
+test_expect_success 'revert --abort works, too' '
+       pristine_detach anotherpick &&
+       test_expect_code 1 git revert base..picked &&
+       git revert --abort &&
+       test_path_is_missing .git/sequencer &&
+       test_cmp_rev anotherpick HEAD
+'
+
+test_expect_success '--abort to cancel single revert' '
+       pristine_detach anotherpick &&
+       test_expect_code 1 git revert picked &&
+       git revert --abort &&
+       test_path_is_missing .git/sequencer &&
+       test_cmp_rev anotherpick HEAD &&
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD
+'
+
+test_expect_success '--abort keeps unrelated change, easy case' '
+       pristine_detach unrelatedpick &&
+       echo changed >expect &&
+       test_expect_code 1 git cherry-pick picked..yetanotherpick &&
+       echo changed >unrelated &&
+       git cherry-pick --abort &&
+       test_cmp expect unrelated
+'
+
+test_expect_success '--abort refuses to clobber unrelated change, harder case' '
+       pristine_detach initial &&
+       echo changed >expect &&
+       test_expect_code 1 git cherry-pick base..anotherpick &&
+       echo changed >unrelated &&
+       test_must_fail git cherry-pick --abort &&
+       test_cmp expect unrelated &&
+       git rev-list HEAD >log &&
+       test_line_count = 2 log &&
+       test_must_fail git update-index --refresh &&
+
+       git checkout unrelated &&
+       git cherry-pick --abort &&
+       test_cmp_rev initial HEAD
+'
+
+test_expect_success 'cherry-pick still writes sequencer state when one commit is left' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick base..picked &&
+       test_path_is_dir .git/sequencer &&
+       echo "resolved" >foo &&
+       git add foo &&
+       git commit &&
+       {
+               git rev-list HEAD |
+               git diff-tree --root --stdin |
+               sed "s/$_x40/OBJID/g"
+       } >actual &&
+       cat >expect <<-\EOF &&
+       OBJID
+       :100644 100644 OBJID OBJID M    foo
+       OBJID
+       :100644 100644 OBJID OBJID M    unrelated
+       OBJID
+       :000000 100644 OBJID OBJID A    foo
+       :000000 100644 OBJID OBJID A    unrelated
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success '--abort after last commit in sequence' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick base..picked &&
+       git cherry-pick --abort &&
+       test_path_is_missing .git/sequencer &&
+       test_cmp_rev initial HEAD &&
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD
+'
+
+test_expect_success 'cherry-pick does not implicitly stomp an existing operation' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick base..anotherpick &&
+       test-chmtime -v +0 .git/sequencer >expect &&
+       test_expect_code 128 git cherry-pick unrelatedpick &&
+       test-chmtime -v +0 .git/sequencer >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--continue complains when no cherry-pick is in progress' '
+       pristine_detach initial &&
+       test_expect_code 128 git cherry-pick --continue
+'
+
+test_expect_success '--continue complains when there are unresolved conflicts' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick base..anotherpick &&
+       test_expect_code 128 git cherry-pick --continue
+'
+
+test_expect_success '--continue of single cherry-pick' '
+       pristine_detach initial &&
+       echo c >expect &&
+       test_must_fail git cherry-pick picked &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+
+       test_cmp expect foo &&
+       test_cmp_rev initial HEAD^ &&
+       git diff --exit-code HEAD &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
+test_expect_success '--continue of single revert' '
+       pristine_detach initial &&
+       echo resolved >expect &&
+       echo "Revert \"picked\"" >expect.msg &&
+       test_must_fail git revert picked &&
+       echo resolved >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+
+       git diff --exit-code HEAD &&
+       test_cmp expect foo &&
+       test_cmp_rev initial HEAD^ &&
+       git diff-tree -s --pretty=tformat:%s HEAD >msg &&
+       test_cmp expect.msg msg &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+       test_must_fail git rev-parse --verify REVERT_HEAD
+'
+
+test_expect_success '--continue after resolving conflicts' '
+       pristine_detach initial &&
+       echo d >expect &&
+       cat >expect.log <<-\EOF &&
+       OBJID
+       :100644 100644 OBJID OBJID M    foo
+       OBJID
+       :100644 100644 OBJID OBJID M    foo
+       OBJID
+       :100644 100644 OBJID OBJID M    unrelated
+       OBJID
+       :000000 100644 OBJID OBJID A    foo
+       :000000 100644 OBJID OBJID A    unrelated
+       EOF
+       test_must_fail git cherry-pick base..anotherpick &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+       {
+               git rev-list HEAD |
+               git diff-tree --root --stdin |
+               sed "s/$_x40/OBJID/g"
+       } >actual.log &&
+       test_cmp expect foo &&
+       test_cmp expect.log actual.log
+'
+
+test_expect_success '--continue after resolving conflicts and committing' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick base..anotherpick &&
+       echo "c" >foo &&
+       git add foo &&
+       git commit &&
+       git cherry-pick --continue &&
+       test_path_is_missing .git/sequencer &&
+       {
+               git rev-list HEAD |
+               git diff-tree --root --stdin |
+               sed "s/$_x40/OBJID/g"
+       } >actual &&
+       cat >expect <<-\EOF &&
+       OBJID
+       :100644 100644 OBJID OBJID M    foo
+       OBJID
+       :100644 100644 OBJID OBJID M    foo
+       OBJID
+       :100644 100644 OBJID OBJID M    unrelated
+       OBJID
+       :000000 100644 OBJID OBJID A    foo
+       :000000 100644 OBJID OBJID A    unrelated
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success '--continue asks for help after resolving patch to nil' '
+       pristine_detach conflicting &&
+       test_must_fail git cherry-pick initial..picked &&
+
+       test_cmp_rev unrelatedpick CHERRY_PICK_HEAD &&
+       git checkout HEAD -- unrelated &&
+       test_must_fail git cherry-pick --continue 2>msg &&
+       test_i18ngrep "The previous cherry-pick is now empty" msg
+'
+
+test_expect_success 'follow advice and skip nil patch' '
+       pristine_detach conflicting &&
+       test_must_fail git cherry-pick initial..picked &&
+
+       git checkout HEAD -- unrelated &&
+       test_must_fail git cherry-pick --continue &&
+       git reset &&
+       git cherry-pick --continue &&
+
+       git rev-list initial..HEAD >commits &&
+       test_line_count = 3 commits
+'
+
+test_expect_success '--continue respects opts' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick -x base..anotherpick &&
+       echo "c" >foo &&
+       git add foo &&
+       git commit &&
+       git cherry-pick --continue &&
+       test_path_is_missing .git/sequencer &&
+       git cat-file commit HEAD >anotherpick_msg &&
+       git cat-file commit HEAD~1 >picked_msg &&
+       git cat-file commit HEAD~2 >unrelatedpick_msg &&
+       git cat-file commit HEAD~3 >initial_msg &&
+       test_must_fail grep "cherry picked from" initial_msg &&
+       grep "cherry picked from" unrelatedpick_msg &&
+       grep "cherry picked from" picked_msg &&
+       grep "cherry picked from" anotherpick_msg
+'
+
+test_expect_success '--continue of single-pick respects -x' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -x picked &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+       test_path_is_missing .git/sequencer &&
+       git cat-file commit HEAD >msg &&
+       grep "cherry picked from" msg
+'
+
+test_expect_success '--continue respects -x in first commit in multi-pick' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -x picked anotherpick &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+       test_path_is_missing .git/sequencer &&
+       git cat-file commit HEAD^ >msg &&
+       picked=$(git rev-parse --verify picked) &&
+       grep "cherry picked from.*$picked" msg
+'
+
+test_expect_success '--signoff is not automatically propagated to resolved conflict' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick --signoff base..anotherpick &&
+       echo "c" >foo &&
+       git add foo &&
+       git commit &&
+       git cherry-pick --continue &&
+       test_path_is_missing .git/sequencer &&
+       git cat-file commit HEAD >anotherpick_msg &&
+       git cat-file commit HEAD~1 >picked_msg &&
+       git cat-file commit HEAD~2 >unrelatedpick_msg &&
+       git cat-file commit HEAD~3 >initial_msg &&
+       test_must_fail grep "Signed-off-by:" initial_msg &&
+       grep "Signed-off-by:" unrelatedpick_msg &&
+       test_must_fail grep "Signed-off-by:" picked_msg &&
+       grep "Signed-off-by:" anotherpick_msg
+'
+
+test_expect_success '--signoff dropped for implicit commit of resolution, multi-pick case' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -s picked anotherpick &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+
+       git diff --exit-code HEAD &&
+       test_cmp_rev initial HEAD^^ &&
+       git cat-file commit HEAD^ >msg &&
+       ! grep Signed-off-by: msg
+'
+
+test_expect_success 'sign-off needs to be reaffirmed after conflict resolution, single-pick case' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -s picked &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+
+       git diff --exit-code HEAD &&
+       test_cmp_rev initial HEAD^ &&
+       git cat-file commit HEAD >msg &&
+       ! grep Signed-off-by: msg
+'
+
+test_expect_success 'malformed instruction sheet 1' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick base..anotherpick &&
+       echo "resolved" >foo &&
+       git add foo &&
+       git commit &&
+       sed "s/pick /pick/" .git/sequencer/todo >new_sheet &&
+       cp new_sheet .git/sequencer/todo &&
+       test_expect_code 128 git cherry-pick --continue
+'
+
+test_expect_success 'malformed instruction sheet 2' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick base..anotherpick &&
+       echo "resolved" >foo &&
+       git add foo &&
+       git commit &&
+       sed "s/pick/revert/" .git/sequencer/todo >new_sheet &&
+       cp new_sheet .git/sequencer/todo &&
+       test_expect_code 128 git cherry-pick --continue
+'
+
+test_expect_success 'empty commit set' '
+       pristine_detach initial &&
+       test_expect_code 128 git cherry-pick base..base
+'
+
+test_expect_success 'malformed instruction sheet 3' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick base..anotherpick &&
+       echo "resolved" >foo &&
+       git add foo &&
+       git commit &&
+       sed "s/pick \([0-9a-f]*\)/pick $_r10/" .git/sequencer/todo >new_sheet &&
+       cp new_sheet .git/sequencer/todo &&
+       test_expect_code 128 git cherry-pick --continue
+'
+
+test_expect_success 'instruction sheet, fat-fingers version' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick base..anotherpick &&
+       echo "c" >foo &&
+       git add foo &&
+       git commit &&
+       sed "s/pick \([0-9a-f]*\)/pick   \1     /" .git/sequencer/todo >new_sheet &&
+       cp new_sheet .git/sequencer/todo &&
+       git cherry-pick --continue
+'
+
+test_expect_success 'commit descriptions in insn sheet are optional' '
+       pristine_detach initial &&
+       test_expect_code 1 git cherry-pick base..anotherpick &&
+       echo "c" >foo &&
+       git add foo &&
+       git commit &&
+       cut -d" " -f1,2 .git/sequencer/todo >new_sheet &&
+       cp new_sheet .git/sequencer/todo &&
+       git cherry-pick --continue &&
+       test_path_is_missing .git/sequencer &&
+       git rev-list HEAD >commits &&
+       test_line_count = 4 commits
+'
+
+test_done
index 575d9508a09911f9d95dad2786fb2f1494abc093..874b3a6444ac64182eecf2b6aca4411fc90097e2 100755 (executable)
@@ -179,6 +179,21 @@ test_expect_success 'git add --refresh' '
        test -z "`git diff-index HEAD -- foo`"
 '
 
+test_expect_success 'git add --refresh with pathspec' '
+       git reset --hard &&
+       echo >foo && echo >bar && echo >baz &&
+       git add foo bar baz && H=$(git rev-parse :foo) && git rm -f foo &&
+       echo "100644 $H 3       foo" | git update-index --index-info &&
+       test-chmtime -60 bar baz &&
+       >expect &&
+       git add --refresh bar >actual &&
+       test_cmp expect actual &&
+
+       git diff-files --name-only >actual &&
+       ! grep bar actual&&
+       grep baz actual
+'
+
 test_expect_success POSIXPERM,SANITY 'git add should fail atomically upon an unreadable file' '
        git reset --hard &&
        date >foo1 &&
index 1f62c151b0aa63b4c85f9bc76f501d53967b0260..d48a7c002d622ffac5087be9a7998f781a242731 100755 (executable)
@@ -34,6 +34,12 @@ test_expect_success 'no encoding header for base case' '
        test z = "z$E"
 '
 
+test_expect_failure 'UTF-16 refused because of NULs' '
+       echo UTF-16 >F &&
+       git commit -a -F "$TEST_DIRECTORY"/t3900/UTF-16.txt
+'
+
+
 for H in ISO8859-1 eucJP ISO-2022-JP
 do
        test_expect_success "$H setup" '
index fcdb18217a777f5dbb77b0071af7159c6985656d..663c60a12e82c96065e60fd448a6583c91e5a2cd 100755 (executable)
@@ -444,7 +444,7 @@ test_expect_success 'stash show - stashes on stack, stash-like argument' '
        git reset --hard &&
        cat >expected <<-EOF &&
         file |    1 +
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        EOF
        git stash show ${STASH_ID} >actual &&
        test_cmp expected actual
@@ -482,7 +482,7 @@ test_expect_success 'stash show - no stashes on stack, stash-like argument' '
        git reset --hard &&
        cat >expected <<-EOF &&
         file |    1 +
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        EOF
        git stash show ${STASH_ID} >actual &&
        test_cmp expected actual
@@ -601,4 +601,28 @@ test_expect_success 'stash apply shows status same as git status (relative to cu
        test_cmp expect actual
 '
 
+cat > expect << EOF
+diff --git a/HEAD b/HEAD
+new file mode 100644
+index 0000000..fe0cbee
+--- /dev/null
++++ b/HEAD
+@@ -0,0 +1 @@
++file-not-a-ref
+EOF
+
+test_expect_success 'stash where working directory contains "HEAD" file' '
+       git stash clear &&
+       git reset --hard &&
+       echo file-not-a-ref > HEAD &&
+       git add HEAD &&
+       test_tick &&
+       git stash &&
+       git diff-files --quiet &&
+       git diff-index --cached --quiet HEAD &&
+       test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" &&
+       git diff stash^..stash > output &&
+       test_cmp output expect
+'
+
 test_done
index 781fd716815d90956575b042d72601e67834231c..70655c184886e35046f05e09ee093df48b59daa6 100755 (executable)
@@ -7,7 +7,8 @@ test_expect_success PERL 'setup' '
        mkdir dir &&
        echo parent > dir/foo &&
        echo dummy > bar &&
-       git add bar dir/foo &&
+       echo committed > HEAD &&
+       git add bar dir/foo HEAD &&
        git commit -m initial &&
        test_tick &&
        test_commit second dir/foo head &&
@@ -17,47 +18,57 @@ test_expect_success PERL 'setup' '
        save_head
 '
 
-# note: bar sorts before dir, so the first 'n' is always to skip 'bar'
+# note: order of files with unstaged changes: HEAD bar dir/foo
 
 test_expect_success PERL 'saying "n" does nothing' '
+       set_state HEAD HEADfile_work HEADfile_index &&
        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
+       (echo n; echo n; echo n) | test_must_fail git stash save -p &&
+       verify_state HEAD HEADfile_work HEADfile_index &&
+       verify_saved_state bar &&
+       verify_state dir/foo work index
 '
 
 test_expect_success PERL 'git stash -p' '
-       (echo n; echo y) | git stash save -p &&
-       verify_state dir/foo head index &&
+       (echo y; echo n; echo y) | git stash save -p &&
+       verify_state HEAD committed HEADfile_index &&
        verify_saved_state bar &&
+       verify_state dir/foo head index &&
        git reset --hard &&
        git stash apply &&
-       verify_state dir/foo work head &&
-       verify_state bar dummy dummy
+       verify_state HEAD HEADfile_work committed &&
+       verify_state bar dummy dummy &&
+       verify_state dir/foo work head
 '
 
 test_expect_success PERL 'git stash -p --no-keep-index' '
-       set_state dir/foo work index &&
+       set_state HEAD HEADfile_work HEADfile_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 &&
+       set_state dir/foo work index &&
+       (echo y; echo n; echo y) | git stash save -p --no-keep-index &&
+       verify_state HEAD committed committed &&
        verify_state bar bar_work dummy &&
+       verify_state dir/foo head head &&
        git reset --hard &&
        git stash apply --index &&
-       verify_state dir/foo work index &&
-       verify_state bar dummy bar_index
+       verify_state HEAD HEADfile_work HEADfile_index &&
+       verify_state bar dummy bar_index &&
+       verify_state dir/foo work index
 '
 
 test_expect_success PERL 'git stash --no-keep-index -p' '
-       set_state dir/foo work index &&
+       set_state HEAD HEADfile_work HEADfile_index &&
        set_state bar bar_work bar_index &&
-       (echo n; echo y) | git stash save --no-keep-index -p &&
+       set_state dir/foo work index &&
+       (echo y; echo n; echo y) | git stash save --no-keep-index -p &&
+       verify_state HEAD committed committed &&
        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
+       verify_state HEAD HEADfile_work HEADfile_index &&
+       verify_state bar dummy bar_index &&
+       verify_state dir/foo work index
 '
 
 test_expect_success PERL 'none of this moved HEAD' '
index 4f2eedfd4f87bdf7b6ba8035b203fb9d7bbb2dbe..a5e7e6b2ba3138d06c400e965decf1389da3274a 100755 (executable)
@@ -17,22 +17,32 @@ test_expect_success 'stash save --include-untracked some dirty working directory
        echo 3 > file &&
        test_tick &&
        echo 1 > file2 &&
+       echo 1 > HEAD &&
+       mkdir untracked &&
+       echo untracked >untracked/untracked &&
        git stash --include-untracked &&
        git diff-files --quiet &&
        git diff-index --cached --quiet HEAD
 '
 
 cat > expect <<EOF
+?? actual
 ?? expect
-?? output
 EOF
 
 test_expect_success 'stash save --include-untracked cleaned the untracked files' '
-       git status --porcelain > output
-       test_cmp output expect
+       git status --porcelain >actual &&
+       test_cmp expect actual
 '
 
 cat > expect.diff <<EOF
+diff --git a/HEAD b/HEAD
+new file mode 100644
+index 0000000..d00491f
+--- /dev/null
++++ b/HEAD
+@@ -0,0 +1 @@
++1
 diff --git a/file2 b/file2
 new file mode 100644
 index 0000000..d00491f
@@ -40,17 +50,28 @@ index 0000000..d00491f
 +++ b/file2
 @@ -0,0 +1 @@
 +1
+diff --git a/untracked/untracked b/untracked/untracked
+new file mode 100644
+index 0000000..5a72eb2
+--- /dev/null
++++ b/untracked/untracked
+@@ -0,0 +1 @@
++untracked
 EOF
 cat > expect.lstree <<EOF
+HEAD
 file2
+untracked
 EOF
 
 test_expect_success 'stash save --include-untracked stashed the untracked files' '
-       test "!" -f file2 &&
-       git diff HEAD..stash^3 -- file2 > output &&
-       test_cmp output expect.diff &&
-       git ls-tree --name-only stash^3: > output &&
-       test_cmp output expect.lstree
+       test_path_is_missing file2 &&
+       test_path_is_missing untracked &&
+       test_path_is_missing HEAD &&
+       git diff HEAD stash^3 -- HEAD file2 untracked >actual &&
+       test_cmp expect.diff actual &&
+       git ls-tree --name-only stash^3: >actual &&
+       test_cmp expect.lstree actual
 '
 test_expect_success 'stash save --patch --include-untracked fails' '
        test_must_fail git stash --patch --include-untracked
@@ -64,18 +85,22 @@ git clean --force --quiet
 
 cat > expect <<EOF
  M file
+?? HEAD
+?? actual
 ?? expect
 ?? file2
-?? output
+?? untracked/
 EOF
 
 test_expect_success 'stash pop after save --include-untracked leaves files untracked again' '
        git stash pop &&
-       git status --porcelain > output
-       test_cmp output expect
+       git status --porcelain >actual &&
+       test_cmp expect actual &&
+       test "1" = "`cat file2`" &&
+       test untracked = "`cat untracked/untracked`"
 '
 
-git clean --force --quiet
+git clean --force --quiet -d
 
 test_expect_success 'stash save -u dirty index' '
        echo 4 > file3 &&
@@ -96,16 +121,18 @@ EOF
 
 test_expect_success 'stash save --include-untracked dirty index got stashed' '
        git stash pop --index &&
-       git diff --cached > output &&
-       test_cmp output expect
+       git diff --cached >actual &&
+       test_cmp expect actual
 '
 
 git reset > /dev/null
 
+# Must direct output somewhere where it won't be considered an untracked file
 test_expect_success 'stash save --include-untracked -q is quiet' '
        echo 1 > file5 &&
-       git stash save --include-untracked --quiet > output.out 2>&1 &&
-       test ! -s output.out
+       git stash save --include-untracked --quiet > .git/stash-output.out 2>&1 &&
+       test_line_count = 0 .git/stash-output.out &&
+       rm -f .git/stash-output.out
 '
 
 test_expect_success 'stash save --include-untracked removed files' '
@@ -119,36 +146,42 @@ rm -f expect
 
 test_expect_success 'stash save --include-untracked removed files got stashed' '
        git stash pop &&
-       test ! -f file
+       test_path_is_missing file
 '
 
 cat > .gitignore <<EOF
 .gitignore
 ignored
+ignored.d/
 EOF
 
 test_expect_success 'stash save --include-untracked respects .gitignore' '
        echo ignored > ignored &&
+       mkdir ignored.d &&
+       echo ignored >ignored.d/untracked &&
        git stash -u &&
        test -s ignored &&
+       test -s ignored.d/untracked &&
        test -s .gitignore
 '
 
 test_expect_success 'stash save -u can stash with only untracked files different' '
        echo 4 > file4 &&
-       git stash -u
-       test "!" -f file4
+       git stash -u &&
+       test_path_is_missing file4
 '
 
 test_expect_success 'stash save --all does not respect .gitignore' '
        git stash -a &&
-       test "!" -f ignored &&
-       test "!" -f .gitignore
+       test_path_is_missing ignored &&
+       test_path_is_missing ignored.d &&
+       test_path_is_missing .gitignore
 '
 
 test_expect_success 'stash save --all is stash poppable' '
        git stash pop &&
        test -s ignored &&
+       test -s ignored.d/untracked &&
        test -s .gitignore
 '
 
index 408a19c4c258da9b7860edffea314b181e273edf..f0d5041c11581ec2d711c0ea3bfd479019814b78 100755 (executable)
@@ -9,85 +9,110 @@ test_description='Test diff of symlinks.
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/diff-lib.sh
 
-cat > expected << EOF
-diff --git a/frotz b/frotz
-new file mode 120000
-index 0000000..7c465af
---- /dev/null
-+++ b/frotz
-@@ -0,0 +1 @@
-+xyzzy
-\ No newline at end of file
-EOF
-
-test_expect_success SYMLINKS \
-    'diff new symlink' \
-    'ln -s xyzzy frotz &&
-    git update-index &&
-    tree=$(git write-tree) &&
-    git update-index --add frotz &&
-    GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree > current &&
-    compare_diff_patch current expected'
-
-test_expect_success SYMLINKS \
-    'diff unchanged symlink' \
-    'tree=$(git write-tree) &&
-    git update-index frotz &&
-    test -z "$(git diff-index --name-only $tree)"'
-
-cat > expected << EOF
-diff --git a/frotz b/frotz
-deleted file mode 120000
-index 7c465af..0000000
---- a/frotz
-+++ /dev/null
-@@ -1 +0,0 @@
--xyzzy
-\ No newline at end of file
-EOF
+test_expect_success SYMLINKS 'diff new symlink and file' '
+       cat >expected <<-\EOF &&
+       diff --git a/frotz b/frotz
+       new file mode 120000
+       index 0000000..7c465af
+       --- /dev/null
+       +++ b/frotz
+       @@ -0,0 +1 @@
+       +xyzzy
+       \ No newline at end of file
+       diff --git a/nitfol b/nitfol
+       new file mode 100644
+       index 0000000..7c465af
+       --- /dev/null
+       +++ b/nitfol
+       @@ -0,0 +1 @@
+       +xyzzy
+       EOF
+       ln -s xyzzy frotz &&
+       echo xyzzy >nitfol &&
+       git update-index &&
+       tree=$(git write-tree) &&
+       git update-index --add frotz nitfol &&
+       GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current &&
+       compare_diff_patch expected current
+'
 
-test_expect_success SYMLINKS \
-    'diff removed symlink' \
-    'mv frotz frotz2 &&
-    git diff-index -M -p $tree > current &&
-    compare_diff_patch current expected'
+test_expect_success SYMLINKS 'diff unchanged symlink and file'  '
+       tree=$(git write-tree) &&
+       git update-index frotz nitfol &&
+       test -z "$(git diff-index --name-only $tree)"
+'
 
-cat > expected << EOF
-diff --git a/frotz b/frotz
-EOF
+test_expect_success SYMLINKS 'diff removed symlink and file' '
+       cat >expected <<-\EOF &&
+       diff --git a/frotz b/frotz
+       deleted file mode 120000
+       index 7c465af..0000000
+       --- a/frotz
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -xyzzy
+       \ No newline at end of file
+       diff --git a/nitfol b/nitfol
+       deleted file mode 100644
+       index 7c465af..0000000
+       --- a/nitfol
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -xyzzy
+       EOF
+       mv frotz frotz2 &&
+       mv nitfol nitfol2 &&
+       git diff-index -M -p $tree >current &&
+       compare_diff_patch expected current
+'
 
-test_expect_success SYMLINKS \
-    'diff identical, but newly created symlink' \
-    'ln -s xyzzy frotz &&
-    git diff-index -M -p $tree > current &&
-    compare_diff_patch current expected'
+test_expect_success SYMLINKS 'diff identical, but newly created symlink and file' '
+       >expected &&
+       rm -f frotz nitfol &&
+       echo xyzzy >nitfol &&
+       test-chmtime +10 nitfol &&
+       ln -s xyzzy frotz &&
+       git diff-index -M -p $tree >current &&
+       compare_diff_patch expected current &&
 
-cat > expected << EOF
-diff --git a/frotz b/frotz
-index 7c465af..df1db54 120000
---- a/frotz
-+++ b/frotz
-@@ -1 +1 @@
--xyzzy
-\ No newline at end of file
-+yxyyz
-\ No newline at end of file
-EOF
+       >expected &&
+       git diff-index -M -p -w $tree >current &&
+       compare_diff_patch expected current
+'
 
-test_expect_success SYMLINKS \
-    'diff different symlink' \
-    'rm frotz &&
-    ln -s yxyyz frotz &&
-    git diff-index -M -p $tree > current &&
-    compare_diff_patch current expected'
+test_expect_success SYMLINKS 'diff different symlink and file' '
+       cat >expected <<-\EOF &&
+       diff --git a/frotz b/frotz
+       index 7c465af..df1db54 120000
+       --- a/frotz
+       +++ b/frotz
+       @@ -1 +1 @@
+       -xyzzy
+       \ No newline at end of file
+       +yxyyz
+       \ No newline at end of file
+       diff --git a/nitfol b/nitfol
+       index 7c465af..df1db54 100644
+       --- a/nitfol
+       +++ b/nitfol
+       @@ -1 +1 @@
+       -xyzzy
+       +yxyyz
+       EOF
+       rm -f frotz &&
+       ln -s yxyyz frotz &&
+       echo yxyyz >nitfol &&
+       git diff-index -M -p $tree >current &&
+       compare_diff_patch expected current
+'
 
-test_expect_success SYMLINKS \
-    'diff symlinks with non-existing targets' \
-    'ln -s narf pinky &&
-    ln -s take\ over brain &&
-    test_must_fail git diff --no-index pinky brain > output 2> output.err &&
-    grep narf output &&
-    ! grep error output.err'
+test_expect_success SYMLINKS 'diff symlinks with non-existing targets' '
+       ln -s narf pinky &&
+       ln -s take\ over brain &&
+       test_must_fail git diff --no-index pinky brain >output 2>output.err &&
+       grep narf output &&
+       ! test -s output.err
+'
 
 test_expect_success SYMLINKS 'setup symlinks with attributes' '
        echo "*.bin diff=bin" >>.gitattributes &&
@@ -96,19 +121,19 @@ test_expect_success SYMLINKS 'setup symlinks with attributes' '
        git add -N file.bin link.bin
 '
 
-cat >expect <<'EOF'
-diff --git a/file.bin b/file.bin
-index e69de29..d95f3ad 100644
-Binary files a/file.bin and b/file.bin differ
-diff --git a/link.bin b/link.bin
-index e69de29..dce41ec 120000
---- a/link.bin
-+++ b/link.bin
-@@ -0,0 +1 @@
-+file.bin
-\ No newline at end of file
-EOF
 test_expect_success SYMLINKS 'symlinks do not respect userdiff config by path' '
+       cat >expect <<-\EOF &&
+       diff --git a/file.bin b/file.bin
+       index e69de29..d95f3ad 100644
+       Binary files a/file.bin and b/file.bin differ
+       diff --git a/link.bin b/link.bin
+       index e69de29..dce41ec 120000
+       --- a/link.bin
+       +++ b/link.bin
+       @@ -0,0 +1 @@
+       +file.bin
+       \ No newline at end of file
+       EOF
        git config diff.bin.binary true &&
        git diff file.bin link.bin >actual &&
        test_cmp expect actual
index 3a9f78a09df1bb12aefbb2b5fb75fd0b9455ed06..2f8560c369c01e72b8ec11d43e04df0a0e9ce60c 100644 (file)
@@ -2,7 +2,7 @@ $ git diff-tree --cc --patch-with-stat --summary master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --cc dir/sub
 index cead32e,7289e35..992913c
index a61ad8cb130093ed63bba5335a0b283f9136177e..72e03c14fb4edb2812202da8acd837b064131510 100644 (file)
@@ -3,7 +3,7 @@ c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
index 49f23b9215c3179d8923915ee2e497ccbc53875a..8b357d964b4773c3078e2c32bd3e765f5749841c 100644 (file)
@@ -2,7 +2,7 @@ $ git diff-tree --cc --patch-with-stat master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --cc dir/sub
 index cead32e,7289e35..992913c
index cc6eb3b3d50f2d620df7116bf64992a4dd42b736..e0568d688311059ad04558f50bb3ddacd21fd403 100644 (file)
@@ -2,5 +2,5 @@ $ git diff-tree --cc --stat --summary master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 $
index 50362be7bf851759885ad17847f5913e5dfb0b3c..5afc8239a11f7f7fc20a3fabfad5213eb49fd153 100644 (file)
@@ -3,6 +3,6 @@ c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 $
index fae7f33255faef186aa7b987c29adfe33d0365f1..f48367a89a64945875a81024e444b35771800855 100644 (file)
@@ -2,5 +2,5 @@ $ git diff-tree --cc --stat master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 $
index d5c333a378c9407abf179d6cc3f5233ca8b5f116..590864c2d798472a3fed9fd6bf1fa41545fd572d 100644 (file)
@@ -3,7 +3,7 @@ $ git diff-tree --pretty=oneline --root --patch-with-stat initial
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 new file mode 100644
index 4d30e7eddc05aa561b43519b031f89de8a30fd98..e05e77875ccdaa8a258310f85f5753ae84b4c6c4 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
index 7dfa6af3c97b1ad469135bb9a9f3c1f8d6d4b60d..0e2c956633d28897e7bbddb58ad1cc053e55a8a6 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 new file mode 100644
index 43bfce253eaf451e2f0a19042c9cbb8879c6fef1..384fa44ddd1ab9dbf0d38caf1f445fed1a0b5030 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index 9154aa4d4700b4a7c1e4c205f43694c198419ece..10384a83d31da363cec05f2fecb9def43ae158d7 100644 (file)
@@ -8,5 +8,5 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
 $
index 1562b627085ff0ad8ffaea61cc42ed39a9d94fc8..f57062ea07ac63aa7c700e470aa43d1ede20e09b 100644 (file)
@@ -3,7 +3,7 @@ $ git diff-tree --root --patch-with-stat initial
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 new file mode 100644
index ac9f641fb48272f25f3385dfbbd05777564f16bc..7088683444e54a026e201849ef5d635fab66fc3b 100644 (file)
@@ -2,5 +2,5 @@ $ git diff-tree -c --stat --summary master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 $
index 2afcca11f4d0c4db6e259e85540e4d90f8c76a10..ef216abb1dcaa91d1aa521599b25faf16f704124 100644 (file)
@@ -3,6 +3,6 @@ c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 $
index c2fe6a98c5f9818f9caf5e0a314aee45831e5329..ad19f103eb3323d1c84719cec13a09885e3b47f4 100644 (file)
@@ -2,5 +2,5 @@ $ git diff-tree -c --stat master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 $
index 9ed317a198c71fcec0b44a7b736b61550eed9394..ddad917ae80a5c80b2c7b7f65a85a19404b59945 100644 (file)
@@ -2,7 +2,7 @@ $ git diff --patch-with-stat -r initial..side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
index 8b50629e668a385179eb9586b472c51237492232..bdbd114d8eaf5dd6f40502300c479839a61f0d6b 100644 (file)
@@ -2,7 +2,7 @@ $ git diff --patch-with-stat initial..side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
index 0517b5d63129f44275b16f51819d9c08bf37d430..6d08f3d3550413a23c08e264b4707dafeba21acf 100644 (file)
@@ -2,5 +2,5 @@ $ git diff --stat initial..side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 $
index 245220d3f9b475b4281395639d85122feaa7948d..2ddb2540e640584532dd7b22d1618b35b322a20d 100644 (file)
@@ -2,5 +2,5 @@ $ git diff -r --stat initial..side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 $
index 52116d3eadc3bc0eed1428d76fefe44c26d75780..3cab049f7d98bfe9988c9f19fe477d0d29c8c2d5 100644 (file)
@@ -15,7 +15,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index ce49bd676e1a59eb015efc77e9963caa6a57a419..564a4d38f229946ea08e9c0110a5ad6a20acce0b 100644 (file)
@@ -75,7 +75,7 @@ Content-Transfer-Encoding: 8bit
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 
@@ -124,7 +124,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index 5f1b23863bd286a946dda09a8a7f3beb022b1f66..4f28460b83dd47a1446fc5c110025816611e2a54 100644 (file)
@@ -75,7 +75,7 @@ Content-Transfer-Encoding: 8bit
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 
index 4a2364abc2263e0e8925053acdd7c3c98282df2e..b10cc2e251cf7e38592ae26279b9613758a47b35 100644 (file)
@@ -15,7 +15,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index 43b81eba5440a1f365d0d5ee81ea263c2b81d686..a976a8aaf42b2fa03fd35d215de9cafee99b946c 100644 (file)
@@ -75,7 +75,7 @@ Content-Transfer-Encoding: 8bit
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 
@@ -124,7 +124,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index ca3f60bf0ed3858903fc6c86b99309211c340d13..b4fd66477aa251c56da1e532d83b7d56ec79d25b 100644 (file)
@@ -75,7 +75,7 @@ Content-Transfer-Encoding: 8bit
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 
@@ -124,7 +124,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index 08f23014bc15792946160c4ef45fdcfeba7e933d..0d31036e7f573d70f0ec3c00b67b61291a663671 100644 (file)
@@ -75,7 +75,7 @@ Content-Transfer-Encoding: 8bit
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 
@@ -124,7 +124,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index 07f1230d31f4cdd9f712bd2a69fea035a6998eb2..18d47144231ab36e6f020071b1b8599dd2e76ace 100644 (file)
@@ -75,7 +75,7 @@ Content-Transfer-Encoding: 8bit
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 
index 67633d424a466507015c5a02e721be9b8e6fdfe4..3572f20b5d4a1834d1c60ca05eca11efd8b46375 100644 (file)
@@ -15,7 +15,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index 3b4e113012568dc936bd230fe33c251ab8bff3dd..54cdcdab40f321559038c954fab572e532801b8b 100644 (file)
@@ -75,7 +75,7 @@ Subject: [DIFFERENT_PREFIX 2/2] Third
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
index f7752ebbea7c735faf38fe6f3c2c963f4a4b0f40..23194ebdaa8defaca544a0f6d18d449a44fcd5f1 100644 (file)
@@ -53,7 +53,7 @@ Subject: [PATCH] Third
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -88,7 +88,7 @@ Subject: [PATCH] Side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
index 8e67dbf76fa508ae498e17834f963506b4740438..78f1a80a976ac54cc53602f203cae96f12be65dc 100644 (file)
@@ -53,7 +53,7 @@ Subject: [PATCH 2/3] Third
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -88,7 +88,7 @@ Subject: [PATCH 3/3] Side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
index 7b89978e321bc20c55a7016f7111275bb553b1f0..a3dab7f773d71da1f1b1ade10b24cf9562ff7d1b 100644 (file)
@@ -53,7 +53,7 @@ Subject: [PATCH 2/3] Third
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -88,7 +88,7 @@ Subject: [PATCH 3/3] Side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
index b7f9725dc4158d4cc6131203d3a6ee1b7fd8c456..39f4a3f2d1f50ce49792f4fcc6373d003ebf971b 100644 (file)
@@ -53,7 +53,7 @@ Subject: [PATCH 2/2] Third
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
index e7650884755b993661ef14fbc812edd7c2710702..88109209db005188c9e70fdd40b906756119dd69 100644 (file)
@@ -8,7 +8,7 @@ Subject: [PATCH] Side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
index bd7f5c0f70571d0aa0f9b9c50a343a322831976f..4085bbde870d0810c69e7d78ef94052df385826c 100644 (file)
@@ -13,7 +13,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
     Side
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -32,7 +32,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
     Third
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -54,7 +54,7 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     This is the second commit.
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
index 14595a614c362da4515f8d5b783bb0e0f079c72b..458627953e29c80b8bed0e640db1c580946a0194 100644 (file)
@@ -15,7 +15,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -56,7 +56,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
index 5a4e72765d316b855a8c2beee8f417cd050de323..6e172cfadd04785269e0060fa55e86b3df117195 100644 (file)
@@ -13,7 +13,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
     Side
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -32,7 +32,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
     Third
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -54,7 +54,7 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     This is the second commit.
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
index df0aaa9f2ca780ce62951a5667ed9fe3aa005e41..48b0d4b91dfa427c1f0971bedbad61931e103bb7 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:04:00 2006 +0000
 
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --cc dir/sub
 index cead32e,7289e35..992913c
@@ -47,7 +47,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
@@ -89,7 +89,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -165,7 +165,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index c11b5f2c7f3e6846643b7d7e0e214993f4a2cf0d..f9dc5122e2a436a62fa80bdf820b4d44cacdfe8e 100644 (file)
@@ -15,7 +15,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
@@ -57,7 +57,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -133,7 +133,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index 5f0c98f9ce3d9d067cfcb662084b975a19c90f03..0807ece2347ac54f87c860904ef257c766da5dd0 100644 (file)
@@ -15,7 +15,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -56,7 +56,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -130,7 +130,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 new file mode 100644
index e62c368dc64dae87c6c168462b175712f197e7af..84f5ef69119edf44e967fa09e3c6acd0bdc6b715 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:04:00 2006 +0000
 
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --combined dir/sub
 index cead32e,7289e35..992913c
@@ -47,7 +47,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
@@ -89,7 +89,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -165,7 +165,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index 377f2b7b7a34a1e83bbcf0f2d72981685a678c24..e60384d24de317e9f9d6dac222d8123ae4d68e02 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
index fb14c530d2dd79730320badff2547ebf755bd350..a3a3255fd3f508ddd47a92cb47552dde3ab8156a 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
index 5bd597762869443cfd91ba8ebdaeff6e8b515ebf..d16f464aca8f83264f53f0355bb3be84eddf1bd8 100644 (file)
@@ -8,6 +8,6 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 $
index 3b22327e48122187af12db46027f17526c8e9caf..6300c0535fff7e4a82fdfb6511392b006f5dfa0c 100644 (file)
@@ -8,5 +8,5 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 $
index 6a467cccc190449049b7baed2dde0a2f9027e663..16ae54345f6a1e93fae3ad7269829b32eaab3739 100644 (file)
@@ -6,7 +6,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
     Side
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -25,7 +25,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
     Third
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -47,7 +47,7 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     This is the second commit.
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
index 1e1bbe19638eeeffb4e656239545ea91fda3dbf2..f3e45ec270ace186d58f40a21a9763598d6dc0ac 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -49,7 +49,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
index 13789f169b6803ba71680470197c50b51795e4e4..c77f0bc320f0241550e9027f7ecb191d77f85b43 100644 (file)
@@ -6,7 +6,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
     Side
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -25,7 +25,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
     Third
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -47,7 +47,7 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     This is the second commit.
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
index e96ff1fb8c11ff8c6774cb249f45744ed05a97c3..8d03efea6ce33bb52018eeedc02629c6bd9253aa 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:04:00 2006 +0000
 
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --cc dir/sub
 index cead32e,7289e35..992913c
@@ -47,7 +47,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
@@ -89,7 +89,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -165,7 +165,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index 02911535870b012720c9ae095499c61e6e242c7e..1874d0616c4d6cf308a6fcc0f59a0d552664777f 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
@@ -50,7 +50,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -126,7 +126,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index 9b0349cd555eb17836b8c056d7aae8cf1eabe537..5211ff2a757b8db234ec1f4b14ae33300c21296a 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -49,7 +49,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -123,7 +123,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 new file mode 100644
index c0aff68ef68c2d956492e7f9817b9bf5afe58005..ad30245a59b94ed18bf9e1bfca3eb0077cd3a14a 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:04:00 2006 +0000
 
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --combined dir/sub
 index cead32e,7289e35..992913c
@@ -47,7 +47,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
@@ -89,7 +89,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -165,7 +165,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index 67975129bc3703e16fe52eb916c2e08b7908ff87..7dfe716cf9ed63f08f512cfa123d9bf93fa92839 100755 (executable)
@@ -520,7 +520,7 @@ test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
 cat > expect << EOF
 ---
  file |   16 ++++++++++++++++
- 1 files changed, 16 insertions(+), 0 deletions(-)
+ 1 file changed, 16 insertions(+)
 
 diff --git a/file b/file
 index 40f36c6..2dc5c23 100644
index b68c56b68c0f80a0d96b513733bc5fb17db6fed4..4bd2a1c838e9a522c4376860e726455d3cdb92b0 100755 (executable)
@@ -105,7 +105,7 @@ test_expect_funcname () {
        grep "^@@.*@@ $1" diff
 }
 
-for p in bibtex cpp csharp fortran html java objc pascal perl php python ruby tex
+for p in bibtex cpp csharp fortran html java matlab objc pascal perl php python ruby tex
 do
        test_expect_success "builtin $p pattern compiles" '
                echo "*.java diff=$p" >.gitattributes &&
index 88c5619ae7471ab0d3286259d88c437ae3953b4a..4ac162cfcf891e5fe0a36a3074db0d486ffa5cf0 100755 (executable)
@@ -86,7 +86,7 @@ test_expect_success 'status -v produces text' '
 
 cat >expect.stat <<'EOF'
  file |  Bin 2 -> 4 bytes
- 1 files changed, 0 insertions(+), 0 deletions(-)
+ 1 file changed, 0 insertions(+), 0 deletions(-)
 EOF
 test_expect_success 'diffstat does not run textconv' '
        echo file diff=fail >.gitattributes &&
index c374aa4c1c60e9a12cf1ebf5587daf3656e4851a..5c2012111c28d338ad979fb7bcca871e744184fe 100755 (executable)
@@ -299,6 +299,7 @@ test_language_driver csharp
 test_language_driver fortran
 test_language_driver html
 test_language_driver java
+test_language_driver matlab
 test_language_driver objc
 test_language_driver pascal
 test_language_driver perl
@@ -333,4 +334,18 @@ test_expect_success 'word-diff with diff.sbe' '
        word_diff --word-diff=plain
 '
 
+test_expect_success 'word-diff with no newline at EOF' '
+       cat >expect <<-\EOF &&
+       diff --git a/pre b/post
+       index 7bf316e..3dd0303 100644
+       --- a/pre
+       +++ b/post
+       @@ -1 +1 @@
+       a a [-a-]{+ab+} a a
+       EOF
+       printf "%s" "a a a a a" >pre &&
+       printf "%s" "a a ab a a" >post &&
+       word_diff --word-diff=plain
+'
+
 test_done
diff --git a/t/t4034/matlab/expect b/t/t4034/matlab/expect
new file mode 100644 (file)
index 0000000..72cf3e9
--- /dev/null
@@ -0,0 +1,14 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index dc204db..70e05f0 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,9 +1,9 @@<RESET>
+(<RED>1<RESET><GREEN>0<RESET>) (<RED>-1e10<RESET><GREEN>-0e10<RESET>) '<RED>b<RESET><GREEN>y<RESET>';
+[<RED>a<RESET><GREEN>x<RESET>] {<RED>a<RESET><GREEN>x<RESET>} <RED>a<RESET><GREEN>x<RESET>.<RED>b<RESET><GREEN>y<RESET>;
+~<RED>a<RESET><GREEN>x<RESET>;
+<RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>.*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>./<RED>b a<RESET><GREEN>y x<RESET>^<RED>b a<RESET><GREEN>y x<RESET>.^<RED>b a<RESET><GREEN>y x<RESET>.\<RED>b a<RESET><GREEN>y x<RESET>.';
+<RED>a<RESET><GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET><GREEN>y<RESET>;
+<RED>a<RESET><GREEN>x<RESET>&<RED>b a<RESET><GREEN>y x<RESET>&&<RED>b a<RESET><GREEN>y x<RESET>|<RED>b a<RESET><GREEN>y x<RESET>||<RED>b<RESET><GREEN>y<RESET>;
+<RED>a<RESET><GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET><GREEN>y<RESET>;
+<RED>a<RESET><GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>~=<RED>b<RESET><GREEN>y<RESET>;
+<RED>a<RESET><GREEN>x<RESET>,<RED>b<RESET><GREEN>y<RESET>;
diff --git a/t/t4034/matlab/post b/t/t4034/matlab/post
new file mode 100644 (file)
index 0000000..70e05f0
--- /dev/null
@@ -0,0 +1,9 @@
+(0) (-0e10) 'y';
+[x] {x} x.y;
+~x;
+x*y x.*y x/y x./y x^y x.^y x.\y x.';
+x+y x-y;
+x&y x&&y x|y x||y;
+x<y x<=y x>y x>=y;
+x==y x~=y;
+x,y;
diff --git a/t/t4034/matlab/pre b/t/t4034/matlab/pre
new file mode 100644 (file)
index 0000000..dc204db
--- /dev/null
@@ -0,0 +1,9 @@
+(1) (-1e10) 'b';
+[a] {a} a.b;
+~a;
+a*b a.*b a/b a./b a^b a.^b a.\b a.';
+a+b a-b;
+a&b a&&b a|b a||b;
+a<b a<=b a>b a>=b;
+a==b a~=b;
+a,b;
index 8a3c63b9e2d8d83b78bd8c9a606b8d774ecf53b3..bd119be106f1a2d6d91f982a1bf8afcbf6b8c831 100755 (executable)
@@ -33,7 +33,7 @@ check_stat() {
 expect=$1; shift
 cat >expected <<EOF
  $expect |    1 +
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ 1 file changed, 1 insertion(+)
 EOF
 test_expect_success "--stat $*" "
        git diff --stat $* HEAD^ >actual &&
index 641e70d14d82b2e3870af058f09d063c99812825..a6d1887536e240e89b8e2263e5f0a643e9a55f71 100755 (executable)
@@ -16,7 +16,7 @@ test_expect_success setup '
        cat >expect <<-\EOF
         a |    1 +
         b |    1 +
-        2 files changed, 2 insertions(+), 0 deletions(-)
+        2 files changed, 2 insertions(+)
        EOF
        git diff --stat --stat-count=2 >actual &&
        test_cmp expect actual
diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh
new file mode 100755 (executable)
index 0000000..001d678
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='diff function context'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
+
+
+cat <<\EOF >hello.c
+#include <stdio.h>
+
+static int a(void)
+{
+       /*
+        * Dummy.
+        */
+}
+
+static int hello_world(void)
+{
+       /* Classic. */
+       printf("Hello world.\n");
+
+       /* Success! */
+       return 0;
+}
+static int b(void)
+{
+       /*
+        * Dummy, too.
+        */
+}
+
+int main(int argc, char **argv)
+{
+       a();
+       b();
+       return hello_world();
+}
+EOF
+
+test_expect_success 'setup' '
+       git add hello.c &&
+       test_tick &&
+       git commit -m initial &&
+
+       grep -v Classic <hello.c >hello.c.new &&
+       mv hello.c.new hello.c
+'
+
+cat <<\EOF >expected
+diff --git a/hello.c b/hello.c
+--- a/hello.c
++++ b/hello.c
+@@ -10,8 +10,7 @@ static int a(void)
+ static int hello_world(void)
+ {
+-      /* Classic. */
+       printf("Hello world.\n");
+       /* Success! */
+       return 0;
+ }
+EOF
+
+test_expect_success 'diff -U0 -W' '
+       git diff -U0 -W >actual &&
+       compare_diff_patch actual expected
+'
+
+cat <<\EOF >expected
+diff --git a/hello.c b/hello.c
+--- a/hello.c
++++ b/hello.c
+@@ -9,9 +9,8 @@ static int a(void)
+ static int hello_world(void)
+ {
+-      /* Classic. */
+       printf("Hello world.\n");
+       /* Success! */
+       return 0;
+ }
+EOF
+
+test_expect_success 'diff -W' '
+       git diff -W >actual &&
+       compare_diff_patch actual expected
+'
+
+test_done
index eef7f2e65cc3e2192935b9bde3ea001244b6db18..55a55c3cc77931fef9c3850992fcbcebc62eec0c 100644 (file)
@@ -1,2 +1,2 @@
  t/t4100-apply-stat.sh |    2 +-
- 1 files changed, 1 insertions(+), 1 deletions(-)
+ 1 file changed, 1 insertion(+), 1 deletion(-)
index eef7f2e65cc3e2192935b9bde3ea001244b6db18..55a55c3cc77931fef9c3850992fcbcebc62eec0c 100644 (file)
@@ -1,2 +1,2 @@
  t/t4100-apply-stat.sh |    2 +-
- 1 files changed, 1 insertions(+), 1 deletions(-)
+ 1 file changed, 1 insertion(+), 1 deletion(-)
index 94373ca9a0c20a4465a8788eef3dd7596f6e6c2d..b1361ce54693a07486f5837b0fe477f828b80a4e 100755 (executable)
@@ -11,7 +11,7 @@ test_expect_success 'setup' '
        test_commit 1 &&
        test_commit 2 &&
        mkdir sub &&
-       test_commit 3 sub/3 &&
+       test_commit 3 sub/3.t &&
        test_commit 4
 '
 
diff --git a/t/t4136-apply-check.sh b/t/t4136-apply-check.sh
new file mode 100755 (executable)
index 0000000..a321f7c
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+test_description='git apply should exit non-zero with unrecognized input.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit 1
+'
+
+test_expect_success 'apply --check exits non-zero with unrecognized input' '
+       test_must_fail git apply --check - <<-\EOF
+       I am not a patch
+       I look nothing like a patch
+       git apply must fail
+       EOF
+'
+
+test_done
index d7d9ccc1c8c31ef3b2d4763532344d0637a18b5c..658354a6ce8ab13fd6b229bb2adecd8e29de9697 100755 (executable)
@@ -123,6 +123,7 @@ test_expect_success setup '
        git commit -m "added another file" &&
 
        git format-patch --stdout master >lorem-move.patch &&
+       git format-patch --no-prefix --stdout master >lorem-zero.patch &&
 
        git checkout -b rename &&
        git mv file renamed &&
@@ -136,7 +137,7 @@ test_expect_success setup '
        git format-patch -M --stdout lorem^ >rename-add.patch &&
 
        # reset time
-       unset test_tick &&
+       sane_unset test_tick &&
        test_tick
 '
 
@@ -276,6 +277,20 @@ test_expect_success 'am -3 falls back to 3-way merge' '
        git diff --exit-code lorem
 '
 
+test_expect_success 'am -3 -p0 can read --no-prefix patch' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout -b lorem3 master2 &&
+       sed -n -e "3,\$p" msg >file &&
+       head -n 9 msg >>file &&
+       git add file &&
+       test_tick &&
+       git commit -m "copied stuff" &&
+       git am -3 -p0 lorem-zero.patch &&
+       ! test -d .git/rebase-apply &&
+       git diff --exit-code lorem
+'
+
 test_expect_success 'am can rename a file' '
        grep "^rename from" rename.patch &&
        rm -fr .git/rebase-apply &&
@@ -495,4 +510,14 @@ test_expect_success 'am -q is quiet' '
        ! test -s output.out
 '
 
+test_expect_success 'am empty-file does not infloop' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       touch empty-file &&
+       test_tick &&
+       { git am empty-file > actual 2>&1 && false || :; } &&
+       echo Patch format detection failed. >expected &&
+       test_cmp expected actual
+'
+
 test_done
index 983e34bec67a4cc4ba1182332e495377cc879ac9..222f7559e92caa2a0bbd128b0bb6bac14e2e113f 100755 (executable)
@@ -346,11 +346,11 @@ test_expect_success 'set up more tangled history' '
 '
 
 cat > expect <<\EOF
-*   Merge commit 'reach'
+*   Merge tag 'reach'
 |\
 | \
 |  \
-*-. \   Merge commit 'octopus-a'; commit 'octopus-b'
+*-. \   Merge tags 'octopus-a' and 'octopus-b'
 |\ \ \
 * | | | seventh
 | | * | octopus-b
index d9068981f8475c37d30e584bc6b795075a2f3063..527c9e7548517d4109a9da077cff804e072e38f1 100755 (executable)
@@ -96,7 +96,7 @@ test_expect_success 'git archive with --output' \
     'git archive --output=b4.tar HEAD &&
     test_cmp b.tar b4.tar'
 
-test_expect_success NOT_MINGW 'git archive --remote' \
+test_expect_success 'git archive --remote' \
     'git archive --remote=. HEAD >b5.tar &&
     test_cmp b.tar b5.tar'
 
@@ -242,6 +242,14 @@ test_expect_success \
     'git archive --list outside of a git repo' \
     'GIT_DIR=some/non-existing/directory git archive --list'
 
+test_expect_success 'clients cannot access unreachable commits' '
+       test_commit unreachable &&
+       sha1=`git rev-parse HEAD` &&
+       git reset --hard HEAD^ &&
+       git archive $sha1 >remote.tar &&
+       test_must_fail git archive --remote=. $sha1 >remote.tar
+'
+
 test_expect_success 'git-archive --prefix=olde-' '
        git archive --prefix=olde- >h.tar HEAD &&
        (
@@ -266,7 +274,7 @@ test_expect_success 'archive --list mentions user filter' '
        grep "^bar\$" output
 '
 
-test_expect_success NOT_MINGW 'archive --list shows only enabled remote filters' '
+test_expect_success 'archive --list shows only enabled remote filters' '
        git archive --list --remote=. >output &&
        ! grep "^tar\.foo\$" output &&
        grep "^bar\$" output
@@ -298,7 +306,7 @@ test_expect_success 'extension matching requires dot' '
        test_cmp b.tar config-implicittar.foo
 '
 
-test_expect_success NOT_MINGW 'only enabled filters are available remotely' '
+test_expect_success 'only enabled filters are available remotely' '
        test_must_fail git archive --remote=. --format=tar.foo HEAD \
                >remote.tar.foo &&
        git archive --remote=. --format=bar >remote.bar HEAD &&
@@ -341,12 +349,12 @@ test_expect_success GZIP,GUNZIP 'extract tgz file' '
        test_cmp b.tar j.tar
 '
 
-test_expect_success GZIP,NOT_MINGW 'remote tar.gz is allowed by default' '
+test_expect_success GZIP 'remote tar.gz is allowed by default' '
        git archive --remote=. --format=tar.gz HEAD >remote.tar.gz &&
        test_cmp j.tgz remote.tar.gz
 '
 
-test_expect_success GZIP,NOT_MINGW 'remote tar.gz can be disabled' '
+test_expect_success GZIP 'remote tar.gz can be disabled' '
        git config tar.tar.gz.remote false &&
        test_must_fail git archive --remote=. --format=tar.gz HEAD \
                >remote.tar.gz
index 02d4d2284d8bcf9637dbd6a670e376b4ae9f7af1..f47d8717fdd93cf8ebf356c2675511567782335e 100755 (executable)
@@ -57,6 +57,15 @@ test_expect_missing  worktree/ignored
 test_expect_exists     worktree/ignored-by-tree
 test_expect_missing    worktree/ignored-by-worktree
 
+test_expect_success 'git archive --worktree-attributes option' '
+       git archive --worktree-attributes --worktree-attributes HEAD >worktree.tar &&
+       (mkdir worktree2 && cd worktree2 && "$TAR" xf -) <worktree.tar
+'
+
+test_expect_missing    worktree2/ignored
+test_expect_exists     worktree2/ignored-by-tree
+test_expect_missing    worktree2/ignored-by-worktree
+
 test_expect_success 'git archive vs. bare' '
        (cd bare && git archive HEAD) >bare-archive.tar &&
        test_cmp archive.tar bare-archive.tar
index 9cc0a42ea977e184be7af02e05352da8155c4728..2af8947eebb3e9ee45f83acb398335ec163a521c 100755 (executable)
@@ -67,9 +67,11 @@ test_expect_success 'setup: two scripts for reading pull requests' '
 
        cat <<-\EOT >read-request.sed &&
        #!/bin/sed -nf
+       # Note that a request could ask for "tag $tagname"
        / in the git repository at:$/!d
        n
        /^$/ n
+       s/ tag \([^ ]*\)$/ tag--\1/
        s/^[    ]*\(.*\) \([^ ]*\)/please pull\
        \1\
        \2/p
@@ -86,13 +88,14 @@ test_expect_success 'setup: two scripts for reading pull requests' '
        s/$downstream_url_for_sed/URL/g
        s/for-upstream/BRANCH/g
        s/mnemonic.txt/FILENAME/g
+       s/^version [0-9]/VERSION/
        /^ FILENAME | *[0-9]* [-+]*\$/ b diffstat
        /^AUTHOR ([0-9]*):\$/ b shortlog
        p
        b
        : diffstat
        n
-       / [0-9]* files changed/ {
+       / [0-9]* files* changed/ {
                a\\
        DIFFSTAT
                b
@@ -176,10 +179,7 @@ test_expect_success 'request names an appropriate branch' '
                read repository &&
                read branch
        } <digest &&
-       {
-               test "$branch" = master ||
-               test "$branch" = for-upstream
-       }
+       test "$branch" = tags/full
 
 '
 
@@ -193,8 +193,17 @@ test_expect_success 'pull request format' '
          SUBJECT (DATE)
 
        are available in the git repository at:
+
          URL BRANCH
 
+       for you to fetch changes up to OBJECT_NAME:
+
+         SUBJECT (DATE)
+
+       ----------------------------------------------------------------
+       VERSION
+
+       ----------------------------------------------------------------
        SHORTLOG
 
        DIFFSTAT
index d05a9138b46eec072750142f912a41caa3e23d83..1753ef2b91e3e5846f12fc16e1209b1895210f3b 100755 (executable)
@@ -31,44 +31,44 @@ EOF
 done
 
 test_expect_success 'post-checkout runs as expected ' '
-        GIT_DIR=clone1/.git git checkout master &&
-        test -e clone1/.git/post-checkout.args
+       GIT_DIR=clone1/.git git checkout master &&
+       test -e clone1/.git/post-checkout.args
 '
 
 test_expect_success 'post-checkout receives the right arguments with HEAD unchanged ' '
-        old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
-        new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
-        flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
-        test $old = $new -a $flag = 1
+       old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
+       new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
+       flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
+       test $old = $new -a $flag = 1
 '
 
 test_expect_success 'post-checkout runs as expected ' '
-        GIT_DIR=clone1/.git git checkout master &&
-        test -e clone1/.git/post-checkout.args
+       GIT_DIR=clone1/.git git checkout master &&
+       test -e clone1/.git/post-checkout.args
 '
 
 test_expect_success 'post-checkout args are correct with git checkout -b ' '
-        GIT_DIR=clone1/.git git checkout -b new1 &&
-        old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
-        new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
-        flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
-        test $old = $new -a $flag = 1
+       GIT_DIR=clone1/.git git checkout -b new1 &&
+       old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
+       new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
+       flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
+       test $old = $new -a $flag = 1
 '
 
 test_expect_success 'post-checkout receives the right args with HEAD changed ' '
-        GIT_DIR=clone2/.git git checkout new2 &&
-        old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
-        new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
-        flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
-        test $old != $new -a $flag = 1
+       GIT_DIR=clone2/.git git checkout new2 &&
+       old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
+       new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
+       flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
+       test $old != $new -a $flag = 1
 '
 
 test_expect_success 'post-checkout receives the right args when not switching branches ' '
-        GIT_DIR=clone2/.git git checkout master b &&
-        old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
-        new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
-        flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
-        test $old = $new -a $flag = 0
+       GIT_DIR=clone2/.git git checkout master b &&
+       old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
+       new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
+       flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
+       test $old = $new -a $flag = 0
 '
 
 if test "$(git config --bool core.filemode)" = true; then
index bafcca765e4fea92f430e7127506a2370e062ec7..9bf69e9a0f0bff632932929b5ba41f767baf3fca 100755 (executable)
@@ -97,7 +97,7 @@ test_expect_success 'setup' '
        git symbolic-ref HEAD refs/heads/B
 '
 
-pull_to_client 1st "A" $((11*3))
+pull_to_client 1st "refs/heads/B refs/heads/A" $((11*3))
 
 test_expect_success 'post 1st pull setup' '
        add A11 $A10 &&
@@ -110,9 +110,9 @@ test_expect_success 'post 1st pull setup' '
        done
 '
 
-pull_to_client 2nd "B" $((64*3))
+pull_to_client 2nd "refs/heads/B" $((64*3))
 
-pull_to_client 3rd "A" $((1*3))
+pull_to_client 3rd "refs/heads/A" $((1*3))
 
 test_expect_success 'clone shallow' '
        git clone --depth 2 "file://$(pwd)/." shallow
index b5ced8483a8fc663eb53b514bbb56df92f217deb..1bc57ac03f9df7e82fab5478b7234ab1435c60e3 100755 (executable)
@@ -28,7 +28,7 @@ test_expect_success setup '
                done
        ) &&
        (
-               git clone --reference=original "file:///$(pwd)/original" one &&
+               git clone --reference=original "file://$(pwd)/original" one &&
                cd one &&
                echo Z >count &&
                git add count &&
diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh
new file mode 100755 (executable)
index 0000000..35ec294
--- /dev/null
@@ -0,0 +1,118 @@
+#!/bin/sh
+
+test_description='fetch/receive strict mode'
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo hello >greetings &&
+       git add greetings &&
+       git commit -m greetings &&
+
+       S=$(git rev-parse :greetings | sed -e "s|^..|&/|") &&
+       X=$(echo bye | git hash-object -w --stdin | sed -e "s|^..|&/|") &&
+       mv -f .git/objects/$X .git/objects/$S &&
+
+       test_must_fail git fsck
+'
+
+test_expect_success 'fetch without strict' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config fetch.fsckobjects false &&
+               git config transfer.fsckobjects false &&
+               test_must_fail git fetch ../.git master
+       )
+'
+
+test_expect_success 'fetch with !fetch.fsckobjects' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config fetch.fsckobjects false &&
+               git config transfer.fsckobjects true &&
+               test_must_fail git fetch ../.git master
+       )
+'
+
+test_expect_success 'fetch with fetch.fsckobjects' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config fetch.fsckobjects true &&
+               git config transfer.fsckobjects false &&
+               test_must_fail git fetch ../.git master
+       )
+'
+
+test_expect_success 'fetch with transfer.fsckobjects' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config transfer.fsckobjects true &&
+               test_must_fail git fetch ../.git master
+       )
+'
+
+cat >exp <<EOF
+To dst
+!      refs/heads/master:refs/heads/test       [remote rejected] (missing necessary objects)
+EOF
+
+test_expect_success 'push without strict' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config fetch.fsckobjects false &&
+               git config transfer.fsckobjects false
+       ) &&
+       test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+       test_cmp exp act
+'
+
+test_expect_success 'push with !receive.fsckobjects' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config receive.fsckobjects false &&
+               git config transfer.fsckobjects true
+       ) &&
+       test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+       test_cmp exp act
+'
+
+cat >exp <<EOF
+To dst
+!      refs/heads/master:refs/heads/test       [remote rejected] (n/a (unpacker error))
+EOF
+
+test_expect_success 'push with receive.fsckobjects' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config receive.fsckobjects true &&
+               git config transfer.fsckobjects false
+       ) &&
+       test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+       test_cmp exp act
+'
+
+test_expect_success 'push with transfer.fsckobjects' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config transfer.fsckobjects true
+       ) &&
+       test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+       test_cmp exp act
+'
+
+test_done
index e0af4c4e62c40a563f692361944cc58e2c8e38e2..308c02ea75f3400b4d215e6091f03179a93943ad 100755 (executable)
@@ -14,6 +14,14 @@ test_bundle_object_count () {
        test "$2" = $(grep '^[0-9a-f]\{40\} ' verify.out | wc -l)
 }
 
+convert_bundle_to_pack () {
+       while read x && test -n "$x"
+       do
+               :;
+       done
+       cat
+}
+
 test_expect_success setup '
        echo >file original &&
        git add file &&
@@ -70,8 +78,8 @@ test_expect_success "fetch test for-merge" '
        master_in_two=`cd ../two && git rev-parse master` &&
        one_in_two=`cd ../two && git rev-parse one` &&
        {
-               echo "$master_in_two    not-for-merge"
                echo "$one_in_two       "
+               echo "$master_in_two    not-for-merge"
        } >expected &&
        cut -f -2 .git/FETCH_HEAD >actual &&
        test_cmp expected actual'
@@ -166,7 +174,7 @@ test_expect_success 'fetch must not resolve short tag name' '
 
 '
 
-test_expect_success 'fetch must not resolve short remote name' '
+test_expect_success 'fetch can now resolve short remote name' '
 
        cd "$D" &&
        git update-ref refs/remotes/six/HEAD HEAD &&
@@ -175,8 +183,7 @@ test_expect_success 'fetch must not resolve short remote name' '
        cd six &&
        git init &&
 
-       test_must_fail git fetch .. six:six
-
+       git fetch .. six:six
 '
 
 test_expect_success 'create bundle 1' '
@@ -207,13 +214,7 @@ test_expect_success 'unbundle 1' '
 
 test_expect_success 'bundle 1 has only 3 files ' '
        cd "$D" &&
-       (
-               while read x && test -n "$x"
-               do
-                       :;
-               done
-               cat
-       ) <bundle1 >bundle.pack &&
+       convert_bundle_to_pack <bundle1 >bundle.pack &&
        git index-pack bundle.pack &&
        test_bundle_object_count bundle.pack 3
 '
@@ -230,13 +231,7 @@ test_expect_success 'bundle does not prerequisite objects' '
        git add file2 &&
        git commit -m add.file2 file2 &&
        git bundle create bundle3 -1 HEAD &&
-       (
-               while read x && test -n "$x"
-               do
-                       :;
-               done
-               cat
-       ) <bundle3 >bundle.pack &&
+       convert_bundle_to_pack <bundle3 >bundle.pack &&
        git index-pack bundle.pack &&
        test_bundle_object_count bundle.pack 3
 '
@@ -434,14 +429,31 @@ test_expect_success 'fetch --dry-run' '
 '
 
 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
+       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_expect_success 'all boundary commits are excluded' '
+       test_commit base &&
+       test_commit oneside &&
+       git checkout HEAD^ &&
+       test_commit otherside &&
+       git checkout master &&
+       test_tick &&
+       git merge otherside &&
+       ad=$(git log --no-walk --format=%ad HEAD) &&
+       git bundle create twoside-boundary.bdl master --since="$ad" &&
+       convert_bundle_to_pack <twoside-boundary.bdl >twoside-boundary.pack &&
+       pack=$(git index-pack --fix-thin --stdin <twoside-boundary.pack) &&
+       test_bundle_object_count .git/objects/pack/pack-${pack##pack    }.pack 3
 '
 
 test_done
index 5c546c99a58854ae0f430e469dad525af52e9292..6764d511ce08cf62da2c257bf5fe50cabaf2f03c 100755 (executable)
@@ -5,7 +5,6 @@ test_description='git ls-remote'
 . ./test-lib.sh
 
 test_expect_success setup '
-
        >file &&
        git add file &&
        test_tick &&
@@ -18,45 +17,33 @@ test_expect_success setup '
        ) >expected.all &&
 
        git remote add self "$(pwd)/.git"
-
 '
 
 test_expect_success 'ls-remote --tags .git' '
-
        git ls-remote --tags .git >actual &&
        test_cmp expected.tag actual
-
 '
 
 test_expect_success 'ls-remote .git' '
-
        git ls-remote .git >actual &&
        test_cmp expected.all actual
-
 '
 
 test_expect_success 'ls-remote --tags self' '
-
        git ls-remote --tags self >actual &&
        test_cmp expected.tag actual
-
 '
 
 test_expect_success 'ls-remote self' '
-
        git ls-remote self >actual &&
        test_cmp expected.all actual
-
 '
 
 test_expect_success 'dies when no remote specified and no default remotes found' '
-
        test_must_fail git ls-remote
-
 '
 
 test_expect_success 'use "origin" when no remote specified' '
-
        URL="$(pwd)/.git" &&
        echo "From $URL" >exp_err &&
 
@@ -65,18 +52,14 @@ test_expect_success 'use "origin" when no remote specified' '
 
        test_cmp exp_err actual_err &&
        test_cmp expected.all actual
-
 '
 
 test_expect_success 'suppress "From <url>" with -q' '
-
        git ls-remote -q 2>actual_err &&
        test_must_fail test_cmp exp_err actual_err
-
 '
 
 test_expect_success 'use branch.<name>.remote if possible' '
-
        #
        # Test that we are indeed using branch.<name>.remote, not "origin", even
        # though the "origin" remote has been set.
@@ -99,14 +82,13 @@ test_expect_success 'use branch.<name>.remote if possible' '
        git ls-remote 2>actual_err >actual &&
        test_cmp exp_err actual_err &&
        test_cmp exp actual
-
 '
 
-cat >exp <<EOF
-fatal: 'refs*master' does not appear to be a git repository
-fatal: The remote end hung up unexpectedly
-EOF
 test_expect_success 'confuses pattern as remote when no remote specified' '
+       cat >exp <<-\EOF &&
+       fatal: '\''refs*master'\'' does not appear to be a git repository
+       fatal: The remote end hung up unexpectedly
+       EOF
        #
        # Do not expect "git ls-remote <pattern>" to work; ls-remote, correctly,
        # confuses <pattern> for <remote>. Although ugly, this behaviour is akin
@@ -120,7 +102,6 @@ test_expect_success 'confuses pattern as remote when no remote specified' '
        # role as a pattern.
        test_must_fail git ls-remote refs*master >actual 2>&1 &&
        test_cmp exp actual
-
 '
 
 test_expect_success 'die with non-2 for wrong repository even with --exit-code' '
index 2e0414f6c3d006ac6f06b884a13397c67f6b7df1..a1bc3d53a606bb67eb5b808834c57a53e1b32fbe 100644 (file)
@@ -1,8 +1,8 @@
 # br-branches-default
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f               branch 'master' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index ca2cc1d1b44e3edc8cd42e2e77d0f85658a52195..12ab08e8acb1ec04ac0033fc5874cdf1cd94f1c7 100644 (file)
@@ -1,9 +1,9 @@
 # br-branches-default-merge
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 7d947cd80f9cf656024206f1ea31da0d9f10f493..54427522dd5f350c11f7f160020cccc2060b7309 100644 (file)
@@ -1,9 +1,9 @@
 # br-branches-default-merge branches-default
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index ec39c54b7e242ddbeec76f55b98f555d562aa271..498a761aae8f048dd82f577de5d7bc92d97fa8f1 100644 (file)
@@ -1,10 +1,10 @@
 # br-branches-default-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 6bf42e24b67b526bac49e3cdb287e32513f4a6c4..0857f134e10d662e6712910bf273985fd4aa8471 100644 (file)
@@ -1,10 +1,10 @@
 # br-branches-default-octopus branches-default
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 4a2bf3c95ca474417d1dd54d1ac0bcc02bb5f402..8cbd718936223fd7dec89638d5ad9a4933a37e9d 100644 (file)
@@ -1,8 +1,8 @@
 # br-branches-default branches-default
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f               branch 'master' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 12ac8d20fba1c8a9402b92aa71e2e6797101a042..c98f67052638fd340b4c2069c85536b69e123b5c 100644 (file)
@@ -1,8 +1,8 @@
 # br-branches-one
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index b4b3b35ce0e2f46a16b015a74b771eb90ed3ebad..54a77420d5d05de53c59c63d10e4438f3ed2b4dc 100644 (file)
@@ -1,9 +1,9 @@
 # br-branches-one-merge
-8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 2ecef384eb7d823104581bfe2b4bd240b449e5df..b4d1bb0b0ba18a01ce5d0705b2822564e63c628c 100644 (file)
@@ -1,9 +1,9 @@
 # br-branches-one-merge branches-one
-8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 96e3029416b46ab4192d3e4aaa285a02489e4054..97c4b544b8c2c4466ab62759533703896f31ca4c 100644 (file)
@@ -1,9 +1,9 @@
 # br-branches-one-octopus
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 55e0bad621cde0c93e6a6fb92dc259c61986aba5..df705f74c7d6e24577ae48f1e783706d7ec939eb 100644 (file)
@@ -1,9 +1,9 @@
 # br-branches-one-octopus branches-one
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 281fa09d481a2d04788d5c1b0a67b8f569203ebc..96890e5bd9920bfe3ea27a7e992f689b01badc2f 100644 (file)
@@ -1,8 +1,8 @@
 # br-branches-one branches-one
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index e2fa9c8654647e46baa89d24501b050209aefdc1..68fc927263ed582621fe3385770603900e7ce841 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index ec1a7231aa7875df2cebd32411bad4861c233dcd..5ce764a06e421e0134b0f6710863d34e565ffb94 100644 (file)
@@ -1,11 +1,11 @@
 # br-config-explicit-merge
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 54f689151ff1006561309f6f7ca5e3523f8626c4..b1152b76dc2ea66a8d1dc4e5259362a34009fe49 100644 (file)
@@ -1,11 +1,11 @@
 # br-config-explicit-merge config-explicit
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 7011dfc18140aade896592c853bec85567d4ccc8..110577bb67921bc061718aafa7c4194454e9acd5 100644 (file)
@@ -1,11 +1,11 @@
 # br-config-explicit-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index bdad51f87163b51c378aaa1ca9bed8fe4a461af1..a29dd8baba047bfea7e44735c902b3b4d0acc7ae 100644 (file)
@@ -1,11 +1,11 @@
 # br-config-explicit-octopus config-explicit
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 1b237dde6e03b9bb1f4b9ad0b5ab75ae31127b57..b19b0162e1f234965687acb5041c504258d5ab68 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index e75ec2f72b4420cf27bcb959ab83518d93e115b1..946d70ca0718e499d576772059060c5304d1e7f2 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index ce8f739a0d5f53d24d1408e0e39719c0411e3c0d..89f2596cb9d7a9d586e4c51c4e232b2ddb48ce52 100644 (file)
@@ -1,11 +1,11 @@
 # br-config-glob-merge
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 5817bed8f88fcc11b9d5e67ce0863249339eeb2c..2ba4832160a3894f439d7d54105c32ba3c1b008b 100644 (file)
@@ -1,11 +1,11 @@
 # br-config-glob-merge config-glob
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 938e532db25e684599b39d1c862680a1caf8ea23..64994df7e2561e18013d005d0918819b9eb1a87a 100644 (file)
@@ -1,11 +1,11 @@
 # br-config-glob-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index c9225bf6ff060118ae85b5c666085b3a558db16e..681a725adc989b427afc134267e16d8f85e71334 100644 (file)
@@ -1,11 +1,11 @@
 # br-config-glob-octopus config-glob
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index a6c20f92ce6e708f915b285310a94b7080eab1e3..19daf0cb77e313f013fe89406cd989d0c12f7fd4 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 83534d2ec85e30dee5c32b8d385dd717cf4930a2..ab44bc551969d358c32c6b99e79c5748df2ec669 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index a9064dd65a0e569cc3e7ce5ae2368313f6bfa7ec..d018b3515f74acaa27c32022b865402d1e08ccd2 100644 (file)
@@ -1,11 +1,11 @@
 # br-remote-explicit-merge
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 732a37e4d358f0239687770b28c109051c438070..0d3d780dd0ccd5f82401adb2124d54d214d9e0b5 100644 (file)
@@ -1,11 +1,11 @@
 # br-remote-explicit-merge remote-explicit
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index ecf020d9298e7a031833e9986f2deb787712cb89..6f843044ede1ad776cb7b0a0769fe9aff8d5332c 100644 (file)
@@ -1,11 +1,11 @@
 # br-remote-explicit-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index af7753101140d43e2a592ec37b88d57d53d45bde..3546a837136dfef38101faa79aabff89efbc42a9 100644 (file)
@@ -1,11 +1,11 @@
 # br-remote-explicit-octopus remote-explicit
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 51fae567c8765fc6f447d7ee93a9796e022663a5..01e014e6a0219073abdd000bd46915acfa45559b 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 94e6ad31e301a87b52160f2287b54051e27b4e18..09bfcee00f9cae28660fc6be3fcddf5272c4a5b3 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 09362e25af564342ccb5ed9507caa103676aedf6..7e1a433a64f38123b342333a91a87ffab5a2c3b1 100644 (file)
@@ -1,11 +1,11 @@
 # br-remote-glob-merge
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index e2eabec62e12b29be2b8cd0c8e6d0cc9ad042f4d..53571bb4ec64bd59a258e69ac1762e1ed73bc321 100644 (file)
@@ -1,11 +1,11 @@
 # br-remote-glob-merge remote-glob
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index b08e0461954dcedc90df43c03302e3d4257c6f4b..c7c8b6d7f47df130b00a25ccb626496e55dd2ff4 100644 (file)
@@ -1,11 +1,11 @@
 # br-remote-glob-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index d4d547c84733f0faacc85c88c7b7fa138933e4a6..36076fba0cc70f5df4879afff225dfb0e1145884 100644 (file)
@@ -1,11 +1,11 @@
 # br-remote-glob-octopus remote-glob
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 646dbc877008edae3644ceef92fd5bed990eb784..20ba5cb1725d4b97a3bc9ac2c2f5a9abf207d063 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 65ce6d99e2631fd777de10e11b3a58db5859c29a..887ccfc41f57fcf85f10a44deafa08fbd2c4d041 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 8258c808689d883b97867b3d07ca89e08146de12..1669cc4af0e8b3d179bcff9dd850f2f363278005 100644 (file)
@@ -1,7 +1,7 @@
 # br-unconfig --tags ../.git
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index f02bab2fb4a1e07c6128fb899bf42183d1f0965e..74115361ba00984ca2fd174ea2dd686e7ec2d7ae 100644 (file)
@@ -2,7 +2,7 @@
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               tag 'tag-one' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 85de41109ecfb6fd4ac3a1b3b15489b83506a63c..7726983818f81aa230a91d5512b079f2b6b733a5 100644 (file)
@@ -1,7 +1,7 @@
 # br-unconfig ../.git tag tag-one-tree tag tag-three-file
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 0da2337f1b95b2011524b3d047a291747bd80305..7b3750ce5c0136030fd8b50ad87c6b0231f1cffe 100644 (file)
@@ -1,7 +1,7 @@
 # br-unconfig ../.git tag tag-one tag tag-three
 8e32a6d901327a23ef831511badce7bf3bf46689               tag 'tag-one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               tag 'tag-three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899               tag 'tag-three' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index fc7041eefc2150417abc504e25f9d82049b3102e..da30e3c62c269b8a94b29f9edcb4a24d26185463 100644 (file)
@@ -1,8 +1,8 @@
 # br-unconfig branches-default
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f               branch 'master' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index e94cde745b58aeb360e2db064466260f2362cf87..e4614314c54e10b6abc285ad35b385de889b6cf4 100644 (file)
@@ -1,8 +1,8 @@
 # br-unconfig branches-one
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 01a283e70d9d10373c363d1fe15b39949bba8d89..ed323c98713c55658906f053517b9543eac7c5e6 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 3a556c5e964785046cc43e9c13351818f436b857..2372ed03c5c506300b233dc983475905c20d2499 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index db216dfa562e894999c565d44d43abb6c6dd562e..6318dd11b4c2d62e89b3be7b32bafaccc18da301 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index aee65c204d1b1468adaee9d41dc5c0a47a3ae999..1d9afad7d841a7d300c7653b19334c35a33bcdf0 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 950fd078db7ccbe27825183cb0236a0dcc45139a..9b29d6720002a3cd7fa19157762d26e908173d52 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 0e59950c7b5e18521b59a17d3aaddc3a314a6f9c..8a7493537b99ac9592fd548300d5332e4163ab77 100644 (file)
@@ -1,7 +1,7 @@
 # master --tags ../.git
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 82868524ca40041b243e146064f4451d6899f3c4..0672d1292f5fab7043842ac142fcdc5474a0cdcb 100644 (file)
@@ -2,7 +2,7 @@
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               tag 'tag-one' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 2e133eff296d291045e86fe6a8cba958106b9aae..0fd737cf81fd852d31346022c7e2b24545079fd2 100644 (file)
@@ -1,7 +1,7 @@
 # master ../.git tag tag-one-tree tag tag-three-file
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 92b18b40ccc883bede4d2067aa4bf7a529d2f059..e488986653714b1af47a2ed11f18eb05283d1d81 100644 (file)
@@ -1,7 +1,7 @@
 # master ../.git tag tag-one tag tag-three
 8e32a6d901327a23ef831511badce7bf3bf46689               tag 'tag-one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               tag 'tag-three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899               tag 'tag-three' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 603d6d23312b6a58d95a685803d8b19f0e0ac4e2..2eedd3bfa4a5f4a14c911cd37e00d40e9b0d1171 100644 (file)
@@ -1,8 +1,8 @@
 # master branches-default
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f               branch 'master' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index fe9bb0b7982951ccf85934e42ff7c9620e2312b4..901ce21d333c8c5a37bcc8953f81f93faf415563 100644 (file)
@@ -1,8 +1,8 @@
 # master branches-one
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 4be97c75752571d46aef0283f164abea025a1be8..251c826aa9dea34ce01bdee293b7c35b02bf6228 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index cb0726ff8d7ea9e801497b564b680710c2a273af..27c158e332f291dcde9aa345807c3b077c28dfd7 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 44a1ca84296079356eabc3fd37d04529a3492409..b3cfe6b98b24f9ffb241d8cea37c70123c6f3273 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 724e8db0a533248cab6784696c90db02ca1435f3..118befd1e483cb91eb8a28b6bba362565dcd59d5 100644 (file)
@@ -3,9 +3,9 @@
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899       not-for-merge   tag 'tag-three' of ../
 0e3b14047d3ee365f4f2a1b673db059c3972589c       not-for-merge   tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba       not-for-merge   tag 'tag-two' of ../
index 3abb2907ea91c5ef6298ca7ee64b7a8156d65d96..b69cf574d7e9a272ea70864e87ed6556abe94f13 100755 (executable)
@@ -40,6 +40,40 @@ mk_test () {
        )
 }
 
+mk_test_with_hooks() {
+       mk_test "$@" &&
+       (
+               cd testrepo &&
+               mkdir .git/hooks &&
+               cd .git/hooks &&
+
+               cat >pre-receive <<-'EOF' &&
+               #!/bin/sh
+               cat - >>pre-receive.actual
+               EOF
+
+               cat >update <<-'EOF' &&
+               #!/bin/sh
+               printf "%s %s %s\n" "$@" >>update.actual
+               EOF
+
+               cat >post-receive <<-'EOF' &&
+               #!/bin/sh
+               cat - >>post-receive.actual
+               EOF
+
+               cat >post-update <<-'EOF' &&
+               #!/bin/sh
+               for ref in "$@"
+               do
+                       printf "%s\n" "$ref" >>post-update.actual
+               done
+               EOF
+
+               chmod +x pre-receive update post-receive post-update
+       )
+}
+
 mk_child() {
        rm -rf "$1" &&
        git clone testrepo "$1"
@@ -559,6 +593,169 @@ test_expect_success 'allow deleting an invalid remote ref' '
 
 '
 
+test_expect_success 'pushing valid refs triggers post-receive and post-update hooks' '
+       mk_test_with_hooks heads/master heads/next &&
+       orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) &&
+       newmaster=$(git show-ref -s --verify refs/heads/master) &&
+       orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) &&
+       newnext=$_z40 &&
+       git push testrepo refs/heads/master:refs/heads/master :refs/heads/next &&
+       (
+               cd testrepo/.git &&
+               cat >pre-receive.expect <<-EOF &&
+               $orgmaster $newmaster refs/heads/master
+               $orgnext $newnext refs/heads/next
+               EOF
+
+               cat >update.expect <<-EOF &&
+               refs/heads/master $orgmaster $newmaster
+               refs/heads/next $orgnext $newnext
+               EOF
+
+               cat >post-receive.expect <<-EOF &&
+               $orgmaster $newmaster refs/heads/master
+               $orgnext $newnext refs/heads/next
+               EOF
+
+               cat >post-update.expect <<-EOF &&
+               refs/heads/master
+               refs/heads/next
+               EOF
+
+               test_cmp pre-receive.expect pre-receive.actual &&
+               test_cmp update.expect update.actual &&
+               test_cmp post-receive.expect post-receive.actual &&
+               test_cmp post-update.expect post-update.actual
+       )
+'
+
+test_expect_success 'deleting dangling ref triggers hooks with correct args' '
+       mk_test_with_hooks heads/master &&
+       rm -f testrepo/.git/objects/??/* &&
+       git push testrepo :refs/heads/master &&
+       (
+               cd testrepo/.git &&
+               cat >pre-receive.expect <<-EOF &&
+               $_z40 $_z40 refs/heads/master
+               EOF
+
+               cat >update.expect <<-EOF &&
+               refs/heads/master $_z40 $_z40
+               EOF
+
+               cat >post-receive.expect <<-EOF &&
+               $_z40 $_z40 refs/heads/master
+               EOF
+
+               cat >post-update.expect <<-EOF &&
+               refs/heads/master
+               EOF
+
+               test_cmp pre-receive.expect pre-receive.actual &&
+               test_cmp update.expect update.actual &&
+               test_cmp post-receive.expect post-receive.actual &&
+               test_cmp post-update.expect post-update.actual
+       )
+'
+
+test_expect_success 'deletion of a non-existent ref is not fed to post-receive and post-update hooks' '
+       mk_test_with_hooks heads/master &&
+       orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) &&
+       newmaster=$(git show-ref -s --verify refs/heads/master) &&
+       git push testrepo master :refs/heads/nonexistent &&
+       (
+               cd testrepo/.git &&
+               cat >pre-receive.expect <<-EOF &&
+               $orgmaster $newmaster refs/heads/master
+               $_z40 $_z40 refs/heads/nonexistent
+               EOF
+
+               cat >update.expect <<-EOF &&
+               refs/heads/master $orgmaster $newmaster
+               refs/heads/nonexistent $_z40 $_z40
+               EOF
+
+               cat >post-receive.expect <<-EOF &&
+               $orgmaster $newmaster refs/heads/master
+               EOF
+
+               cat >post-update.expect <<-EOF &&
+               refs/heads/master
+               EOF
+
+               test_cmp pre-receive.expect pre-receive.actual &&
+               test_cmp update.expect update.actual &&
+               test_cmp post-receive.expect post-receive.actual &&
+               test_cmp post-update.expect post-update.actual
+       )
+'
+
+test_expect_success 'deletion of a non-existent ref alone does trigger post-receive and post-update hooks' '
+       mk_test_with_hooks heads/master &&
+       git push testrepo :refs/heads/nonexistent &&
+       (
+               cd testrepo/.git &&
+               cat >pre-receive.expect <<-EOF &&
+               $_z40 $_z40 refs/heads/nonexistent
+               EOF
+
+               cat >update.expect <<-EOF &&
+               refs/heads/nonexistent $_z40 $_z40
+               EOF
+
+               test_cmp pre-receive.expect pre-receive.actual &&
+               test_cmp update.expect update.actual &&
+               test_path_is_missing post-receive.actual &&
+               test_path_is_missing post-update.actual
+       )
+'
+
+test_expect_success 'mixed ref updates, deletes, invalid deletes trigger hooks with correct input' '
+       mk_test_with_hooks heads/master heads/next heads/pu &&
+       orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) &&
+       newmaster=$(git show-ref -s --verify refs/heads/master) &&
+       orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) &&
+       newnext=$_z40 &&
+       orgpu=$(cd testrepo && git show-ref -s --verify refs/heads/pu) &&
+       newpu=$(git show-ref -s --verify refs/heads/master) &&
+       git push testrepo refs/heads/master:refs/heads/master \
+           refs/heads/master:refs/heads/pu :refs/heads/next \
+           :refs/heads/nonexistent &&
+       (
+               cd testrepo/.git &&
+               cat >pre-receive.expect <<-EOF &&
+               $orgmaster $newmaster refs/heads/master
+               $orgnext $newnext refs/heads/next
+               $orgpu $newpu refs/heads/pu
+               $_z40 $_z40 refs/heads/nonexistent
+               EOF
+
+               cat >update.expect <<-EOF &&
+               refs/heads/master $orgmaster $newmaster
+               refs/heads/next $orgnext $newnext
+               refs/heads/pu $orgpu $newpu
+               refs/heads/nonexistent $_z40 $_z40
+               EOF
+
+               cat >post-receive.expect <<-EOF &&
+               $orgmaster $newmaster refs/heads/master
+               $orgnext $newnext refs/heads/next
+               $orgpu $newpu refs/heads/pu
+               EOF
+
+               cat >post-update.expect <<-EOF &&
+               refs/heads/master
+               refs/heads/next
+               refs/heads/pu
+               EOF
+
+               test_cmp pre-receive.expect pre-receive.actual &&
+               test_cmp update.expect update.actual &&
+               test_cmp post-receive.expect post-receive.actual &&
+               test_cmp post-update.expect post-update.actual
+       )
+'
+
 test_expect_success 'allow deleting a ref using --delete' '
        mk_test heads/master &&
        (cd testrepo && git config receive.denyDeleteCurrent warn) &&
index 0e5eb678ce6b2f4fad79c39947455e5284313ba4..35304b41e9ce6222f7d713e3d310e47241d8e6e0 100755 (executable)
@@ -94,16 +94,35 @@ test_expect_success '--rebase' '
        test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
        test new = $(git show HEAD:file2)
 '
+test_expect_success 'pull.rebase' '
+       git reset --hard before-rebase &&
+       git config --bool pull.rebase true &&
+       test_when_finished "git config --unset pull.rebase" &&
+       git pull . copy &&
+       test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
+       test new = $(git show HEAD:file2)
+'
 
 test_expect_success 'branch.to-rebase.rebase' '
        git reset --hard before-rebase &&
-       git config branch.to-rebase.rebase 1 &&
+       git config --bool branch.to-rebase.rebase true &&
+       test_when_finished "git config --unset branch.to-rebase.rebase" &&
        git pull . copy &&
-       git config branch.to-rebase.rebase 0 &&
        test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
        test new = $(git show HEAD:file2)
 '
 
+test_expect_success 'branch.to-rebase.rebase should override pull.rebase' '
+       git reset --hard before-rebase &&
+       git config --bool pull.rebase true &&
+       test_when_finished "git config --unset pull.rebase" &&
+       git config --bool branch.to-rebase.rebase false &&
+       test_when_finished "git config --unset branch.to-rebase.rebase" &&
+       git pull . copy &&
+       test $(git rev-parse HEAD^) != $(git rev-parse copy) &&
+       test new = $(git show HEAD:file2)
+'
+
 test_expect_success '--rebase with rebased upstream' '
 
        git remote add -f me . &&
index c229fe68f11007fbb96d7b3837c18fd2f306a0bf..3683df13a6ae2208212bd3606187f9876e0562b5 100755 (executable)
@@ -101,11 +101,19 @@ test_expect_success TTY 'push -q suppresses progress' '
        ! grep "Writing objects" err
 '
 
-test_expect_failure TTY 'push --no-progress suppresses progress' '
+test_expect_success TTY 'push --no-progress suppresses progress' '
        ensure_fresh_upstream &&
 
        test_terminal git push -u --no-progress upstream master >out 2>err &&
+       ! grep "Unpacking objects" err &&
        ! grep "Writing objects" err
 '
 
+test_expect_success TTY 'quiet push' '
+       ensure_fresh_upstream &&
+
+       test_terminal git push --quiet --no-progress upstream master 2>&1 | tee output &&
+       test_cmp /dev/null output
+'
+
 test_done
diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh
new file mode 100755 (executable)
index 0000000..edea9f9
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+test_description='test fetching of oddly-named refs'
+. ./test-lib.sh
+
+# afterwards we will have:
+#  HEAD - two
+#  refs/for/refs/heads/master - one
+#  refs/heads/master - three
+test_expect_success 'setup repo with odd suffix ref' '
+       echo content >file &&
+       git add . &&
+       git commit -m one &&
+       git update-ref refs/for/refs/heads/master HEAD &&
+       echo content >>file &&
+       git commit -a -m two &&
+       echo content >>file &&
+       git commit -a -m three &&
+       git checkout HEAD^
+'
+
+test_expect_success 'suffix ref is ignored during fetch' '
+       git clone --bare file://"$PWD" suffix &&
+       echo three >expect &&
+       git --git-dir=suffix log -1 --format=%s refs/heads/master >actual &&
+       test_cmp expect actual
+'
+
+test_done
index 64767d87055c9ad65a225432b5193497c9fad405..1eea64765608dc4a2e4b19eec4155890306e5d59 100755 (executable)
@@ -40,6 +40,22 @@ test_expect_success 'setup remote repository' '
        mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH"
 '
 
+test_expect_success 'create password-protected repository' '
+       mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb" &&
+       cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
+              "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git"
+'
+
+test_expect_success 'setup askpass helper' '
+       cat >askpass <<-\EOF &&
+       #!/bin/sh
+       echo user@host
+       EOF
+       chmod +x askpass &&
+       GIT_ASKPASS="$PWD/askpass" &&
+       export GIT_ASKPASS
+'
+
 test_expect_success 'clone remote repository' '
        cd "$ROOT_PATH" &&
        git clone $HTTPD_URL/dumb/test_repo.git test_repo_clone
@@ -144,6 +160,24 @@ test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
 test_http_push_nonff "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \
        "$ROOT_PATH"/test_repo_clone master
 
+test_expect_success 'push to password-protected repository (user in URL)' '
+       test_commit pw-user &&
+       git push "$HTTPD_URL_USER/auth/dumb/test_repo.git" HEAD &&
+       git rev-parse --verify HEAD >expect &&
+       git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git" \
+               rev-parse --verify HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_failure 'push to password-protected repository (no user in URL)' '
+       test_commit pw-nouser &&
+       git push "$HTTPD_URL/auth/dumb/test_repo.git" HEAD &&
+       git rev-parse --verify HEAD >expect &&
+       git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git" \
+               rev-parse --verify HEAD >actual &&
+       test_cmp expect actual
+'
+
 stop_httpd
 
 test_done
index a73c82635ff6fba6fcef4f139ff09205c1b9b6de..cc6f081711002b42bcf6b2cb26287dcc56852a06 100755 (executable)
@@ -14,6 +14,7 @@ fi
 ROOT_PATH="$PWD"
 LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5541'}
 . "$TEST_DIRECTORY"/lib-httpd.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
 start_httpd
 
 test_expect_success 'setup remote repository' '
@@ -95,6 +96,32 @@ test_expect_success 'create and delete remote branch' '
        test_must_fail git show-ref --verify refs/remotes/origin/dev
 '
 
+cat >"$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git/hooks/update" <<EOF
+#!/bin/sh
+exit 1
+EOF
+chmod a+x "$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git/hooks/update"
+
+cat >exp <<EOF
+remote: error: hook declined to update refs/heads/dev2
+To http://127.0.0.1:$LIB_HTTPD_PORT/smart/test_repo.git
+ ! [remote rejected] dev2 -> dev2 (hook declined)
+error: failed to push some refs to 'http://127.0.0.1:$LIB_HTTPD_PORT/smart/test_repo.git'
+EOF
+
+test_expect_success 'rejected update prints status' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       git checkout -b dev2 &&
+       : >path4 &&
+       git add path4 &&
+       test_tick &&
+       git commit -m dev2 &&
+       test_must_fail git push origin dev2 2>act &&
+       sed -e "/^remote: /s/ *$//" <act >cmp &&
+       test_cmp exp cmp
+'
+rm -f "$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git/hooks/update"
+
 cat >exp <<EOF
 
 GET  /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
@@ -106,6 +133,8 @@ 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
+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 "
@@ -154,5 +183,44 @@ test_expect_success 'push (chunked)' '
         test $HEAD = $(git rev-parse --verify HEAD))
 '
 
+test_expect_success 'push --all can push to empty repo' '
+       d=$HTTPD_DOCUMENT_ROOT_PATH/empty-all.git &&
+       git init --bare "$d" &&
+       git --git-dir="$d" config http.receivepack true &&
+       git push --all "$HTTPD_URL"/smart/empty-all.git
+'
+
+test_expect_success 'push --mirror can push to empty repo' '
+       d=$HTTPD_DOCUMENT_ROOT_PATH/empty-mirror.git &&
+       git init --bare "$d" &&
+       git --git-dir="$d" config http.receivepack true &&
+       git push --mirror "$HTTPD_URL"/smart/empty-mirror.git
+'
+
+test_expect_success 'push --all to repo with alternates' '
+       s=$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git &&
+       d=$HTTPD_DOCUMENT_ROOT_PATH/alternates-all.git &&
+       git clone --bare --shared "$s" "$d" &&
+       git --git-dir="$d" config http.receivepack true &&
+       git --git-dir="$d" repack -adl &&
+       git push --all "$HTTPD_URL"/smart/alternates-all.git
+'
+
+test_expect_success 'push --mirror to repo with alternates' '
+       s=$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git &&
+       d=$HTTPD_DOCUMENT_ROOT_PATH/alternates-mirror.git &&
+       git clone --bare --shared "$s" "$d" &&
+       git --git-dir="$d" config http.receivepack true &&
+       git --git-dir="$d" repack -adl &&
+       git push --mirror "$HTTPD_URL"/smart/alternates-mirror.git
+'
+
+test_expect_success TTY 'quiet push' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       test_commit quiet &&
+       test_terminal git push --quiet --no-progress 2>&1 | tee output &&
+       test_cmp /dev/null output
+'
+
 stop_httpd
 test_done
index a1883ca6b649a236e5e08a5e4b76d1479353dcac..e5e6b8f643206c2d4fd01e3ad71ca50a43f3da19 100755 (executable)
@@ -8,8 +8,8 @@ if test -n "$NO_CURL"; then
        test_done
 fi
 
-. "$TEST_DIRECTORY"/lib-httpd.sh
 LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5550'}
+. "$TEST_DIRECTORY"/lib-httpd.sh
 start_httpd
 
 test_expect_success 'setup repository' '
@@ -35,11 +35,98 @@ test_expect_success 'clone http repository' '
        test_cmp file clone/file
 '
 
-test_expect_success 'clone http repository with authentication' '
+test_expect_success 'create password-protected repository' '
        mkdir "$HTTPD_DOCUMENT_ROOT_PATH/auth/" &&
-       cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" "$HTTPD_DOCUMENT_ROOT_PATH/auth/repo.git" &&
-       git clone $AUTH_HTTPD_URL/auth/repo.git clone-auth &&
-       test_cmp file clone-auth/file
+       cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
+              "$HTTPD_DOCUMENT_ROOT_PATH/auth/repo.git"
+'
+
+test_expect_success 'setup askpass helpers' '
+       cat >askpass <<-EOF &&
+       #!/bin/sh
+       echo >>"$PWD/askpass-query" "askpass: \$*" &&
+       cat "$PWD/askpass-response"
+       EOF
+       chmod +x askpass &&
+       GIT_ASKPASS="$PWD/askpass" &&
+       export GIT_ASKPASS
+'
+
+expect_askpass() {
+       dest=$HTTPD_DEST
+       {
+               case "$1" in
+               none)
+                       ;;
+               pass)
+                       echo "askpass: Password for 'http://$2@$dest': "
+                       ;;
+               both)
+                       echo "askpass: Username for 'http://$dest': "
+                       echo "askpass: Password for 'http://$2@$dest': "
+                       ;;
+               *)
+                       false
+                       ;;
+               esac
+       } >askpass-expect &&
+       test_cmp askpass-expect askpass-query
+}
+
+test_expect_success 'cloning password-protected repository can fail' '
+       >askpass-query &&
+       echo wrong >askpass-response &&
+       test_must_fail git clone "$HTTPD_URL/auth/repo.git" clone-auth-fail &&
+       expect_askpass both wrong
+'
+
+test_expect_success 'http auth can use user/pass in URL' '
+       >askpass-query &&
+       echo wrong >askpass-response &&
+       git clone "$HTTPD_URL_USER_PASS/auth/repo.git" clone-auth-none &&
+       expect_askpass none
+'
+
+test_expect_success 'http auth can use just user in URL' '
+       >askpass-query &&
+       echo user@host >askpass-response &&
+       git clone "$HTTPD_URL_USER/auth/repo.git" clone-auth-pass &&
+       expect_askpass pass user@host
+'
+
+test_expect_success 'http auth can request both user and pass' '
+       >askpass-query &&
+       echo user@host >askpass-response &&
+       git clone "$HTTPD_URL/auth/repo.git" clone-auth-both &&
+       expect_askpass both user@host
+'
+
+test_expect_success 'http auth respects credential helper config' '
+       test_config_global credential.helper "!f() {
+               cat >/dev/null
+               echo username=user@host
+               echo password=user@host
+       }; f" &&
+       >askpass-query &&
+       echo wrong >askpass-response &&
+       git clone "$HTTPD_URL/auth/repo.git" clone-auth-helper &&
+       expect_askpass none
+'
+
+test_expect_success 'http auth can get username from config' '
+       test_config_global "credential.$HTTPD_URL.username" user@host &&
+       >askpass-query &&
+       echo user@host >askpass-response &&
+       git clone "$HTTPD_URL/auth/repo.git" clone-auth-user &&
+       expect_askpass pass user@host
+'
+
+test_expect_success 'configured username does not override URL' '
+       test_config_global "credential.$HTTPD_URL.username" wrong &&
+       >askpass-query &&
+       echo user@host >askpass-response &&
+       git clone "$HTTPD_URL_USER/auth/repo.git" clone-auth-user2 &&
+       expect_askpass pass user@host
 '
 
 test_expect_success 'fetch changes via http' '
@@ -75,8 +162,7 @@ test_expect_success 'http remote detects correct HEAD' '
 test_expect_success 'fetch packed objects' '
        cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
        (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
-        git --bare repack &&
-        git --bare prune-packed
+        git --bare repack -a -d
        ) &&
        git clone $HTTPD_URL/dumb/repo_pack.git
 '
index 0ad7ce07c4550ed28a22743a3c543e16f4d4c558..ef98d95e00d7d25ee408b311339cf4f10c44a5ad 100755 (executable)
@@ -17,7 +17,7 @@ run_backend() {
 GET() {
        REQUEST_METHOD="GET" && export REQUEST_METHOD &&
        run_backend "/repo.git/$1" &&
-       unset REQUEST_METHOD &&
+       sane_unset REQUEST_METHOD &&
        if ! grep "Status" act.out >act
        then
                printf "Status: 200 OK\r\n" >act
@@ -30,8 +30,8 @@ POST() {
        REQUEST_METHOD="POST" && export REQUEST_METHOD &&
        CONTENT_TYPE="application/x-$1-request" && export CONTENT_TYPE &&
        run_backend "/repo.git/$1" "$2" &&
-       unset REQUEST_METHOD &&
-       unset CONTENT_TYPE &&
+       sane_unset REQUEST_METHOD &&
+       sane_unset CONTENT_TYPE &&
        if ! grep "Status" act.out >act
        then
                printf "Status: 200 OK\r\n" >act
index e8103144bb026afb12f5b058b9ec399b70abebbd..87ee01662c2fcfa58d8f9896fd43881e2e7ed7d6 100755 (executable)
@@ -206,6 +206,20 @@ test_expect_success 'clone from .git file' '
        git clone dst/.git dst2
 '
 
+test_expect_success 'fetch from .git gitfile' '
+       (
+               cd dst2 &&
+               git fetch ../dst/.git
+       )
+'
+
+test_expect_success 'fetch from gitfile parent' '
+       (
+               cd dst2 &&
+               git fetch ../dst
+       )
+'
+
 test_expect_success 'clone separate gitdir where target already exists' '
        rm -rf dst &&
        test_must_fail git clone --separate-git-dir realgitdir src dst
index 895f5595aee9341276e79497b9c4a8736c78e5e7..c4c375ac042bbb7f58998c87d8c9277d2f5004a6 100755 (executable)
@@ -146,4 +146,11 @@ test_expect_success 'cloning with reference being subset of source (-l -s)' \
 
 cd "$base_dir"
 
+test_expect_success 'clone with reference from a tagged repository' '
+       (
+               cd A && git tag -a -m 'tagged' HEAD
+       ) &&
+       git clone --reference=A A I
+'
+
 test_done
index 728ccd88c3d69f06bff2c3593ddc325f9186402c..9e43731fe504c46737c5294e4d508b5c20bbb663 100755 (executable)
@@ -4,53 +4,58 @@ test_description='some bundle related tests'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
-
-       : > file &&
-       git add file &&
-       test_tick &&
-       git commit -m initial &&
+       test_commit initial &&
        test_tick &&
        git tag -m tag tag &&
-       : > file2 &&
-       git add file2 &&
-       : > file3 &&
-       test_tick &&
-       git commit -m second &&
-       git add file3 &&
-       test_tick &&
-       git commit -m third
-
+       test_commit second &&
+       test_commit third &&
+       git tag -d initial &&
+       git tag -d second &&
+       git tag -d third
 '
 
 test_expect_success 'tags can be excluded by rev-list options' '
-
        git bundle create bundle --all --since=7.Apr.2005.15:16:00.-0700 &&
        git ls-remote bundle > output &&
        ! grep tag output
-
 '
 
 test_expect_success 'die if bundle file cannot be created' '
-
        mkdir adir &&
        test_must_fail git bundle create adir --all
-
 '
 
 test_expect_failure 'bundle --stdin' '
-
        echo master | git bundle create stdin-bundle.bdl --stdin &&
        git ls-remote stdin-bundle.bdl >output &&
        grep master output
-
 '
 
 test_expect_failure 'bundle --stdin <rev-list options>' '
-
        echo master | git bundle create hybrid-bundle.bdl --stdin tag &&
        git ls-remote hybrid-bundle.bdl >output &&
        grep master output
+'
+
+test_expect_success 'empty bundle file is rejected' '
+       : >empty-bundle &&
+       test_must_fail git fetch empty-bundle
+'
 
+# This triggers a bug in older versions where the resulting line (with
+# --pretty=oneline) was longer than a 1024-char buffer.
+test_expect_success 'ridiculously long subject in boundary' '
+       : >file4 &&
+       test_tick &&
+       git add file4 &&
+       printf "%01200d\n" 0 | git commit -F - &&
+       test_commit fifth &&
+       git bundle create long-subject-bundle.bdl HEAD^..HEAD &&
+       git bundle list-heads long-subject-bundle.bdl >heads &&
+       test -s heads &&
+       git fetch long-subject-bundle.bdl &&
+       sed -n "/^-/{p;q;}" long-subject-bundle.bdl >boundary &&
+       grep "^-[0-9a-f]\\{40\\} " boundary
 '
 
 test_done
diff --git a/t/t5900-repo-selection.sh b/t/t5900-repo-selection.sh
new file mode 100755 (executable)
index 0000000..3d5b418
--- /dev/null
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+test_description='selecting remote repo in ambiguous cases'
+. ./test-lib.sh
+
+reset() {
+       rm -rf foo foo.git fetch clone
+}
+
+make_tree() {
+       git init "$1" &&
+       (cd "$1" && test_commit "$1")
+}
+
+make_bare() {
+       git init --bare "$1" &&
+       (cd "$1" &&
+        tree=`git hash-object -w -t tree /dev/null` &&
+        commit=$(echo "$1" | git commit-tree $tree) &&
+        git update-ref HEAD $commit
+       )
+}
+
+get() {
+       git init --bare fetch &&
+       (cd fetch && git fetch "../$1") &&
+       git clone "$1" clone
+}
+
+check() {
+       echo "$1" >expect &&
+       (cd fetch && git log -1 --format=%s FETCH_HEAD) >actual.fetch &&
+       (cd clone && git log -1 --format=%s HEAD) >actual.clone &&
+       test_cmp expect actual.fetch &&
+       test_cmp expect actual.clone
+}
+
+test_expect_success 'find .git dir in worktree' '
+       reset &&
+       make_tree foo &&
+       get foo &&
+       check foo
+'
+
+test_expect_success 'automagically add .git suffix' '
+       reset &&
+       make_bare foo.git &&
+       get foo &&
+       check foo.git
+'
+
+test_expect_success 'automagically add .git suffix to worktree' '
+       reset &&
+       make_tree foo.git &&
+       get foo &&
+       check foo.git
+'
+
+test_expect_success 'prefer worktree foo over bare foo.git' '
+       reset &&
+       make_tree foo &&
+       make_bare foo.git &&
+       get foo &&
+       check foo
+'
+
+test_expect_success 'prefer bare foo over bare foo.git' '
+       reset &&
+       make_bare foo &&
+       make_bare foo.git &&
+       get foo &&
+       check foo
+'
+
+test_expect_success 'disambiguate with full foo.git' '
+       reset &&
+       make_bare foo &&
+       make_bare foo.git &&
+       get foo.git &&
+       check foo.git
+'
+
+test_expect_success 'we are not fooled by non-git foo directory' '
+       reset &&
+       make_bare foo.git &&
+       mkdir foo &&
+       get foo &&
+       check foo.git
+'
+
+test_expect_success 'prefer inner .git over outer bare' '
+       reset &&
+       make_tree foo &&
+       make_bare foo.git &&
+       mv foo/.git foo.git &&
+       get foo.git &&
+       check foo
+'
+
+test_done
index d918cc02d090157485d404d34d49005e50cd9f1a..444279077e803ca96e48281ae956ea6536317608 100755 (executable)
@@ -267,6 +267,12 @@ test_expect_success '%gd shortens ref name' '
        test_cmp expect.gd-short actual.gd-short
 '
 
+test_expect_success 'reflog identity' '
+       echo "C O Mitter:committer@example.com" >expect &&
+       git log -g -1 --format="%gn:%ge" >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'oneline with empty message' '
        git commit -m "dummy" --allow-empty &&
        git commit -m "dummy" --allow-empty &&
index af34a1e81711ca183680c9882353bd786b55e79f..839ad97b791c6aa757d0b82eea7fc16369ad4586 100755 (executable)
@@ -86,5 +86,6 @@ check_result 'I H E C B A' --full-history --date-order -- file
 check_result 'I E C B A' --simplify-merges -- file
 check_result 'I B A' -- file
 check_result 'I B A' --topo-order -- file
+check_result 'H' --first-parent -- another-file
 
 test_done
index c6f1f9f8ab2353ec81401828f8500cb7c1a869fb..691e4a4481eba2994ec40e498d4cd25fcff67f26 100755 (executable)
@@ -164,7 +164,7 @@ test_expect_success 'bisect start: existing ".git/BISECT_START" not modified if
        cp .git/BISECT_START saved &&
        test_must_fail git bisect start $HASH4 foo -- &&
        git branch > branch.output &&
-       grep "* (no branch)" branch.output > /dev/null &&
+       test_i18ngrep "* (no branch)" branch.output > /dev/null &&
        test_cmp saved .git/BISECT_START
 '
 test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' '
index fdb6c253718e3d4c8acfcd1fc197607aac04b4c4..94f010be8a17b8aaa8099d1a54ad2bd4317b67dc 100755 (executable)
@@ -95,7 +95,7 @@ test_expect_success 'setup large simple rename' '
 '
 
 test_expect_success 'massive simple rename does not spam added files' '
-       unset GIT_MERGE_VERBOSITY &&
+       sane_unset GIT_MERGE_VERBOSITY &&
        git merge --no-stat simple-rename | grep -v Removing >output &&
        test 5 -gt "$(wc -l < output)"
 '
index 19de5b16eb530d428f66e5161a3ab48fce34049d..19272bc551277903bc1c444f4f0f05d8f2d7d672 100755 (executable)
@@ -51,6 +51,22 @@ test_expect_success 'branch -v' '
        test_i18ncmp expect actual
 '
 
+cat >expect <<\EOF
+b1 origin/master: ahead 1, behind 1
+b2 origin/master: ahead 1, behind 1
+b3 origin/master: behind 1
+b4 origin/master: ahead 2
+EOF
+
+test_expect_success 'branch -vv' '
+       (
+               cd test &&
+               git branch -vv
+       ) |
+       sed -n -e "$script" >actual &&
+       test_i18ncmp expect actual
+'
+
 test_expect_success 'checkout' '
        (
                cd test && git checkout b1
index 7dc8a510c7f33d048cd3268424298fdda2f8c2bb..172178490ad126e07b70a6e3f72e0bf723d2c42b 100755 (executable)
@@ -6,6 +6,7 @@
 test_description='for-each-ref test'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
 
 # Mon Jul 3 15:18:43 2006 +0000
 datestamp=1151939923
@@ -37,11 +38,13 @@ test_atom() {
        case "$1" in
                head) ref=refs/heads/master ;;
                 tag) ref=refs/tags/testtag ;;
+                  *) ref=$1 ;;
        esac
        printf '%s\n' "$3" >expected
-       test_expect_${4:-success} "basic atom: $1 $2" "
+       test_expect_${4:-success} $PREREQ "basic atom: $1 $2" "
                git for-each-ref --format='%($2)' $ref >actual &&
-               test_cmp expected actual
+               sanitize_pgp <actual >actual.clean &&
+               test_cmp expected actual.clean
        "
 }
 
@@ -71,7 +74,10 @@ test_atom head taggerdate ''
 test_atom head creator 'C O Mitter <committer@example.com> 1151939923 +0200'
 test_atom head creatordate 'Mon Jul 3 17:18:43 2006 +0200'
 test_atom head subject 'Initial'
+test_atom head contents:subject 'Initial'
 test_atom head body ''
+test_atom head contents:body ''
+test_atom head contents:signature ''
 test_atom head contents 'Initial
 '
 
@@ -101,7 +107,10 @@ test_atom tag taggerdate 'Mon Jul 3 17:18:45 2006 +0200'
 test_atom tag creator 'C O Mitter <committer@example.com> 1151939925 +0200'
 test_atom tag creatordate 'Mon Jul 3 17:18:45 2006 +0200'
 test_atom tag subject 'Tagging at 1151939927'
+test_atom tag contents:subject 'Tagging at 1151939927'
 test_atom tag body ''
+test_atom tag contents:body ''
+test_atom tag contents:signature ''
 test_atom tag contents 'Tagging at 1151939927
 '
 
@@ -359,4 +368,92 @@ test_expect_success 'an unusual tag with an incomplete line' '
 
 '
 
+test_expect_success 'create tag with subject and body content' '
+       cat >>msg <<-\EOF &&
+               the subject line
+
+               first body line
+               second body line
+       EOF
+       git tag -F msg subject-body
+'
+test_atom refs/tags/subject-body subject 'the subject line'
+test_atom refs/tags/subject-body body 'first body line
+second body line
+'
+test_atom refs/tags/subject-body contents 'the subject line
+
+first body line
+second body line
+'
+
+test_expect_success 'create tag with multiline subject' '
+       cat >msg <<-\EOF &&
+               first subject line
+               second subject line
+
+               first body line
+               second body line
+       EOF
+       git tag -F msg multiline
+'
+test_atom refs/tags/multiline subject 'first subject line second subject line'
+test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
+test_atom refs/tags/multiline body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:signature ''
+test_atom refs/tags/multiline contents 'first subject line
+second subject line
+
+first body line
+second body line
+'
+
+test_expect_success GPG 'create signed tags' '
+       git tag -s -m "" signed-empty &&
+       git tag -s -m "subject line" signed-short &&
+       cat >msg <<-\EOF &&
+       subject line
+
+       body contents
+       EOF
+       git tag -s -F msg signed-long
+'
+
+sig='-----BEGIN PGP SIGNATURE-----
+-----END PGP SIGNATURE-----
+'
+
+PREREQ=GPG
+test_atom refs/tags/signed-empty subject ''
+test_atom refs/tags/signed-empty contents:subject ''
+test_atom refs/tags/signed-empty body "$sig"
+test_atom refs/tags/signed-empty contents:body ''
+test_atom refs/tags/signed-empty contents:signature "$sig"
+test_atom refs/tags/signed-empty contents "$sig"
+
+test_atom refs/tags/signed-short subject 'subject line'
+test_atom refs/tags/signed-short contents:subject 'subject line'
+test_atom refs/tags/signed-short body "$sig"
+test_atom refs/tags/signed-short contents:body ''
+test_atom refs/tags/signed-short contents:signature "$sig"
+test_atom refs/tags/signed-short contents "subject line
+$sig"
+
+test_atom refs/tags/signed-long subject 'subject line'
+test_atom refs/tags/signed-long contents:subject 'subject line'
+test_atom refs/tags/signed-long body "body contents
+$sig"
+test_atom refs/tags/signed-long contents:body 'body contents
+'
+test_atom refs/tags/signed-long contents:signature "$sig"
+test_atom refs/tags/signed-long contents "subject line
+
+body contents
+$sig"
+
 test_done
index 097ce2bc8382e748925aa25c2dc0a1f06f6c7b4c..4ef79aabc47a4ef2e9def65253edf6bcd8ce91a3 100755 (executable)
@@ -8,6 +8,7 @@ test_description='git tag
 Tests for operations with tags.'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
 
 # creating and listing lightweight tags:
 
@@ -585,23 +586,18 @@ test_expect_success \
        test_cmp expect actual
 '
 
-# subsequent tests require gpg; check if it is available
-gpg --version >/dev/null 2>/dev/null
-if [ $? -eq 127 ]; then
-       say "# gpg not found - skipping tag signing and verification tests"
-else
-       # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
-       # the gpg version 1.0.6 didn't parse trust packets correctly, so for
-       # that version, creation of signed tags using the generated key fails.
-       case "$(gpg --version)" in
-       'gpg (GnuPG) 1.0.6'*)
-               say "Skipping signed tag tests, because a bug in 1.0.6 version"
-               ;;
-       *)
-               test_set_prereq GPG
-               ;;
-       esac
-fi
+test_expect_success 'annotations for blobs are empty' '
+       blob=$(git hash-object -w --stdin <<-\EOF
+       Blob paragraph 1.
+
+       Blob paragraph 2.
+       EOF
+       ) &&
+       git tag tag-blob $blob &&
+       echo "tag-blob        " >expect &&
+       git tag -n1 -l tag-blob >actual &&
+       test_cmp expect actual
+'
 
 # trying to verify annotated non-signed tags:
 
@@ -625,16 +621,6 @@ test_expect_success GPG \
 
 # creating and verifying signed tags:
 
-# key generation info: gpg --homedir t/t7004 --gen-key
-# Type DSA and Elgamal, size 2048 bits, no expiration date.
-# Name and email: C O Mitter <committer@example.com>
-# No password given, to enable non-interactive operation.
-
-cp -R "$TEST_DIRECTORY"/t7004 ./gpghome
-chmod 0700 gpghome
-GNUPGHOME="$(pwd)/gpghome"
-export GNUPGHOME
-
 get_tag_header signed-tag $commit commit $time >expect
 echo 'A signed tag message' >>expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
diff --git a/t/t7004/pubring.gpg b/t/t7004/pubring.gpg
deleted file mode 100644 (file)
index 83855fa..0000000
Binary files a/t/t7004/pubring.gpg and /dev/null differ
diff --git a/t/t7004/random_seed b/t/t7004/random_seed
deleted file mode 100644 (file)
index 8fed133..0000000
Binary files a/t/t7004/random_seed and /dev/null differ
diff --git a/t/t7004/secring.gpg b/t/t7004/secring.gpg
deleted file mode 100644 (file)
index d831cd9..0000000
Binary files a/t/t7004/secring.gpg and /dev/null differ
diff --git a/t/t7004/trustdb.gpg b/t/t7004/trustdb.gpg
deleted file mode 100644 (file)
index abace96..0000000
Binary files a/t/t7004/trustdb.gpg and /dev/null differ
index 320e1d1dbe62c81e29186d7a32b73447d2f998b8..ff2590849de960cf6ec751f83904f1792862f626 100755 (executable)
@@ -6,11 +6,6 @@ test_description='Test automatic use of a pager.'
 . "$TEST_DIRECTORY"/lib-pager.sh
 . "$TEST_DIRECTORY"/lib-terminal.sh
 
-cleanup_fail() {
-       echo >&2 cleanup failed
-       (exit 1)
-}
-
 test_expect_success 'setup' '
        sane_unset GIT_PAGER GIT_PAGER_IN_USE &&
        test_unconfig core.pager &&
@@ -22,9 +17,7 @@ test_expect_success 'setup' '
 '
 
 test_expect_success TTY 'some commands use a pager' '
-       rm -f paginated.out ||
-       cleanup_fail &&
-
+       rm -f paginated.out &&
        test_terminal git log &&
        test -e paginated.out
 '
@@ -45,49 +38,37 @@ test_expect_failure TTY 'pager runs from subdir' '
 '
 
 test_expect_success TTY 'some commands do not use a pager' '
-       rm -f paginated.out ||
-       cleanup_fail &&
-
+       rm -f paginated.out &&
        test_terminal git rev-list HEAD &&
        ! test -e paginated.out
 '
 
 test_expect_success 'no pager when stdout is a pipe' '
-       rm -f paginated.out ||
-       cleanup_fail &&
-
+       rm -f paginated.out &&
        git log | cat &&
        ! test -e paginated.out
 '
 
 test_expect_success 'no pager when stdout is a regular file' '
-       rm -f paginated.out ||
-       cleanup_fail &&
-
+       rm -f paginated.out &&
        git log >file &&
        ! test -e paginated.out
 '
 
 test_expect_success TTY 'git --paginate rev-list uses a pager' '
-       rm -f paginated.out ||
-       cleanup_fail &&
-
+       rm -f paginated.out &&
        test_terminal git --paginate rev-list HEAD &&
        test -e paginated.out
 '
 
 test_expect_success 'no pager even with --paginate when stdout is a pipe' '
-       rm -f file paginated.out ||
-       cleanup_fail &&
-
+       rm -f file paginated.out &&
        git --paginate log | cat &&
        ! test -e paginated.out
 '
 
 test_expect_success TTY 'no pager with --no-pager' '
-       rm -f paginated.out ||
-       cleanup_fail &&
-
+       rm -f paginated.out &&
        test_terminal git --no-pager log &&
        ! test -e paginated.out
 '
@@ -136,9 +117,7 @@ colorful() {
 }
 
 test_expect_success 'tests can detect color' '
-       rm -f colorful.log colorless.log ||
-       cleanup_fail &&
-
+       rm -f colorful.log colorless.log &&
        git log --no-color >colorless.log &&
        git log --color >colorful.log &&
        ! colorful colorless.log &&
@@ -147,18 +126,14 @@ test_expect_success 'tests can detect color' '
 
 test_expect_success 'no color when stdout is a regular file' '
        rm -f colorless.log &&
-       test_config color.ui auto ||
-       cleanup_fail &&
-
+       test_config color.ui auto &&
        git log >colorless.log &&
        ! colorful colorless.log
 '
 
 test_expect_success TTY 'color when writing to a pager' '
        rm -f paginated.out &&
-       test_config color.ui auto ||
-       cleanup_fail &&
-
+       test_config color.ui auto &&
        (
                TERM=vt100 &&
                export TERM &&
@@ -181,9 +156,7 @@ test_expect_success TTY 'colors are suppressed by color.pager' '
 
 test_expect_success 'color when writing to a file intended for a pager' '
        rm -f colorful.log &&
-       test_config color.ui auto ||
-       cleanup_fail &&
-
+       test_config color.ui auto &&
        (
                TERM=vt100 &&
                GIT_PAGER_IN_USE=true &&
@@ -242,9 +215,7 @@ test_default_pager() {
        $test_expectation SIMPLEPAGER,TTY "$cmd - default pager is used by default" "
                sane_unset PAGER GIT_PAGER &&
                test_unconfig core.pager &&
-               rm -f default_pager_used ||
-               cleanup_fail &&
-
+               rm -f default_pager_used &&
                cat >\$less <<-\EOF &&
                #!/bin/sh
                wc >default_pager_used
@@ -265,9 +236,7 @@ test_PAGER_overrides() {
        $test_expectation TTY "$cmd - PAGER overrides default pager" "
                sane_unset GIT_PAGER &&
                test_unconfig core.pager &&
-               rm -f PAGER_used ||
-               cleanup_fail &&
-
+               rm -f PAGER_used &&
                PAGER='wc >PAGER_used' &&
                export PAGER &&
                $full_command &&
@@ -292,9 +261,7 @@ test_core_pager() {
 
        $test_expectation TTY "$cmd - repository-local core.pager setting $used_if_wanted" "
                sane_unset GIT_PAGER &&
-               rm -f core.pager_used ||
-               cleanup_fail &&
-
+               rm -f core.pager_used &&
                PAGER=wc &&
                export PAGER &&
                test_config core.pager 'wc >core.pager_used' &&
@@ -321,9 +288,7 @@ test_pager_subdir_helper() {
        $test_expectation TTY "$cmd - core.pager $used_if_wanted from subdirectory" "
                sane_unset GIT_PAGER &&
                rm -f core.pager_used &&
-               rm -fr sub ||
-               cleanup_fail &&
-
+               rm -fr sub &&
                PAGER=wc &&
                stampname=\$(pwd)/core.pager_used &&
                export PAGER stampname &&
@@ -341,9 +306,7 @@ test_GIT_PAGER_overrides() {
        parse_args "$@"
 
        $test_expectation TTY "$cmd - GIT_PAGER overrides core.pager" "
-               rm -f GIT_PAGER_used ||
-               cleanup_fail &&
-
+               rm -f GIT_PAGER_used &&
                test_config core.pager wc &&
                GIT_PAGER='wc >GIT_PAGER_used' &&
                export GIT_PAGER &&
@@ -356,9 +319,7 @@ test_doesnt_paginate() {
        parse_args "$@"
 
        $test_expectation TTY "no pager for '$cmd'" "
-               rm -f GIT_PAGER_used ||
-               cleanup_fail &&
-
+               rm -f GIT_PAGER_used &&
                GIT_PAGER='wc >GIT_PAGER_used' &&
                export GIT_PAGER &&
                $full_command &&
index 917a264eea6c6b3593e9f19caadbef715daace20..fd6410fc7149ed381d4d09120089859281e40696 100755 (executable)
@@ -99,4 +99,28 @@ test_expect_success 'git grep y<NUL>x a' "
        test_must_fail git grep -f f a
 "
 
+test_expect_success 'grep respects binary diff attribute' '
+       echo text >t &&
+       git add t &&
+       echo t:text >expect &&
+       git grep text t >actual &&
+       test_cmp expect actual &&
+       echo "t -diff" >.gitattributes &&
+       echo "Binary file t matches" >expect &&
+       git grep text t >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'grep respects not-binary diff attribute' '
+       echo binQary | q_to_nul >b &&
+       git add b &&
+       echo "Binary file b matches" >expect &&
+       git grep bin b >actual &&
+       test_cmp expect actual &&
+       echo "b diff" >.gitattributes &&
+       echo "b:binQary" >expect &&
+       git grep bin b | nul_to_q >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 69115269c2a284dcfc99abd2f7feb3d87f4e5bcc..695f7afdf31cc589e9c31d764d1e713ad64240c0 100755 (executable)
@@ -362,10 +362,10 @@ test_expect_success 'update --init' '
        git submodule update init > update.out &&
        cat update.out &&
        test_i18ngrep "not initialized" update.out &&
-       ! test -d init/.git &&
+       test_must_fail git rev-parse --resolve-git-dir init/.git &&
 
        git submodule update --init init &&
-       test -d init/.git
+       git rev-parse --resolve-git-dir init/.git
 '
 
 test_expect_success 'do not add files from a submodule' '
index 95ffe349a769a84d2eec850d376f11bdfcf2b56f..3620215c1f84b8e184b470c92b9a01c15fd75dc2 100755 (executable)
@@ -56,8 +56,9 @@ test_expect_success '"git submodule sync" should update submodule URLs' '
         git pull --no-recurse-submodules &&
         git submodule sync
        ) &&
-       test -d "$(git config -f super-clone/submodule/.git/config \
-                               remote.origin.url)" &&
+       test -d "$(cd super-clone/submodule &&
+        git config remote.origin.url
+       )" &&
        (cd super-clone/submodule &&
         git checkout master &&
         git pull
index a8fb30b7921dd17f910d48e82fbb2374fcb45ac3..0d5b42a25bbe79d3fb7b09ce4bf5107ee55928e5 100755 (executable)
@@ -228,4 +228,55 @@ test_expect_success 'merging with a modify/modify conflict between merge bases'
        git merge d
 '
 
+# canonical criss-cross history in top and submodule
+test_expect_success 'setup for recursive merge with submodule' '
+       mkdir merge-recursive &&
+       (cd merge-recursive &&
+        git init &&
+        mkdir sub &&
+        (cd sub &&
+         git init &&
+         test_commit a &&
+         git checkout -b sub-b master &&
+         test_commit b &&
+         git checkout -b sub-c master &&
+         test_commit c &&
+         git checkout -b sub-bc sub-b &&
+         git merge sub-c &&
+         git checkout -b sub-cb sub-c &&
+         git merge sub-b &&
+         git checkout master) &&
+        git add sub &&
+        git commit -m a &&
+        git checkout -b top-b master &&
+        (cd sub && git checkout sub-b) &&
+        git add sub &&
+        git commit -m b &&
+        git checkout -b top-c master &&
+        (cd sub && git checkout sub-c) &&
+        git add sub &&
+        git commit -m c &&
+        git checkout -b top-bc top-b &&
+        git merge -s ours --no-commit top-c &&
+        (cd sub && git checkout sub-bc) &&
+        git add sub &&
+        git commit -m bc &&
+        git checkout -b top-cb top-c &&
+        git merge -s ours --no-commit top-b &&
+        (cd sub && git checkout sub-cb) &&
+        git add sub &&
+        git commit -m cb)
+'
+
+# merge should leave submodule unmerged in index
+test_expect_success 'recursive merge with submodule' '
+       (cd merge-recursive &&
+        test_must_fail git merge top-bc &&
+        echo "160000 $(git rev-parse top-cb:sub) 2     sub" > expect2 &&
+        echo "160000 $(git rev-parse top-bc:sub) 3     sub" > expect3 &&
+        git ls-files -u > actual &&
+        grep "$(cat expect2)" actual > /dev/null &&
+        grep "$(cat expect3)" actual > /dev/null)
+'
+
 test_done
index c679f36a4daeab3f114c9cf0c84c93549d28c680..5b97222c48a15dff018cb814ca14c59ed0ee91a2 100755 (executable)
@@ -30,6 +30,7 @@ test_expect_success 'setup a submodule tree' '
        git clone super submodule &&
        git clone super rebasing &&
        git clone super merging &&
+       git clone super none &&
        (cd super &&
         git submodule add ../submodule submodule &&
         test_tick &&
@@ -58,6 +59,11 @@ test_expect_success 'setup a submodule tree' '
         test_tick &&
         git commit -m "rebasing"
        )
+       (cd super &&
+        git submodule add ../none none &&
+        test_tick &&
+        git commit -m "none"
+       )
 '
 
 test_expect_success 'submodule update detaching the HEAD ' '
@@ -298,6 +304,62 @@ test_expect_success 'submodule update ignores update=rebase config for new submo
        )
 '
 
+test_expect_success 'submodule init picks up update=none' '
+       (cd super &&
+        git config -f .gitmodules submodule.none.update none &&
+        git submodule init none &&
+        test "none" = "$(git config submodule.none.update)"
+       )
+'
+
+test_expect_success 'submodule update - update=none in .git/config' '
+       (cd super &&
+        git config submodule.submodule.update none &&
+        (cd submodule &&
+         git checkout master &&
+         compare_head
+        ) &&
+        git diff --raw | grep "        submodule" &&
+        git submodule update &&
+        git diff --raw | grep "        submodule" &&
+        (cd submodule &&
+         compare_head
+        ) &&
+        git config --unset submodule.submodule.update &&
+        git submodule update submodule
+       )
+'
+
+test_expect_success 'submodule update - update=none in .git/config but --checkout given' '
+       (cd super &&
+        git config submodule.submodule.update none &&
+        (cd submodule &&
+         git checkout master &&
+         compare_head
+        ) &&
+        git diff --raw | grep "        submodule" &&
+        git submodule update --checkout &&
+        test_must_fail git diff --raw \| grep "        submodule" &&
+        (cd submodule &&
+         test_must_fail compare_head
+        ) &&
+        git config --unset submodule.submodule.update
+       )
+'
+
+test_expect_success 'submodule update --init skips submodule with update=none' '
+       (cd super &&
+        git add .gitmodules &&
+        git commit -m ".gitmodules"
+       ) &&
+       git clone super cloned &&
+       (cd cloned &&
+        git submodule update --init &&
+        test -e submodule/.git &&
+        test_must_fail test -e none/.git
+       )
+'
+
 test_expect_success 'submodule update continues after checkout error' '
        (cd super &&
         git reset --hard HEAD &&
@@ -408,6 +470,7 @@ test_expect_success 'submodule update exit immediately in case of merge conflict
         test_cmp expect actual
        )
 '
+
 test_expect_success 'submodule update exit immediately after recursive rebase error' '
        (cd super &&
         git checkout master &&
@@ -442,4 +505,118 @@ test_expect_success 'submodule update exit immediately after recursive rebase er
         test_cmp expect actual
        )
 '
+
+test_expect_success 'add different submodules to the same path' '
+       (cd super &&
+        git submodule add ../submodule s1 &&
+        test_must_fail git submodule add ../merging s1
+       )
+'
+
+test_expect_success 'submodule add places git-dir in superprojects git-dir' '
+       (cd super &&
+        mkdir deeper &&
+        git submodule add ../submodule deeper/submodule &&
+        (cd deeper/submodule &&
+         git log > ../../expected
+        ) &&
+        (cd .git/modules/deeper/submodule &&
+         git log > ../../../../actual
+        ) &&
+        test_cmp actual expected
+       )
+'
+
+test_expect_success 'submodule update places git-dir in superprojects git-dir' '
+       (cd super &&
+        git commit -m "added submodule"
+       ) &&
+       git clone super super2 &&
+       (cd super2 &&
+        git submodule init deeper/submodule &&
+        git submodule update &&
+        (cd deeper/submodule &&
+         git log > ../../expected
+        ) &&
+        (cd .git/modules/deeper/submodule &&
+         git log > ../../../../actual
+        ) &&
+        test_cmp actual expected
+       )
+'
+
+test_expect_success 'submodule add places git-dir in superprojects git-dir recursive' '
+       (cd super2 &&
+        (cd deeper/submodule &&
+         git submodule add ../submodule subsubmodule &&
+         (cd subsubmodule &&
+          git log > ../../../expected
+         ) &&
+         git commit -m "added subsubmodule" &&
+         git push
+        ) &&
+        (cd .git/modules/deeper/submodule/modules/subsubmodule &&
+         git log > ../../../../../actual
+        ) &&
+        git add deeper/submodule &&
+        git commit -m "update submodule" &&
+        git push &&
+        test_cmp actual expected
+       )
+'
+
+test_expect_success 'submodule update places git-dir in superprojects git-dir recursive' '
+       mkdir super_update_r &&
+       (cd super_update_r &&
+        git init --bare
+       ) &&
+       mkdir subsuper_update_r &&
+       (cd subsuper_update_r &&
+        git init --bare
+       ) &&
+       mkdir subsubsuper_update_r &&
+       (cd subsubsuper_update_r &&
+        git init --bare
+       ) &&
+       git clone subsubsuper_update_r subsubsuper_update_r2 &&
+       (cd subsubsuper_update_r2 &&
+        test_commit "update_subsubsuper" file &&
+        git push origin master
+       ) &&
+       git clone subsuper_update_r subsuper_update_r2 &&
+       (cd subsuper_update_r2 &&
+        test_commit "update_subsuper" file &&
+        git submodule add ../subsubsuper_update_r subsubmodule &&
+        git commit -am "subsubmodule" &&
+        git push origin master
+       ) &&
+       git clone super_update_r super_update_r2 &&
+       (cd super_update_r2 &&
+        test_commit "update_super" file &&
+        git submodule add ../subsuper_update_r submodule &&
+        git commit -am "submodule" &&
+        git push origin master
+       ) &&
+       rm -rf super_update_r2 &&
+       git clone super_update_r super_update_r2 &&
+       (cd super_update_r2 &&
+        git submodule update --init --recursive &&
+        (cd submodule/subsubmodule &&
+         git log > ../../expected
+        ) &&
+        (cd .git/modules/submodule/modules/subsubmodule
+         git log > ../../../../../actual
+        )
+        test_cmp actual expected
+       )
+'
+
+test_expect_success 'submodule add properly re-creates deeper level submodules' '
+       (cd super &&
+        git reset --hard master &&
+        rm -rf deeper/ &&
+        git submodule add ../submodule deeper/submodule
+       )
+'
+
 test_done
index be745fb23f3f27ed319143d3d3ab3f26d01857bc..9b69fe2e1470e406b005e629a6f7759f8e564ddf 100755 (executable)
@@ -118,19 +118,19 @@ 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 &&
+               test_must_fail git rev-parse --resolve-git-dir sub1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub2/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub3/.git &&
+               test_must_fail git rev-parse --resolve-git-dir 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 rev-parse --resolve-git-dir sub1/.git &&
+               git rev-parse --resolve-git-dir sub2/.git &&
+               git rev-parse --resolve-git-dir sub3/.git &&
+               git rev-parse --resolve-git-dir nested1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir nested1/nested2/.git &&
                git submodule foreach "git submodule update --init" &&
-               test -d nested1/nested2/.git &&
-               test ! -d nested1/nested2/nested3/.git
+               git rev-parse --resolve-git-dir nested1/nested1/nested2/.git
+               test_must_fail git rev-parse --resolve-git-dir nested1/nested2/nested3/.git
        )
 '
 
@@ -138,8 +138,8 @@ 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
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git
        )
 '
 
@@ -183,18 +183,18 @@ 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 &&
+               test_must_fail git rev-parse --resolve-git-dir sub1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub2/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub3/.git &&
+               test_must_fail git rev-parse --resolve-git-dir 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
+               git rev-parse --resolve-git-dir sub1/.git &&
+               git rev-parse --resolve-git-dir sub2/.git &&
+               git rev-parse --resolve-git-dir sub3/.git &&
+               git rev-parse --resolve-git-dir nested1/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git
        )
 '
 
@@ -247,14 +247,17 @@ test_expect_success 'ensure "status --cached --recursive" preserves the --cached
 
 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
+       (
+               cd clone4 &&
+               git rev-parse --resolve-git-dir .git &&
+               git rev-parse --resolve-git-dir sub1/.git &&
+               git rev-parse --resolve-git-dir sub2/.git &&
+               git rev-parse --resolve-git-dir sub3/.git &&
+               git rev-parse --resolve-git-dir nested1/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git
+       )
 '
 
 test_expect_success 'test "update --recursive" with a flag with spaces' '
@@ -262,14 +265,14 @@ test_expect_success 'test "update --recursive" with a flag with spaces' '
        git clone super clone5 &&
        (
                cd clone5 &&
-               test ! -d nested1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir d nested1/.git &&
                git submodule update --init --recursive --reference="$(dirname "$PWD")/common objects" &&
-               test -d nested1/.git &&
-               test -d nested1/nested2/.git &&
-               test -d nested1/nested2/nested3/.git &&
-               test -f nested1/.git/objects/info/alternates &&
-               test -f nested1/nested2/.git/objects/info/alternates &&
-               test -f nested1/nested2/nested3/.git/objects/info/alternates
+               git rev-parse --resolve-git-dir nested1/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+               test -f .git/modules/nested1/objects/info/alternates &&
+               test -f .git/modules/nested1/modules/nested2/objects/info/alternates &&
+               test -f .git/modules/nested1/modules/nested2/modules/nested3/objects/info/alternates
        )
 '
 
@@ -277,18 +280,18 @@ test_expect_success 'use "update --recursive nested1" to checkout all submodules
        git clone super clone6 &&
        (
                cd clone6 &&
-               test ! -d sub1/.git &&
-               test ! -d sub2/.git &&
-               test ! -d sub3/.git &&
-               test ! -d nested1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub2/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub3/.git &&
+               test_must_fail git rev-parse --resolve-git-dir nested1/.git &&
                git submodule update --init --recursive -- nested1 &&
-               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
+               test_must_fail git rev-parse --resolve-git-dir sub1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub2/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub3/.git &&
+               git rev-parse --resolve-git-dir nested1/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git
        )
 '
 
index cc16d3f05de7ec51e8a071253654a6698f8f33c4..ab37c368d071236d0a2851417e85d2a216c7f4fc 100755 (executable)
@@ -43,7 +43,7 @@ git commit -m B-super-added'
 cd "$base_dir"
 
 test_expect_success 'after add: existence of info/alternates' \
-'test `wc -l <super/sub/.git/objects/info/alternates` = 1'
+'test `wc -l <super/.git/modules/sub/objects/info/alternates` = 1'
 
 cd "$base_dir"
 
@@ -66,7 +66,7 @@ test_expect_success 'update with reference' \
 cd "$base_dir"
 
 test_expect_success 'after update: existence of info/alternates' \
-'test `wc -l <super-clone/sub/.git/objects/info/alternates` = 1'
+'test `wc -l <super-clone/.git/modules/sub/objects/info/alternates` = 1'
 
 cd "$base_dir"
 
index 3ad04363b5920497617f806a18a3a5a0083ac1b9..8bb38337a9796142bc091c2b108f7f9e79b0f377 100755 (executable)
@@ -8,39 +8,39 @@
 
 test_description='git commit'
 . ./test-lib.sh
+. "$TEST_DIRECTORY/diff-lib.sh"
 
-test_tick
+author='The Real Author <someguy@his.email.org>'
 
-test_expect_success \
-       "initial status" \
-       "echo 'bongo bongo' >file &&
-        git add file"
+test_tick
 
-test_expect_success "Constructing initial commit" '
+test_expect_success 'initial status' '
+       echo bongo bongo >file &&
+       git add file &&
        git status >actual &&
        test_i18ngrep "Initial commit" actual
 '
 
-test_expect_success \
-       "fail initial amend" \
-       "test_must_fail git commit --amend"
+test_expect_success 'fail initial amend' '
+       test_must_fail git commit --amend
+'
 
-test_expect_success \
-       "initial commit" \
-       "git commit -m initial"
+test_expect_success 'setup: initial commit' '
+       git commit -m initial
+'
 
-test_expect_success \
-       "invalid options 1" \
-       "test_must_fail git commit -m foo -m bar -F file"
+test_expect_success '-m and -F do not mix' '
+       test_must_fail git commit -m foo -m bar -F file
+'
 
-test_expect_success \
-       "invalid options 2" \
-       "test_must_fail git commit -C HEAD -m illegal"
+test_expect_success '-m and -C do not mix' '
+       test_must_fail git commit -C HEAD -m illegal
+'
 
-test_expect_success \
-       "using paths with -a" \
-       "echo King of the bongo >file &&
-       test_must_fail git commit -m foo -a file"
+test_expect_success 'paths and -a do not mix' '
+       echo King of the bongo >file &&
+       test_must_fail git commit -m foo -a file
+'
 
 test_expect_success PERL 'can use paths with --interactive' '
        echo bong-o-bong >file &&
@@ -50,139 +50,163 @@ test_expect_success PERL 'can use paths with --interactive' '
        git reset --hard HEAD^
 '
 
-test_expect_success \
-       "using invalid commit with -C" \
-       "test_must_fail git commit -C bogus"
+test_expect_success 'using invalid commit with -C' '
+       test_must_fail git commit -C bogus
+'
 
-test_expect_success \
-       "testing nothing to commit" \
-       "test_must_fail git commit -m initial"
+test_expect_success 'nothing to commit' '
+       test_must_fail git commit -m initial
+'
 
-test_expect_success \
-       "next commit" \
-       "echo 'bongo bongo bongo' >file \
-        git commit -m next -a"
+test_expect_success 'setup: non-initial commit' '
+       echo bongo bongo bongo >file &&
+       git commit -m next -a
+'
 
-test_expect_success \
-       "commit message from non-existing file" \
-       "echo 'more bongo: bongo bongo bongo bongo' >file && \
-        test_must_fail git commit -F gah -a"
+test_expect_success 'commit message from non-existing file' '
+       echo more bongo: bongo bongo bongo bongo >file &&
+       test_must_fail git commit -F gah -a
+'
 
-# Empty except stray tabs and spaces on a few lines.
-sed -e 's/@$//' >msg <<EOF
-               @
+test_expect_success 'empty commit message' '
+       # Empty except stray tabs and spaces on a few lines.
+       sed -e "s/@//g" >msg <<-\EOF &&
+               @               @
+               @@
+               @  @
+               @Signed-off-by: hula@
+       EOF
+       test_must_fail git commit -F msg -a
+'
 
-  @
-Signed-off-by: hula
-EOF
-test_expect_success \
-       "empty commit message" \
-       "test_must_fail git commit -F msg -a"
+test_expect_success 'setup: commit message from file' '
+       echo this is the commit message, coming from a file >msg &&
+       git commit -F msg -a
+'
 
-test_expect_success \
-       "commit message from file" \
-       "echo 'this is the commit message, coming from a file' >msg && \
-        git commit -F msg -a"
+test_expect_success 'amend commit' '
+       cat >editor <<-\EOF &&
+       #!/bin/sh
+       sed -e "s/a file/an amend commit/g" < "$1" > "$1-"
+       mv "$1-" "$1"
+       EOF
+       chmod 755 editor &&
+       EDITOR=./editor git commit --amend
+'
 
-cat >editor <<\EOF
-#!/bin/sh
-sed -e "s/a file/an amend commit/g" < "$1" > "$1-"
-mv "$1-" "$1"
-EOF
-chmod 755 editor
+test_expect_success 'set up editor' '
+       cat >editor <<-\EOF &&
+       #!/bin/sh
+       sed -e "s/unamended/amended/g" <"$1" >"$1-"
+       mv "$1-" "$1"
+       EOF
+       chmod 755 editor
+'
 
-test_expect_success \
-       "amend commit" \
-       "EDITOR=./editor git commit --amend"
+test_expect_success 'amend without launching editor' '
+       echo unamended >expect &&
+       git commit --allow-empty -m "unamended" &&
+       echo needs more bongo >file &&
+       git add file &&
+       EDITOR=./editor git commit --no-edit --amend &&
+       git diff --exit-code HEAD -- file &&
+       git diff-tree -s --format=%s HEAD >msg &&
+       test_cmp expect msg
+'
 
-test_expect_success \
-       "passing -m and -F" \
-       "echo 'enough with the bongos' >file && \
-        test_must_fail git commit -F msg -m amending ."
+test_expect_success '--amend --edit' '
+       echo amended >expect &&
+       git commit --allow-empty -m "unamended" &&
+       echo bongo again >file &&
+       git add file &&
+       EDITOR=./editor git commit --edit --amend &&
+       git diff-tree -s --format=%s HEAD >msg &&
+       test_cmp expect msg
+'
 
-test_expect_success \
-       "using message from other commit" \
-       "git commit -C HEAD^ ."
+test_expect_success '-m --edit' '
+       echo amended >expect &&
+       git commit --allow-empty -m buffer &&
+       echo bongo bongo >file &&
+       git add file &&
+       EDITOR=./editor git commit -m unamended --edit &&
+       git diff-tree -s  --format=%s HEAD >msg &&
+       test_cmp expect msg
+'
 
-cat >editor <<\EOF
-#!/bin/sh
-sed -e "s/amend/older/g"  < "$1" > "$1-"
-mv "$1-" "$1"
-EOF
-chmod 755 editor
-
-test_expect_success \
-       "editing message from other commit" \
-       "echo 'hula hula' >file && \
-        EDITOR=./editor git commit -c HEAD^ -a"
-
-test_expect_success \
-       "message from stdin" \
-       "echo 'silly new contents' >file && \
-        echo commit message from stdin | git commit -F - -a"
-
-test_expect_success \
-       "overriding author from command line" \
-       "echo 'gak' >file && \
-        git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a >output 2>&1"
-
-test_expect_success \
-       "commit --author output mentions author" \
-       "grep Rubber.Duck output"
-
-test_expect_success PERL \
-       "interactive add" \
-       "echo 7 | git commit --interactive | grep 'What now'"
-
-test_expect_success PERL \
-       "commit --interactive doesn't change index if editor aborts" \
-       "echo zoo >file &&
+test_expect_success '-m and -F do not mix' '
+       echo enough with the bongos >file &&
+       test_must_fail git commit -F msg -m amending .
+'
+
+test_expect_success 'using message from other commit' '
+       git commit -C HEAD^ .
+'
+
+test_expect_success 'editing message from other commit' '
+       cat >editor <<-\EOF &&
+       #!/bin/sh
+       sed -e "s/amend/older/g"  < "$1" > "$1-"
+       mv "$1-" "$1"
+       EOF
+       chmod 755 editor &&
+       echo hula hula >file &&
+       EDITOR=./editor git commit -c HEAD^ -a
+'
+
+test_expect_success 'message from stdin' '
+       echo silly new contents >file &&
+       echo commit message from stdin |
+       git commit -F - -a
+'
+
+test_expect_success 'overriding author from command line' '
+       echo gak >file &&
+       git commit -m author \
+               --author "Rubber Duck <rduck@convoy.org>" -a >output 2>&1 &&
+       grep Rubber.Duck output
+'
+
+test_expect_success PERL 'interactive add' '
+       echo 7 |
+       git commit --interactive |
+       grep "What now"
+'
+
+test_expect_success PERL "commit --interactive doesn't change index if editor aborts" '
+       echo zoo >file &&
        test_must_fail git diff --exit-code >diff1 &&
-       (echo u ; echo '*' ; echo q) |
-       (EDITOR=: && export EDITOR &&
-        test_must_fail git commit --interactive) &&
+       (echo u ; echo "*" ; echo q) |
+       (
+               EDITOR=: &&
+               export EDITOR &&
+               test_must_fail git commit --interactive
+       ) &&
        git diff >diff2 &&
-       test_cmp diff1 diff2"
-
-test_expect_success \
-       "showing committed revisions" \
-       "git rev-list HEAD >current"
+       compare_diff_patch diff1 diff2
+'
 
-cat >editor <<\EOF
-#!/bin/sh
-sed -e "s/good/bad/g" < "$1" > "$1-"
-mv "$1-" "$1"
-EOF
-chmod 755 editor
-
-cat >msg <<EOF
-A good commit message.
-EOF
-
-test_expect_success \
-       'editor not invoked if -F is given' '
-        echo "moo" >file &&
-        EDITOR=./editor git commit -a -F msg &&
-        git show -s --pretty=format:"%s" | grep -q good &&
-        echo "quack" >file &&
-        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
-# easier to isolate bugs.
-
-cat >expected <<\EOF
-72c0dc9855b0c9dadcbfd5a31cab072e0cb774ca
-9b88fc14ce6b32e3d9ee021531a54f18a5cf38a2
-3536bbb352c3a1ef9a420f5b4242d48578b92aa7
-d381ac431806e53f3dd7ac2f1ae0534f36d738b9
-4fd44095ad6334f3ef72e4c5ec8ddf108174b54a
-402702b49136e7587daa9280e91e4bb7cb2179f7
-EOF
-
-test_expect_success \
-    'validate git rev-list output.' \
-    'test_cmp expected current'
+test_expect_success 'editor not invoked if -F is given' '
+       cat >editor <<-\EOF &&
+       #!/bin/sh
+       sed -e s/good/bad/g <"$1" >"$1-"
+       mv "$1-" "$1"
+       EOF
+       chmod 755 editor &&
+
+       echo A good commit message. >msg &&
+       echo moo >file &&
+
+       EDITOR=./editor git commit -a -F msg &&
+       git show -s --pretty=format:%s >subject &&
+       grep -q good subject &&
+
+       echo quack >file &&
+       echo Another good message. |
+       EDITOR=./editor git commit -a -F - &&
+       git show -s --pretty=format:%s >subject &&
+       grep -q good subject
+'
 
 test_expect_success 'partial commit that involves removal (1)' '
 
@@ -216,7 +240,6 @@ test_expect_success 'partial commit that involves removal (3)' '
 
 '
 
-author="The Real Author <someguy@his.email.org>"
 test_expect_success 'amend commit to fix author' '
 
        oldtick=$GIT_AUTHOR_DATE &&
@@ -345,7 +368,6 @@ test_expect_success 'multiple -m' '
 
 '
 
-author="The Real Author <someguy@his.email.org>"
 test_expect_success 'amend commit to fix author' '
 
        oldtick=$GIT_AUTHOR_DATE &&
@@ -372,15 +394,8 @@ test_expect_success 'git commit <file> with dirty index' '
 
 test_expect_success 'same tree (single parent)' '
 
-       git reset --hard
-
-       if git commit -m empty
-       then
-               echo oops -- should have complained
-               false
-       else
-               : happy
-       fi
+       git reset --hard &&
+       test_must_fail git commit -m empty
 
 '
 
index 905255adf0ca5b15d9befa772cda4a650bd15f34..fc57b135c50e34ab86c04d0a0ec81052ce40f8ff 100755 (executable)
@@ -189,7 +189,7 @@ test_expect_success 'status with gitignore' '
        #       untracked
        EOF
        git status --ignored >output &&
-       test_cmp expect output
+       test_i18ncmp expect output
 '
 
 test_expect_success 'status with gitignore (nothing untracked)' '
@@ -247,7 +247,7 @@ test_expect_success 'status with gitignore (nothing untracked)' '
        #       untracked
        EOF
        git status --ignored >output &&
-       test_cmp expect output
+       test_i18ncmp expect output
 '
 
 rm -f .gitignore
diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh
new file mode 100755 (executable)
index 0000000..1d3c56f
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/sh
+
+test_description='signed commit tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+test_expect_success GPG 'create signed commits' '
+       echo 1 >file && git add file &&
+       test_tick && git commit -S -m initial &&
+       git tag initial &&
+       git branch side &&
+
+       echo 2 >file && test_tick && git commit -a -S -m second &&
+       git tag second &&
+
+       git checkout side &&
+       echo 3 >elif && git add elif &&
+       test_tick && git commit -m "third on side" &&
+
+       git checkout master &&
+       test_tick && git merge -S side &&
+       git tag merge &&
+
+       echo 4 >file && test_tick && git commit -a -m "fourth unsigned" &&
+       git tag fourth-unsigned &&
+
+       test_tick && git commit --amend -S -m "fourth signed" &&
+       git tag fourth-signed
+'
+
+test_expect_success GPG 'show signatures' '
+       (
+               for commit in initial second merge master
+               do
+                       git show --pretty=short --show-signature $commit >actual &&
+                       grep "Good signature from" actual || exit 1
+                       ! grep "BAD signature from" actual || exit 1
+                       echo $commit OK
+               done
+       ) &&
+       (
+               for commit in merge^2 fourth-unsigned
+               do
+                       git show --pretty=short --show-signature $commit >actual &&
+                       grep "Good signature from" actual && exit 1
+                       ! grep "BAD signature from" actual || exit 1
+                       echo $commit OK
+               done
+       )
+'
+
+test_expect_success GPG 'detect fudged signature' '
+       git cat-file commit master >raw &&
+
+       sed -e "s/fourth signed/4th forged/" raw >forged1 &&
+       git hash-object -w -t commit forged1 >forged1.commit &&
+       git show --pretty=short --show-signature $(cat forged1.commit) >actual1 &&
+       grep "BAD signature from" actual1 &&
+       ! grep "Good signature from" actual1
+'
+
+test_expect_success GPG 'detect fudged signature with NUL' '
+       git cat-file commit master >raw &&
+       cat raw >forged2 &&
+       echo Qwik | tr "Q" "\000" >>forged2 &&
+       git hash-object -w -t commit forged2 >forged2.commit &&
+       git show --pretty=short --show-signature $(cat forged2.commit) >actual2 &&
+       grep "BAD signature from" actual2 &&
+       ! grep "Good signature from" actual2
+'
+
+test_expect_success GPG 'amending already signed commit' '
+       git checkout fourth-signed^0 &&
+       git commit --amend -S --no-edit &&
+       git show -s --show-signature HEAD >actual &&
+       grep "Good signature from" actual &&
+       ! grep "BAD signature from" actual
+'
+
+test_done
index 87aac835a1b864eab083b25940892b93af491326..9e27bbf902e275671cb80310630a8bedcaab353c 100755 (executable)
@@ -27,6 +27,7 @@ Testing basic merge operations/option parsing.
 '
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
 
 printf '%s\n' 1 2 3 4 5 6 7 8 9 >file
 printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >file.1
@@ -38,8 +39,8 @@ printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9
 >empty
 
 create_merge_msgs () {
-       echo "Merge commit 'c2'" >msg.1-5 &&
-       echo "Merge commit 'c2'; commit 'c3'" >msg.1-5-9 &&
+       echo "Merge tag 'c2'" >msg.1-5 &&
+       echo "Merge tags 'c2' and 'c3'" >msg.1-5-9 &&
        {
                echo "Squashed commit of the following:" &&
                echo &&
@@ -57,7 +58,7 @@ create_merge_msgs () {
        } >squash.1-5-9 &&
        echo >msg.nolog &&
        {
-               echo "* commit 'c3':" &&
+               echo "* tag 'c3':" &&
                echo "  commit 3" &&
                echo
        } >msg.log
@@ -96,7 +97,11 @@ verify_parents () {
 
 verify_mergeheads () {
        printf '%s\n' "$@" >mergehead.expected &&
-       test_cmp mergehead.expected .git/MERGE_HEAD
+       while read sha1 rest
+       do
+               git rev-parse $sha1
+       done <.git/MERGE_HEAD >mergehead.actual &&
+       test_cmp mergehead.expected mergehead.actual
 }
 
 verify_no_mergehead () {
@@ -643,4 +648,51 @@ test_expect_success 'amending no-ff merge commit' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
+cat >editor <<\EOF
+#!/bin/sh
+# Add a new message string that was not in the template
+(
+       echo "Merge work done on the side branch c1"
+       echo
+       cat <"$1"
+) >"$1.tmp" && mv "$1.tmp" "$1"
+# strip comments and blank lines from end of message
+sed -e '/^#/d' < "$1" | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' > expected
+EOF
+chmod 755 editor
+
+test_expect_success 'merge --no-ff --edit' '
+       git reset --hard c0 &&
+       EDITOR=./editor git merge --no-ff --edit c1 &&
+       verify_parents $c0 $c1 &&
+       git cat-file commit HEAD >raw &&
+       grep "work done on the side branch" raw &&
+       sed "1,/^$/d" >actual raw &&
+       test_cmp actual expected
+'
+
+test_expect_success GPG 'merge --ff-only tag' '
+       git reset --hard c0 &&
+       git commit --allow-empty -m "A newer commit" &&
+       git tag -s -m "A newer commit" signed &&
+       git reset --hard c0 &&
+
+       git merge --ff-only signed &&
+       git rev-parse signed^0 >expect &&
+       git rev-parse HEAD >actual &&
+       test_cmp actual expect
+'
+
+test_expect_success GPG 'merge --no-edit tag should skip editor' '
+       git reset --hard c0 &&
+       git commit --allow-empty -m "A newer commit" &&
+       git tag -f -s -m "A newer commit" signed &&
+       git reset --hard c0 &&
+
+       EDITOR=false git merge --no-edit signed &&
+       git rev-parse signed^0 >expect &&
+       git rev-parse HEAD^2 >actual &&
+       test_cmp actual expect
+'
+
 test_done
index 61f36baa1f3459d68ac30e67680e096093cbe414..5783ebf3ab042d3c78633a89d842c432c96a0d4d 100755 (executable)
@@ -57,7 +57,7 @@ Merge made by the 'octopus' strategy.
  c2.c |    1 +
  c3.c |    1 +
  c4.c |    1 +
- 3 files changed, 3 insertions(+), 0 deletions(-)
+ 3 files changed, 3 insertions(+)
  create mode 100644 c2.c
  create mode 100644 c3.c
  create mode 100644 c4.c
@@ -74,7 +74,7 @@ Already up-to-date with c4
 Trying simple merge with c5
 Merge made by the 'octopus' strategy.
  c5.c |    1 +
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ 1 file changed, 1 insertion(+)
  create mode 100644 c5.c
 EOF
 
@@ -89,7 +89,7 @@ Trying simple merge with c2
 Merge made by the 'octopus' strategy.
  c1.c |    1 +
  c2.c |    1 +
- 2 files changed, 2 insertions(+), 0 deletions(-)
+ 2 files changed, 2 insertions(+)
  create mode 100644 c1.c
  create mode 100644 c2.c
 EOF
index 9114785ef7c850ae2d393bed3565e13f11339ba0..89619cf44608282efa6d75c2ed93cd3bdc4096fc 100755 (executable)
@@ -11,7 +11,7 @@ create_merge_msgs() {
 
        cp exp.subject exp.log &&
        echo >>exp.log "" &&
-       echo >>exp.log "* commit 'c2':" &&
+       echo >>exp.log "* tag 'c2':" &&
        echo >>exp.log "  c2"
 }
 
index 9225fa6f025cd9586b46f30464a69ccb57ca6b2a..8e7e0a5865d762c1cb859154c2d4735b9bf8722b 100755 (executable)
@@ -35,7 +35,7 @@ test_expect_success 'merge tag' '
        git checkout master &&
        test_commit master-3 &&
        git merge tag-1 &&
-       check_oneline "Merge commit Qtag-1Q"
+       check_oneline "Merge tag Qtag-1Q"
 '
 
 test_expect_success 'ambiguous tag' '
@@ -44,7 +44,7 @@ test_expect_success 'ambiguous tag' '
        git checkout master &&
        test_commit master-4 &&
        git merge ambiguous &&
-       check_oneline "Merge commit QambiguousQ"
+       check_oneline "Merge tag QambiguousQ"
 '
 
 test_expect_success 'remote-tracking branch' '
index 4aab2a75b8ef5836a58f30b6c27a8eec4a733afc..f5e16fc7db3f1dd84aff6d86261d10b3b3d00826 100755 (executable)
@@ -39,6 +39,7 @@ test_expect_success 'setup' '
     echo branch1 change >file1 &&
     echo branch1 newfile >file2 &&
     echo branch1 spaced >"spaced name" &&
+    echo branch1 both added >both &&
     echo branch1 change file11 >file11 &&
     echo branch1 change file13 >file13 &&
     echo branch1 sub >subdir/file3 &&
@@ -50,6 +51,7 @@ test_expect_success 'setup' '
        git checkout -b submod-branch1
     ) &&
     git add file1 "spaced name" file11 file13 file2 subdir/file3 submod &&
+    git add both &&
     git rm file12 &&
     git commit -m "branch1 changes" &&
 
@@ -58,6 +60,7 @@ test_expect_success 'setup' '
     echo master updated >file1 &&
     echo master new >file2 &&
     echo master updated spaced >"spaced name" &&
+    echo master both added >both &&
     echo master updated file12 >file12 &&
     echo master updated file14 >file14 &&
     echo master new sub >subdir/file3 &&
@@ -69,18 +72,22 @@ test_expect_success 'setup' '
        git checkout -b submod-master
     ) &&
     git add file1 "spaced name" file12 file14 file2 subdir/file3 submod &&
+    git add both &&
     git rm file11 &&
     git commit -m "master updates" &&
 
     git config merge.tool mytool &&
     git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" &&
-    git config mergetool.mytool.trustExitCode true
+    git config mergetool.mytool.trustExitCode true &&
+    git config mergetool.mybase.cmd "cat \"\$BASE\" >\"\$MERGED\"" &&
+    git config mergetool.mybase.trustExitCode true
 '
 
 test_expect_success 'custom mergetool' '
     git checkout -b test1 branch1 &&
     git submodule update -N &&
     test_must_fail git merge master >/dev/null 2>&1 &&
+    ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool file1 file1 ) &&
     ( yes "" | git mergetool file2 "spaced name" >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
@@ -101,6 +108,7 @@ test_expect_success 'mergetool crlf' '
     ( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool "spaced name" >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
@@ -131,6 +139,7 @@ test_expect_success 'mergetool on file in parent dir' '
        cd subdir &&
        ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) &&
        ( yes "" | git mergetool ../file2 ../spaced\ name >/dev/null 2>&1 ) &&
+       ( yes "" | git mergetool ../both >/dev/null 2>&1 ) &&
        ( yes "d" | git mergetool ../file11 >/dev/null 2>&1 ) &&
        ( yes "d" | git mergetool ../file12 >/dev/null 2>&1 ) &&
        ( yes "l" | git mergetool ../submod >/dev/null 2>&1 ) &&
@@ -212,6 +221,7 @@ test_expect_success 'deleted vs modified submodule' '
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
     ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "r" | git mergetool submod ) &&
     rmdir submod && mv submod-movedaside submod &&
@@ -228,6 +238,7 @@ test_expect_success 'deleted vs modified submodule' '
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
     ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "l" | git mergetool submod ) &&
     test ! -e submod &&
@@ -241,6 +252,7 @@ test_expect_success 'deleted vs modified submodule' '
     test_must_fail git merge test6 &&
     test -n "$(git ls-files -u)" &&
     ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "r" | git mergetool submod ) &&
     test ! -e submod &&
@@ -256,6 +268,7 @@ test_expect_success 'deleted vs modified submodule' '
     test_must_fail git merge test6 &&
     test -n "$(git ls-files -u)" &&
     ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "l" | git mergetool submod ) &&
     test "$(cat submod/bar)" = "master submodule" &&
@@ -279,6 +292,7 @@ test_expect_success 'file vs modified submodule' '
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
     ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "r" | git mergetool submod ) &&
     rmdir submod && mv submod-movedaside submod &&
@@ -294,6 +308,7 @@ test_expect_success 'file vs modified submodule' '
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
     ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "l" | git mergetool submod ) &&
     git submodule update -N &&
@@ -309,6 +324,7 @@ test_expect_success 'file vs modified submodule' '
     test_must_fail git merge test7 &&
     test -n "$(git ls-files -u)" &&
     ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "r" | git mergetool submod ) &&
     test -d submod.orig &&
@@ -324,6 +340,7 @@ test_expect_success 'file vs modified submodule' '
     test_must_fail git merge test7 &&
     test -n "$(git ls-files -u)" &&
     ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool both>/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "l" | git mergetool submod ) &&
     test "$(cat submod/bar)" = "master submodule" &&
@@ -445,4 +462,13 @@ test_expect_success 'directory vs modified submodule' '
     git submodule update -N
 '
 
+test_expect_success 'file with no base' '
+    git checkout -b test13 branch1 &&
+    test_must_fail git merge master &&
+    git mergetool --no-prompt --tool mybase -- both &&
+    >expected &&
+    test_cmp both expected &&
+    git reset --hard master >/dev/null 2>&1
+'
+
 test_done
index 395adfc8a946bfd67b8a61cbef1366b78d9d855e..4fb4c9384a0045d3b041d627e9d814637d9268e2 100755 (executable)
@@ -38,7 +38,17 @@ restore_test_defaults()
 prompt_given()
 {
        prompt="$1"
-       test "$prompt" = "Hit return to launch 'test-tool': branch"
+       test "$prompt" = "Launch 'test-tool' [Y/n]: branch"
+}
+
+stdin_contains()
+{
+       grep >/dev/null "$1"
+}
+
+stdin_doesnot_contain()
+{
+       ! stdin_contains "$1"
 }
 
 # Create a file on master and change it on branch
@@ -265,4 +275,35 @@ test_expect_success PERL 'difftool --extcmd cat arg2' '
        test "$diff" = branch
 '
 
+# Create a second file on master and a different version on branch
+test_expect_success PERL 'setup with 2 files different' '
+       echo m2 >file2 &&
+       git add file2 &&
+       git commit -m "added file2" &&
+
+       git checkout branch &&
+       echo br2 >file2 &&
+       git add file2 &&
+       git commit -a -m "branch changed file2" &&
+       git checkout master
+'
+
+test_expect_success PERL 'say no to the first file' '
+       diff=$( (echo n; echo) | git difftool -x cat branch ) &&
+
+       echo "$diff" | stdin_contains m2 &&
+       echo "$diff" | stdin_contains br2 &&
+       echo "$diff" | stdin_doesnot_contain master &&
+       echo "$diff" | stdin_doesnot_contain branch
+'
+
+test_expect_success PERL 'say no to the second file' '
+       diff=$( (echo; echo n) | git difftool -x cat branch ) &&
+
+       echo "$diff" | stdin_contains master &&
+       echo "$diff" | stdin_contains branch &&
+       echo "$diff" | stdin_doesnot_contain m2 &&
+       echo "$diff" | stdin_doesnot_contain br2
+'
+
 test_done
index 0d600163c8284a318fbd21f3a00dd7853b2f8956..75f4716d8cbca0c295668e181a557b186cb37432 100755 (executable)
@@ -245,6 +245,28 @@ do
        '
 done
 
+cat >expected <<EOF
+file
+EOF
+test_expect_success 'grep -l -C' '
+       git grep -l -C1 foo >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+file:5
+EOF
+test_expect_success 'grep -l -C' '
+       git grep -c -C1 foo >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep -L -C' '
+       git ls-files >expected &&
+       git grep -L -C1 nonexistent_string >actual &&
+       test_cmp expected actual
+'
+
 cat >expected <<EOF
 file:foo mmap bar_mmap
 EOF
@@ -523,6 +545,20 @@ test_expect_success 'grep -W' '
        test_cmp expected actual
 '
 
+cat >expected <<EOF
+hello.c=       printf("Hello world.\n");
+hello.c:       return 0;
+hello.c-       /* char ?? */
+EOF
+
+test_expect_success 'grep -W with userdiff' '
+       test_when_finished "rm -f .gitattributes" &&
+       git config diff.custom.xfuncname "(printf.*|})$" &&
+       echo "hello.c diff=custom" >.gitattributes &&
+       git grep -W return >actual &&
+       test_cmp expected actual
+'
+
 test_expect_success 'grep from a subdirectory to search wider area (1)' '
        mkdir -p s &&
        (
@@ -554,7 +590,6 @@ test_expect_success 'outside of git repository' '
        mkdir -p non/git/sub &&
        echo hello >non/git/file1 &&
        echo world >non/git/sub/file2 &&
-       echo ".*o*" >non/git/.gitignore &&
        {
                echo file1:hello &&
                echo sub/file2:world
@@ -571,6 +606,23 @@ test_expect_success 'outside of git repository' '
                test_must_fail git grep o &&
                git grep --no-index o >../../actual.sub &&
                test_cmp ../../expect.sub ../../actual.sub
+       ) &&
+
+       echo ".*o*" >non/git/.gitignore &&
+       (
+               GIT_CEILING_DIRECTORIES="$(pwd)/non/git" &&
+               export GIT_CEILING_DIRECTORIES &&
+               cd non/git &&
+               test_must_fail git grep o &&
+               git grep --no-index --exclude-standard o >../actual.full &&
+               test_cmp ../expect.full ../actual.full &&
+
+               {
+                       echo ".gitignore:.*o*"
+                       cat ../expect.full
+               } >../expect.with.ignored &&
+               git grep --no-index --no-exclude o >../actual.full &&
+               test_cmp ../expect.with.ignored ../actual.full
        )
 '
 
@@ -583,6 +635,10 @@ test_expect_success 'inside git repository but with --no-index' '
        {
                echo file1:hello &&
                echo sub/file2:world
+       } >is/expect.unignored &&
+       {
+               echo ".gitignore:.*o*" &&
+               cat is/expect.unignored
        } >is/expect.full &&
        : >is/expect.empty &&
        echo file2:world >is/expect.sub &&
@@ -591,12 +647,24 @@ test_expect_success 'inside git repository but with --no-index' '
                git init &&
                test_must_fail git grep o >../actual.full &&
                test_cmp ../expect.empty ../actual.full &&
+
+               git grep --untracked o >../actual.unignored &&
+               test_cmp ../expect.unignored ../actual.unignored &&
+
                git grep --no-index o >../actual.full &&
                test_cmp ../expect.full ../actual.full &&
+
+               git grep --no-index --exclude-standard o >../actual.unignored &&
+               test_cmp ../expect.unignored ../actual.unignored &&
+
                cd sub &&
                test_must_fail git grep o >../../actual.sub &&
                test_cmp ../../expect.empty ../../actual.sub &&
+
                git grep --no-index o >../../actual.sub &&
+               test_cmp ../../expect.sub ../../actual.sub &&
+
+               git grep --untracked o >../../actual.sub &&
                test_cmp ../../expect.sub ../../actual.sub
        )
 '
index 32ec82ad678d56bbf27f525fc8588b3391d9117d..c3c22f7764adc1542579dfddf1b47a687a93607a 100755 (executable)
@@ -10,11 +10,12 @@ find_blame() {
 cat >helper <<'EOF'
 #!/bin/sh
 grep -q '^bin: ' "$1" || { echo "E: $1 is not \"binary\" file" 1>&2; exit 1; }
-sed 's/^bin: /converted: /' "$1"
+perl -p -e 's/^bin: /converted: /' "$1"
 EOF
 chmod +x helper
 
 test_expect_success 'setup ' '
+       echo "bin: test number 0" >zero.bin &&
        echo "bin: test 1" >one.bin &&
        echo "bin: test number 2" >two.bin &&
        if test_have_prereq SYMLINKS; then
@@ -43,6 +44,7 @@ test_expect_success 'no filter specified' '
 
 test_expect_success 'setup textconv filters' '
        echo "*.bin diff=test" >.gitattributes &&
+       echo "zero.bin eol=crlf" >>.gitattributes &&
        git config diff.test.textconv ./helper &&
        git config diff.test.cachetextconv false
 '
@@ -74,6 +76,15 @@ test_expect_success 'blame --textconv going through revisions' '
        test_cmp expected result
 '
 
+test_expect_success 'blame --textconv with local changes' '
+       test_when_finished "git checkout zero.bin" &&
+       printf "bin: updated number 0\015" >zero.bin &&
+       git blame --textconv zero.bin >blame &&
+       expect="(Not Committed Yet ....-..-.. ..:..:.. +0000 1)" &&
+       expect="$expect converted: updated number 0" &&
+       expr "$(find_blame <blame)" : "^$expect"
+'
+
 test_expect_success 'setup +cachetextconv' '
        git config diff.test.cachetextconv true
 '
index 579ddb7572c27889060da315ddecb773b6fa1272..8c12c65c72658acb37fa715fc93756381bc824bb 100755 (executable)
@@ -23,6 +23,7 @@ test_expect_success $PREREQ \
       echo do
       echo "  echo \"!\$a!\""
       echo "done >commandline\$output"
+      test_have_prereq MINGW && echo "dos2unix commandline\$output"
       echo "cat > msgtxt\$output"
       ) >fake.sendmail &&
      chmod +x ./fake.sendmail &&
@@ -1168,4 +1169,32 @@ test_expect_success $PREREQ '--force sends cover letter template anyway' '
        test -n "$(ls msgtxt*)"
 '
 
+test_expect_success $PREREQ 'sendemail.aliasfiletype=mailrc' '
+       clean_fake_sendmail &&
+       echo "alias sbd  somebody@example.org" >.mailrc &&
+       git config --replace-all sendemail.aliasesfile "$(pwd)/.mailrc" &&
+       git config sendemail.aliasfiletype mailrc &&
+       git send-email \
+         --from="Example <nobody@example.com>" \
+         --to=sbd \
+         --smtp-server="$(pwd)/fake.sendmail" \
+         outdir/0001-*.patch \
+         2>errors >out &&
+       grep "^!somebody@example\.org!$" commandline1
+'
+
+test_expect_success $PREREQ 'sendemail.aliasfile=~/.mailrc' '
+       clean_fake_sendmail &&
+       echo "alias sbd  someone@example.org" >~/.mailrc &&
+       git config --replace-all sendemail.aliasesfile "~/.mailrc" &&
+       git config sendemail.aliasfiletype mailrc &&
+       git send-email \
+         --from="Example <nobody@example.com>" \
+         --to=sbd \
+         --smtp-server="$(pwd)/fake.sendmail" \
+         outdir/0001-*.patch \
+         2>errors >out &&
+       grep "^!someone@example\.org!$" commandline1
+'
+
 test_done
index b324c491c52eb0fa39d713cc5f227d134c1adb01..c3443ceb251f87806312f4e6dadb54881b52a8fe 100755 (executable)
@@ -96,8 +96,8 @@ test_expect_success 'fresh clone with svn.authors-file in config' '
                rm -r "$GIT_DIR" &&
                test x = x"$(git config svn.authorsfile)" &&
                test_config="$HOME"/.gitconfig &&
-               unset GIT_DIR &&
-               unset GIT_CONFIG &&
+               sane_unset GIT_DIR &&
+               sane_unset GIT_CONFIG &&
                git config --global \
                  svn.authorsfile "$HOME"/svn-authors &&
                test x"$HOME"/svn-authors = x"$(git config svn.authorsfile)" &&
diff --git a/t/t9162-git-svn-dcommit-interactive.sh b/t/t9162-git-svn-dcommit-interactive.sh
new file mode 100755 (executable)
index 0000000..e38d9fa
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Frédéric Heitzmann
+
+test_description='git svn dcommit --interactive series'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize repo' '
+       svn_cmd mkdir -m"mkdir test-interactive" "$svnrepo/test-interactive" &&
+       git svn clone "$svnrepo/test-interactive" test-interactive &&
+       cd test-interactive &&
+       touch foo && git add foo && git commit -m"foo: first commit" &&
+       git svn dcommit
+       '
+
+test_expect_success 'answers: y [\n] yes' '
+       (
+               echo "change #1" >> foo && git commit -a -m"change #1" &&
+               echo "change #2" >> foo && git commit -a -m"change #2" &&
+               echo "change #3" >> foo && git commit -a -m"change #3" &&
+               ( echo "y
+
+y" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
+               test $(git rev-parse HEAD) = $(git rev-parse remotes/git-svn)
+       )
+       '
+
+test_expect_success 'answers: yes yes no' '
+       (
+               echo "change #1" >> foo && git commit -a -m"change #1" &&
+               echo "change #2" >> foo && git commit -a -m"change #2" &&
+               echo "change #3" >> foo && git commit -a -m"change #3" &&
+               ( echo "yes
+yes
+no" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
+               test $(git rev-parse HEAD^^^) = $(git rev-parse remotes/git-svn) &&
+               git reset --hard remotes/git-svn
+       )
+       '
+
+test_expect_success 'answers: yes quit' '
+       (
+               echo "change #1" >> foo && git commit -a -m"change #1" &&
+               echo "change #2" >> foo && git commit -a -m"change #2" &&
+               echo "change #3" >> foo && git commit -a -m"change #3" &&
+               ( echo "yes
+quit" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
+               test $(git rev-parse HEAD^^^) = $(git rev-parse remotes/git-svn) &&
+               git reset --hard remotes/git-svn
+       )
+       '
+
+test_expect_success 'answers: all' '
+       (
+               echo "change #1" >> foo && git commit -a -m"change #1" &&
+               echo "change #2" >> foo && git commit -a -m"change #2" &&
+               echo "change #3" >> foo && git commit -a -m"change #3" &&
+               ( echo "all" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
+               test $(git rev-parse HEAD) = $(git rev-parse remotes/git-svn) &&
+               git reset --hard remotes/git-svn
+       )
+       '
+
+test_done
index 41db05cb4af6f42ef3065ba0576d8cfa3509c0ca..b59be9a894b2481ece7dc5ea9038bc6c88d29f15 100755 (executable)
@@ -19,9 +19,9 @@ then
     test_done
 fi
 
-CVSROOT=$(pwd)/cvsroot
-CVSWORK=$(pwd)/cvswork
-GIT_DIR=$(pwd)/.git
+CVSROOT=$PWD/cvsroot
+CVSWORK=$PWD/cvswork
+GIT_DIR=$PWD/.git
 export CVSROOT CVSWORK GIT_DIR
 
 rm -rf "$CVSROOT" "$CVSWORK"
@@ -321,7 +321,7 @@ test_expect_success 'use the same checkout for Git and CVS' '
 
        (mkdir shared &&
         cd shared &&
-        unset GIT_DIR &&
+        sane_unset GIT_DIR &&
         cvs co . &&
         git init &&
         git add " space" &&
index 1a6c06631c1c2a8fc29b66c9265541b54e8a9715..438aaf6b14b8ae0cb35739d6006eaf646876107c 100755 (executable)
@@ -820,6 +820,18 @@ test_expect_success \
        'test 1 = `git rev-list J | wc -l` &&
         test 0 = `git ls-tree J | wc -l`'
 
+cat >input <<INPUT_END
+reset refs/heads/J2
+
+tag wrong_tag
+from refs/heads/J2
+data <<EOF
+Tag branch that was reset.
+EOF
+INPUT_END
+test_expect_success \
+       'J: tag must fail on empty branch' \
+       'test_must_fail git fast-import <input'
 ###
 ### series K
 ###
@@ -1975,6 +1987,23 @@ test_expect_success \
        'Q: verify second note for second commit' \
        'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual'
 
+cat >input <<EOF
+reset refs/heads/Q0
+
+commit refs/heads/note-Q0
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+Note for an empty branch.
+COMMIT
+
+N inline refs/heads/Q0
+data <<NOTE
+some note
+NOTE
+EOF
+test_expect_success \
+       'Q: deny note on empty branch' \
+       'test_must_fail git fast-import <input'
 ###
 ### series R (feature and option)
 ###
@@ -2208,7 +2237,7 @@ test_expect_success 'R: cat-blob-fd must be a nonnegative integer' '
        test_must_fail git fast-import --cat-blob-fd=-1 </dev/null
 '
 
-test_expect_success 'R: print old blob' '
+test_expect_success NOT_MINGW 'R: print old blob' '
        blob=$(echo "yes it can" | git hash-object -w --stdin) &&
        cat >expect <<-EOF &&
        ${blob} blob 11
@@ -2220,7 +2249,7 @@ test_expect_success 'R: print old blob' '
        test_cmp expect actual
 '
 
-test_expect_success 'R: in-stream cat-blob-fd not respected' '
+test_expect_success NOT_MINGW 'R: in-stream cat-blob-fd not respected' '
        echo hello >greeting &&
        blob=$(git hash-object -w greeting) &&
        cat >expect <<-EOF &&
@@ -2241,7 +2270,7 @@ test_expect_success 'R: in-stream cat-blob-fd not respected' '
        test_cmp expect actual.1
 '
 
-test_expect_success 'R: print new blob' '
+test_expect_success NOT_MINGW 'R: print new blob' '
        blob=$(echo "yep yep yep" | git hash-object --stdin) &&
        cat >expect <<-EOF &&
        ${blob} blob 12
@@ -2259,7 +2288,7 @@ test_expect_success 'R: print new blob' '
        test_cmp expect actual
 '
 
-test_expect_success 'R: print new blob by sha1' '
+test_expect_success NOT_MINGW 'R: print new blob by sha1' '
        blob=$(echo "a new blob named by sha1" | git hash-object --stdin) &&
        cat >expect <<-EOF &&
        ${blob} blob 25
index 463254c72734beaf74948b6c424367ef4fea9d1a..83acf68bc3c19770eb690ece139892ca75329216 100755 (executable)
@@ -505,9 +505,63 @@ test_expect_success 'verify that non-notes are untouched by a fanout change' '
        test_cmp expect_non-note3 actual
 
 '
+
+# Change the notes for the three top commits
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/many_notes
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+changing notes for the top three commits
+COMMIT
+from refs/notes/many_notes^0
+INPUT_END
+
+rm expect
+i=$num_commits
+j=0
+while test $j -lt 3
+do
+       cat >>input <<INPUT_END
+N inline refs/heads/many_commits~$j
+data <<EOF
+changed note for commit #$i
+EOF
+INPUT_END
+       cat >>expect <<EXPECT_END
+    commit #$i
+    changed note for commit #$i
+EXPECT_END
+       i=$(($i - 1))
+       j=$(($j + 1))
+done
+
+test_expect_success 'change a few existing notes' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/many_notes git log -n3 refs/heads/many_commits |
+           grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_expect_success 'verify that changing notes respect existing fanout' '
+
+       # None of the entries in the top-level notes tree should be a full SHA1
+       git ls-tree --name-only refs/notes/many_notes |
+       while read path
+       do
+               if test $(expr length "$path") -ge 40
+               then
+                       return 1
+               fi
+       done
+
+'
+
 remaining_notes=10
 test_tick
-cat >>input <<INPUT_END
+cat >input <<INPUT_END
 commit refs/notes/many_notes
 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 data <<COMMIT
@@ -516,12 +570,11 @@ COMMIT
 from refs/notes/many_notes^0
 INPUT_END
 
-i=$remaining_notes
-while test $i -lt $num_commits
+i=$(($num_commits - $remaining_notes))
+for sha1 in $(git rev-list -n $i refs/heads/many_commits)
 do
-       i=$(($i + 1))
        cat >>input <<INPUT_END
-N 0000000000000000000000000000000000000000 :$i
+N 0000000000000000000000000000000000000000 $sha1
 INPUT_END
 done
 
index 53297156a314a5b5e03385a3ad887eff959359c4..90bb6050c13ece02199b6978e43e3c67de563c84 100755 (executable)
@@ -273,6 +273,53 @@ test_expect_success \
        'commitdiff(2): directory becomes symlink' \
        'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=foo-symlinked-to-bar"'
 
+# ----------------------------------------------------------------------
+# commitdiff testing (incomplete lines)
+
+test_expect_success 'setup incomplete lines' '
+       cat >file<<-\EOF &&
+       Dominus regit me,
+       et nihil mihi deerit.
+       In loco pascuae ibi me collocavit,
+       super aquam refectionis educavit me;
+       animam meam convertit,
+       deduxit me super semitas jusitiae,
+       propter nomen suum.
+       CHANGE_ME
+       EOF
+       git commit -a -m "Preparing for incomplete lines" &&
+       echo "incomplete" | tr -d "\\012" >>file &&
+       git commit -a -m "Add incomplete line" &&
+       git tag incomplete_lines_add &&
+       sed -e s/CHANGE_ME/change_me/ <file >file+ &&
+       mv -f file+ file &&
+       git commit -a -m "Incomplete context line" &&
+       git tag incomplete_lines_ctx &&
+       echo "Dominus regit me," >file &&
+       echo "incomplete line" | tr -d "\\012" >>file &&
+       git commit -a -m "Change incomplete line" &&
+       git tag incomplete_lines_chg
+       echo "Dominus regit me," >file &&
+       git commit -a -m "Remove incomplete line" &&
+       git tag incomplete_lines_rem
+'
+
+test_expect_success 'commitdiff(1): addition of incomplete line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_add"
+'
+
+test_expect_success 'commitdiff(1): incomplete line as context line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_ctx"
+'
+
+test_expect_success 'commitdiff(1): change incomplete line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_chg"
+'
+
+test_expect_success 'commitdiff(1): removal of incomplete line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_rem"
+'
+
 # ----------------------------------------------------------------------
 # commit, commitdiff: merge, large
 test_expect_success \
@@ -282,7 +329,8 @@ test_expect_success \
         git add b &&
         git commit -a -m "On branch" &&
         git checkout master &&
-        git pull . b'
+        git pull . b &&
+        git tag merge_commit'
 
 test_expect_success \
        'commit(0): merge commit' \
@@ -331,6 +379,29 @@ test_expect_success \
        'commitdiff(1): large commit' \
        'gitweb_run "p=.git;a=commitdiff;h=b"'
 
+# ----------------------------------------------------------------------
+# side-by-side diff
+
+test_expect_success 'side-by-side: addition of incomplete line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_add;ds=sidebyside"
+'
+
+test_expect_success 'side-by-side: incomplete line as context line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_ctx;ds=sidebyside"
+'
+
+test_expect_success 'side-by-side: changed incomplete line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_chg;ds=sidebyside"
+'
+
+test_expect_success 'side-by-side: removal of incomplete line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_rem;ds=sidebyside"
+'
+
+test_expect_success 'side-by-side: merge commit' '
+       gitweb_run "p=.git;a=commitdiff;h=merge_commit;ds=sidebyside"
+'
+
 # ----------------------------------------------------------------------
 # tags testing
 
@@ -403,6 +474,14 @@ test_expect_success \
        'path_info: project/branch:dir/' \
        'gitweb_run "" "/.git/master:foo/"'
 
+test_expect_success \
+       'path_info: project/branch (non-existent)' \
+       'gitweb_run "" "/.git/non-existent"'
+
+test_expect_success \
+       'path_info: project/branch:filename (non-existent branch)' \
+       'gitweb_run "" "/.git/non-existent:non-existent"'
+
 test_expect_success \
        'path_info: project/branch:file (non-existent)' \
        'gitweb_run "" "/.git/master:non-existent"'
@@ -558,6 +637,45 @@ test_expect_success \
        'config override: tree view, features enabled in repo config (2)' \
        'gitweb_run "p=.git;a=tree"'
 
+# ----------------------------------------------------------------------
+# searching
+
+cat >>gitweb_config.perl <<\EOF
+
+# enable search
+$feature{'search'}{'default'} = [1];
+$feature{'grep'}{'default'} = [1];
+$feature{'pickaxe'}{'default'} = [1];
+EOF
+
+test_expect_success \
+       'search: preparation' \
+       'echo "1st MATCH" >>file &&
+        echo "2nd MATCH" >>file &&
+        echo "MATCH" >>bar &&
+        git add file bar &&
+        git commit -m "Added MATCH word"'
+
+test_expect_success \
+       'search: commit author' \
+       'gitweb_run "p=.git;a=search;h=HEAD;st=author;s=A+U+Thor"'
+
+test_expect_success \
+       'search: commit message' \
+       'gitweb_run "p=.git;a=search;h=HEAD;st=commitr;s=MATCH"'
+
+test_expect_success \
+       'search: grep' \
+       'gitweb_run "p=.git;a=search;h=HEAD;st=grep;s=MATCH"'
+
+test_expect_success \
+       'search: pickaxe' \
+       'gitweb_run "p=.git;a=search;h=HEAD;st=pickaxe;s=MATCH"'
+
+test_expect_success \
+       'search: projects' \
+       'gitweb_run "a=project_list;s=.git"'
+
 # ----------------------------------------------------------------------
 # non-ASCII in README.html
 
@@ -660,4 +778,13 @@ test_expect_success \
        'echo "\$projects_list_group_categories = 1;" >>gitweb_config.perl &&
         gitweb_run'
 
+# ----------------------------------------------------------------------
+# unborn branches
+
+test_expect_success \
+       'unborn HEAD: "summary" page (with "heads" subview)' \
+       'git checkout orphan_branch || git checkout --orphan orphan_branch &&
+        test_when_finished "git checkout master" &&
+        gitweb_run "p=.git;a=summary"'
+
 test_done
index 26102ee9b0c36a87ba17a75b0ca644cc42e2c1c4..31076edc5bd45261f5874b10dad6376e49fb9002 100755 (executable)
@@ -134,4 +134,14 @@ our $maxload = undef;
 EOF
 
 
+# ----------------------------------------------------------------------
+# invalid arguments
+
+test_expect_success 'invalid arguments: invalid regexp (in project search)' '
+       gitweb_run "a=project_list;s=*\.git;sr=1" &&
+       grep "Status: 400" gitweb.headers &&
+       grep "400 - Invalid.*regexp" gitweb.body
+'
+test_debug 'cat gitweb.headers'
+
 test_done
index 3787186703f51f75103824f562c3849483ceaae1..435d8964763d3f048a71db5453b3a19e0cbb7fb9 100755 (executable)
@@ -43,7 +43,11 @@ test_expect_success \
      git config --add test.booltrue true &&
      git config --add test.boolfalse no &&
      git config --add test.boolother other &&
-     git config --add test.int 2k
+     git config --add test.int 2k &&
+     git config --add test.path "~/foo" &&
+     git config --add test.pathexpanded "$HOME/foo" &&
+     git config --add test.pathmulti foo &&
+     git config --add test.pathmulti bar
      '
 
 # The external test will outputs its own plan
index 13ba96e21a9295805aed40c89ecd5178db6bfae4..3b9b48408a87150fad9b1ac1f8cf5ea65e2b09ba 100755 (executable)
@@ -33,6 +33,10 @@ is($r->config_int("test.int"), 2048, "config_int: integer");
 is($r->config_int("test.nonexistent"), undef, "config_int: nonexistent");
 ok($r->config_bool("test.booltrue"), "config_bool: true");
 ok(!$r->config_bool("test.boolfalse"), "config_bool: false");
+is($r->config_path("test.path"), $r->config("test.pathexpanded"),
+   "config_path: ~/foo expansion");
+is_deeply([$r->config_path("test.pathmulti")], ["foo", "bar"],
+   "config_path: multiple values");
 our $ansi_green = "\x1b[32m";
 is($r->get_color("color.test.slot1", "red"), $ansi_green, "get_color");
 # Cannot test $r->get_colorbool("color.foo")) because we do not
diff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh
new file mode 100755 (executable)
index 0000000..486c8ee
--- /dev/null
@@ -0,0 +1,470 @@
+#!/bin/sh
+
+test_description='git-p4 tests'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'add p4 files' '
+       (
+               cd "$cli" &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "file1" &&
+               echo file2 >file2 &&
+               p4 add file2 &&
+               p4 submit -d "file2"
+       )
+'
+
+test_expect_success 'basic git-p4 clone' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git log --oneline >lines &&
+               test_line_count = 1 lines
+       )
+'
+
+test_expect_success 'git-p4 clone @all' '
+       "$GITP4" clone --dest="$git" //depot@all &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git log --oneline >lines &&
+               test_line_count = 2 lines
+       )
+'
+
+test_expect_success 'git-p4 sync uninitialized repo' '
+       test_create_repo "$git" &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               test_must_fail "$GITP4" sync
+       )
+'
+
+#
+# Create a git repo by hand.  Add a commit so that HEAD is valid.
+# Test imports a new p4 repository into a new git branch.
+#
+test_expect_success 'git-p4 sync new branch' '
+       test_create_repo "$git" &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               test_commit head &&
+               "$GITP4" sync --branch=refs/remotes/p4/depot //depot@all &&
+               git log --oneline p4/depot >lines &&
+               test_line_count = 2 lines
+       )
+'
+
+test_expect_success 'clone two dirs' '
+       (
+               cd "$cli" &&
+               mkdir sub1 sub2 &&
+               echo sub1/f1 >sub1/f1 &&
+               echo sub2/f2 >sub2/f2 &&
+               p4 add sub1/f1 &&
+               p4 submit -d "sub1/f1" &&
+               p4 add sub2/f2 &&
+               p4 submit -d "sub2/f2"
+       ) &&
+       "$GITP4" clone --dest="$git" //depot/sub1 //depot/sub2 &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git ls-files >lines &&
+               test_line_count = 2 lines &&
+               git log --oneline p4/master >lines &&
+               test_line_count = 1 lines
+       )
+'
+
+test_expect_success 'clone two dirs, @all' '
+       (
+               cd "$cli" &&
+               echo sub1/f3 >sub1/f3 &&
+               p4 add sub1/f3 &&
+               p4 submit -d "sub1/f3"
+       ) &&
+       "$GITP4" clone --dest="$git" //depot/sub1@all //depot/sub2@all &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git ls-files >lines &&
+               test_line_count = 3 lines &&
+               git log --oneline p4/master >lines &&
+               test_line_count = 3 lines
+       )
+'
+
+test_expect_success 'clone two dirs, @all, conflicting files' '
+       (
+               cd "$cli" &&
+               echo sub2/f3 >sub2/f3 &&
+               p4 add sub2/f3 &&
+               p4 submit -d "sub2/f3"
+       ) &&
+       "$GITP4" clone --dest="$git" //depot/sub1@all //depot/sub2@all &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git ls-files >lines &&
+               test_line_count = 3 lines &&
+               git log --oneline p4/master >lines &&
+               test_line_count = 4 lines &&
+               echo sub2/f3 >expected &&
+               test_cmp expected f3
+       )
+'
+
+test_expect_success 'exit when p4 fails to produce marshaled output' '
+       badp4dir="$TRASH_DIRECTORY/badp4dir" &&
+       mkdir "$badp4dir" &&
+       test_when_finished "rm \"$badp4dir/p4\" && rmdir \"$badp4dir\"" &&
+       cat >"$badp4dir"/p4 <<-EOF &&
+       #!$SHELL_PATH
+       exit 1
+       EOF
+       chmod 755 "$badp4dir"/p4 &&
+       PATH="$badp4dir:$PATH" "$GITP4" clone --dest="$git" //depot >errs 2>&1 ; retval=$? &&
+       test $retval -eq 1 &&
+       test_must_fail grep -q Traceback errs
+'
+
+test_expect_success 'add p4 files with wildcards in the names' '
+       (
+               cd "$cli" &&
+               echo file-wild-hash >file-wild#hash &&
+               echo file-wild-star >file-wild\*star &&
+               echo file-wild-at >file-wild@at &&
+               echo file-wild-percent >file-wild%percent &&
+               p4 add -f file-wild* &&
+               p4 submit -d "file wildcards"
+       )
+'
+
+test_expect_success 'wildcard files git-p4 clone' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               test -f file-wild#hash &&
+               test -f file-wild\*star &&
+               test -f file-wild@at &&
+               test -f file-wild%percent
+       )
+'
+
+test_expect_success 'clone bare' '
+       "$GITP4" clone --dest="$git" --bare //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               test ! -d .git &&
+               bare=`git config --get core.bare` &&
+               test "$bare" = true
+       )
+'
+
+p4_add_user() {
+       name=$1 fullname=$2 &&
+       p4 user -f -i <<-EOF &&
+       User: $name
+       Email: $name@localhost
+       FullName: $fullname
+       EOF
+       p4 passwd -P secret $name
+}
+
+p4_grant_admin() {
+       name=$1 &&
+       {
+               p4 protect -o &&
+               echo "    admin user $name * //depot/..."
+       } | p4 protect -i
+}
+
+p4_check_commit_author() {
+       file=$1 user=$2 &&
+       p4 changes -m 1 //depot/$file | grep -q $user
+}
+
+make_change_by_user() {
+       file=$1 name=$2 email=$3 &&
+       echo "username: a change by $name" >>"$file" &&
+       git add "$file" &&
+       git commit --author "$name <$email>" -m "a change by $name"
+}
+
+# Test username support, submitting as user 'alice'
+test_expect_success 'preserve users' '
+       p4_add_user alice Alice &&
+       p4_add_user bob Bob &&
+       p4_grant_admin alice &&
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               echo "username: a change by alice" >>file1 &&
+               echo "username: a change by bob" >>file2 &&
+               git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 &&
+               git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 &&
+               git config git-p4.skipSubmitEditCheck true &&
+               P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
+               p4_check_commit_author file1 alice &&
+               p4_check_commit_author file2 bob
+       )
+'
+
+# Test username support, submitting as bob, who lacks admin rights. Should
+# not submit change to p4 (git diff should show deltas).
+test_expect_success 'refuse to preserve users without perms' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               echo "username-noperms: a change by alice" >>file1 &&
+               git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
+               P4EDITOR=touch P4USER=bob P4PASSWD=secret &&
+               export P4EDITOR P4USER P4PASSWD &&
+               test_must_fail "$GITP4" commit --preserve-user &&
+               ! git diff --exit-code HEAD..p4/master
+       )
+'
+
+# What happens with unknown author? Without allowMissingP4Users it should fail.
+test_expect_success 'preserve user where author is unknown to p4' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               echo "username-bob: a change by bob" >>file1 &&
+               git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
+               echo "username-unknown: a change by charlie" >>file1 &&
+               git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 &&
+               P4EDITOR=touch P4USER=alice P4PASSWD=secret &&
+               export P4EDITOR P4USER P4PASSWD &&
+               test_must_fail "$GITP4" commit --preserve-user &&
+               ! git diff --exit-code HEAD..p4/master &&
+
+               echo "$0: repeat with allowMissingP4Users enabled" &&
+               git config git-p4.allowMissingP4Users true &&
+               git config git-p4.preserveUser true &&
+               "$GITP4" commit &&
+               git diff --exit-code HEAD..p4/master &&
+               p4_check_commit_author file1 alice
+       )
+'
+
+# If we're *not* using --preserve-user, git-p4 should warn if we're submitting
+# changes that are not all ours.
+# Test: user in p4 and user unknown to p4.
+# Test: warning disabled and user is the same.
+test_expect_success 'not preserving user with mixed authorship' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               p4_add_user derek Derek &&
+
+               make_change_by_user usernamefile3 Derek derek@localhost &&
+               P4EDITOR=cat P4USER=alice P4PASSWD=secret &&
+               export P4EDITOR P4USER P4PASSWD &&
+               "$GITP4" commit |\
+               grep "git author derek@localhost does not match" &&
+
+               make_change_by_user usernamefile3 Charlie charlie@localhost &&
+               "$GITP4" commit |\
+               grep "git author charlie@localhost does not match" &&
+
+               make_change_by_user usernamefile3 alice alice@localhost &&
+               "$GITP4" commit |\
+               test_must_fail grep "git author.*does not match" &&
+
+               git config git-p4.skipUserNameCheck true &&
+               make_change_by_user usernamefile3 Charlie charlie@localhost &&
+               "$GITP4" commit |\
+               test_must_fail grep "git author.*does not match" &&
+
+               p4_check_commit_author usernamefile3 alice
+       )
+'
+
+marshal_dump() {
+       what=$1
+       "$PYTHON_PATH" -c 'import marshal, sys; d = marshal.load(sys.stdin); print d["'$what'"]'
+}
+
+# Sleep a bit so that the top-most p4 change did not happen "now".  Then
+# import the repo and make sure that the initial import has the same time
+# as the top-most change.
+test_expect_success 'initial import time from top change time' '
+       p4change=$(p4 -G changes -m 1 //depot/... | marshal_dump change) &&
+       p4time=$(p4 -G changes -m 1 //depot/... | marshal_dump time) &&
+       sleep 3 &&
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               gittime=$(git show -s --raw --pretty=format:%at HEAD) &&
+               echo $p4time $gittime &&
+               test $p4time = $gittime
+       )
+'
+
+# Rename a file and confirm that rename is not detected in P4.
+# Rename the new file again with detectRenames option enabled and confirm that
+# this is detected in P4.
+# Rename the new file again adding an extra line, configure a big threshold in
+# detectRenames and confirm that rename is not detected in P4.
+# Repeat, this time with a smaller threshold and confirm that the rename is
+# detected in P4.
+test_expect_success 'detect renames' '
+       "$GITP4" clone --dest="$git" //depot@all &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+
+               git mv file1 file4 &&
+               git commit -a -m "Rename file1 to file4" &&
+               git diff-tree -r -M HEAD &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file4 &&
+               p4 filelog //depot/file4 | test_must_fail grep -q "branch from" &&
+
+               git mv file4 file5 &&
+               git commit -a -m "Rename file4 to file5" &&
+               git diff-tree -r -M HEAD &&
+               git config git-p4.detectRenames true &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file5 &&
+               p4 filelog //depot/file5 | grep -q "branch from //depot/file4" &&
+
+               git mv file5 file6 &&
+               echo update >>file6 &&
+               git add file6 &&
+               git commit -a -m "Rename file5 to file6 with changes" &&
+               git diff-tree -r -M HEAD &&
+               level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
+               test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
+               git config git-p4.detectRenames $(($level + 2)) &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file6 &&
+               p4 filelog //depot/file6 | test_must_fail grep -q "branch from" &&
+
+               git mv file6 file7 &&
+               echo update >>file7 &&
+               git add file7 &&
+               git commit -a -m "Rename file6 to file7 with changes" &&
+               git diff-tree -r -M HEAD &&
+               level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
+               test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
+               git config git-p4.detectRenames $(($level - 2)) &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file7 &&
+               p4 filelog //depot/file7 | grep -q "branch from //depot/file6"
+       )
+'
+
+# Copy a file and confirm that copy is not detected in P4.
+# Copy a file with detectCopies option enabled and confirm that copy is not
+# detected in P4.
+# Modify and copy a file with detectCopies option enabled and confirm that copy
+# is detected in P4.
+# Copy a file with detectCopies and detectCopiesHarder options enabled and
+# confirm that copy is detected in P4.
+# Modify and copy a file, configure a bigger threshold in detectCopies and
+# confirm that copy is not detected in P4.
+# Modify and copy a file, configure a smaller threshold in detectCopies and
+# confirm that copy is detected in P4.
+test_expect_success 'detect copies' '
+       "$GITP4" clone --dest="$git" //depot@all &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+
+               cp file2 file8 &&
+               git add file8 &&
+               git commit -a -m "Copy file2 to file8" &&
+               git diff-tree -r -C HEAD &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file8 &&
+               p4 filelog //depot/file8 | test_must_fail grep -q "branch from" &&
+
+               cp file2 file9 &&
+               git add file9 &&
+               git commit -a -m "Copy file2 to file9" &&
+               git diff-tree -r -C HEAD &&
+               git config git-p4.detectCopies true &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file9 &&
+               p4 filelog //depot/file9 | test_must_fail grep -q "branch from" &&
+
+               echo "file2" >>file2 &&
+               cp file2 file10 &&
+               git add file2 file10 &&
+               git commit -a -m "Modify and copy file2 to file10" &&
+               git diff-tree -r -C HEAD &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file10 &&
+               p4 filelog //depot/file10 | grep -q "branch from //depot/file" &&
+
+               cp file2 file11 &&
+               git add file11 &&
+               git commit -a -m "Copy file2 to file11" &&
+               git diff-tree -r -C --find-copies-harder HEAD &&
+               src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+               test "$src" = file10 &&
+               git config git-p4.detectCopiesHarder true &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file11 &&
+               p4 filelog //depot/file11 | grep -q "branch from //depot/file" &&
+
+               cp file2 file12 &&
+               echo "some text" >>file12 &&
+               git add file12 &&
+               git commit -a -m "Copy file2 to file12 with changes" &&
+               git diff-tree -r -C --find-copies-harder HEAD &&
+               level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
+               test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
+               src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+               test "$src" = file10 &&
+               git config git-p4.detectCopies $(($level + 2)) &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file12 &&
+               p4 filelog //depot/file12 | test_must_fail grep -q "branch from" &&
+
+               cp file2 file13 &&
+               echo "different text" >>file13 &&
+               git add file13 &&
+               git commit -a -m "Copy file2 to file13 with changes" &&
+               git diff-tree -r -C --find-copies-harder HEAD &&
+               level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
+               test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
+               src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+               test "$src" = file10 &&
+               git config git-p4.detectCopies $(($level - 2)) &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file13 &&
+               p4 filelog //depot/file13 | grep -q "branch from //depot/file"
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9800-git-p4.sh b/t/t9800-git-p4.sh
deleted file mode 100755 (executable)
index 01ba041..0000000
+++ /dev/null
@@ -1,476 +0,0 @@
-#!/bin/sh
-
-test_description='git-p4 tests'
-
-. ./test-lib.sh
-
-( p4 -h && p4d -h ) >/dev/null 2>&1 || {
-       skip_all='skipping git-p4 tests; no p4 or p4d'
-       test_done
-}
-
-GITP4=$GIT_BUILD_DIR/contrib/fast-import/git-p4
-P4DPORT=10669
-
-export P4PORT=localhost:$P4DPORT
-
-db="$TRASH_DIRECTORY/db"
-cli="$TRASH_DIRECTORY/cli"
-git="$TRASH_DIRECTORY/git"
-
-test_debug 'echo p4d -q -d -r "$db" -p $P4DPORT'
-test_expect_success setup '
-       mkdir -p "$db" &&
-       p4d -q -d -r "$db" -p $P4DPORT &&
-       mkdir -p "$cli" &&
-       mkdir -p "$git" &&
-       export P4PORT=localhost:$P4DPORT
-'
-
-test_expect_success 'add p4 files' '
-       cd "$cli" &&
-       p4 client -i <<-EOF &&
-       Client: client
-       Description: client
-       Root: $cli
-       View: //depot/... //client/...
-       EOF
-       export P4CLIENT=client &&
-       echo file1 >file1 &&
-       p4 add file1 &&
-       p4 submit -d "file1" &&
-       echo file2 >file2 &&
-       p4 add file2 &&
-       p4 submit -d "file2" &&
-       cd "$TRASH_DIRECTORY"
-'
-
-cleanup_git() {
-       cd "$TRASH_DIRECTORY" &&
-       rm -rf "$git" &&
-       mkdir "$git"
-}
-
-test_expect_success 'basic git-p4 clone' '
-       "$GITP4" clone --dest="$git" //depot &&
-       test_when_finished cleanup_git &&
-       cd "$git" &&
-       git log --oneline >lines &&
-       test_line_count = 1 lines
-'
-
-test_expect_success 'git-p4 clone @all' '
-       "$GITP4" clone --dest="$git" //depot@all &&
-       test_when_finished cleanup_git &&
-       cd "$git" &&
-       git log --oneline >lines &&
-       test_line_count = 2 lines
-'
-
-test_expect_success 'git-p4 sync uninitialized repo' '
-       test_create_repo "$git" &&
-       test_when_finished cleanup_git &&
-       cd "$git" &&
-       test_must_fail "$GITP4" sync
-'
-
-#
-# Create a git repo by hand.  Add a commit so that HEAD is valid.
-# Test imports a new p4 repository into a new git branch.
-#
-test_expect_success 'git-p4 sync new branch' '
-       test_create_repo "$git" &&
-       test_when_finished cleanup_git &&
-       cd "$git" &&
-       test_commit head &&
-       "$GITP4" sync --branch=refs/remotes/p4/depot //depot@all &&
-       git log --oneline p4/depot >lines &&
-       test_line_count = 2 lines
-'
-
-test_expect_success 'exit when p4 fails to produce marshaled output' '
-       badp4dir="$TRASH_DIRECTORY/badp4dir" &&
-       mkdir -p "$badp4dir" &&
-       test_when_finished "rm -rf $badp4dir" &&
-       cat >"$badp4dir"/p4 <<-EOF &&
-       #!$SHELL_PATH
-       exit 1
-       EOF
-       chmod 755 "$badp4dir"/p4 &&
-       PATH="$badp4dir:$PATH" "$GITP4" clone --dest="$git" //depot >errs 2>&1 ; retval=$? &&
-       test $retval -eq 1 &&
-       test_must_fail grep -q Traceback errs
-'
-
-test_expect_success 'add p4 files with wildcards in the names' '
-       cd "$cli" &&
-       echo file-wild-hash >file-wild#hash &&
-       echo file-wild-star >file-wild\*star &&
-       echo file-wild-at >file-wild@at &&
-       echo file-wild-percent >file-wild%percent &&
-       p4 add -f file-wild* &&
-       p4 submit -d "file wildcards"
-'
-
-test_expect_success 'wildcard files git-p4 clone' '
-       "$GITP4" clone --dest="$git" //depot &&
-       test_when_finished cleanup_git &&
-       cd "$git" &&
-       test -f file-wild#hash &&
-       test -f file-wild\*star &&
-       test -f file-wild@at &&
-       test -f file-wild%percent
-'
-
-test_expect_success 'clone bare' '
-       "$GITP4" clone --dest="$git" --bare //depot &&
-       test_when_finished cleanup_git &&
-       cd "$git" &&
-       test ! -d .git &&
-       bare=`git config --get core.bare` &&
-       test "$bare" = true
-'
-
-p4_add_user() {
-    name=$1
-    fullname=$2
-    p4 user -f -i <<EOF &&
-User: $name
-Email: $name@localhost
-FullName: $fullname
-EOF
-    p4 passwd -P secret $name
-}
-
-p4_grant_admin() {
-    name=$1
-    p4 protect -o |\
-       awk "{print}END{print \"    admin user $name * //depot/...\"}" |\
-       p4 protect -i
-}
-
-p4_check_commit_author() {
-    file=$1
-    user=$2
-    if p4 changes -m 1 //depot/$file | grep $user > /dev/null ; then
-       return 0
-    else
-       echo "file $file not modified by user $user" 1>&2
-       return 1
-    fi
-}
-
-make_change_by_user() {
-       file=$1 name=$2 email=$3 &&
-       echo "username: a change by $name" >>"$file" &&
-       git add "$file" &&
-       git commit --author "$name <$email>" -m "a change by $name"
-}
-
-# Test username support, submitting as user 'alice'
-test_expect_success 'preserve users' '
-       p4_add_user alice Alice &&
-       p4_add_user bob Bob &&
-       p4_grant_admin alice &&
-       "$GITP4" clone --dest="$git" //depot &&
-       test_when_finished cleanup_git &&
-       cd "$git" &&
-       echo "username: a change by alice" >> file1 &&
-       echo "username: a change by bob" >> file2 &&
-       git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 &&
-       git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 &&
-       git config git-p4.skipSubmitEditCheck true &&
-       P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
-       p4_check_commit_author file1 alice &&
-       p4_check_commit_author file2 bob
-'
-
-# Test username support, submitting as bob, who lacks admin rights. Should
-# not submit change to p4 (git diff should show deltas).
-test_expect_success 'refuse to preserve users without perms' '
-       "$GITP4" clone --dest="$git" //depot &&
-       test_when_finished cleanup_git &&
-       cd "$git" &&
-       git config git-p4.skipSubmitEditCheck true &&
-       echo "username-noperms: a change by alice" >> file1 &&
-       git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
-       ! P4EDITOR=touch P4USER=bob P4PASSWD=secret "$GITP4" commit --preserve-user &&
-       ! git diff --exit-code HEAD..p4/master > /dev/null
-'
-
-# What happens with unknown author? Without allowMissingP4Users it should fail.
-test_expect_success 'preserve user where author is unknown to p4' '
-       "$GITP4" clone --dest="$git" //depot &&
-       test_when_finished cleanup_git &&
-       cd "$git" &&
-       git config git-p4.skipSubmitEditCheck true &&
-       echo "username-bob: a change by bob" >> file1 &&
-       git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
-       echo "username-unknown: a change by charlie" >> file1 &&
-       git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 &&
-       ! P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
-       ! git diff --exit-code HEAD..p4/master > /dev/null &&
-       echo "$0: repeat with allowMissingP4Users enabled" &&
-       git config git-p4.allowMissingP4Users true &&
-       git config git-p4.preserveUser true &&
-       P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit &&
-       git diff --exit-code HEAD..p4/master > /dev/null &&
-       p4_check_commit_author file1 alice
-'
-
-# If we're *not* using --preserve-user, git-p4 should warn if we're submitting
-# changes that are not all ours.
-# Test: user in p4 and user unknown to p4.
-# Test: warning disabled and user is the same.
-test_expect_success 'not preserving user with mixed authorship' '
-       "$GITP4" clone --dest="$git" //depot &&
-       test_when_finished cleanup_git &&
-       cd "$git" &&
-       git config git-p4.skipSubmitEditCheck true &&
-       p4_add_user derek Derek &&
-
-       make_change_by_user usernamefile3 Derek derek@localhost &&
-       P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
-       grep "git author derek@localhost does not match" actual &&
-
-       make_change_by_user usernamefile3 Charlie charlie@localhost &&
-       P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
-       grep "git author charlie@localhost does not match" actual &&
-
-       make_change_by_user usernamefile3 alice alice@localhost &&
-       P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
-       ! grep "git author.*does not match" actual &&
-
-       git config git-p4.skipUserNameCheck true &&
-       make_change_by_user usernamefile3 Charlie charlie@localhost &&
-       P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
-       ! grep "git author.*does not match" actual &&
-
-       p4_check_commit_author usernamefile3 alice
-'
-
-marshal_dump() {
-       what=$1
-       python -c 'import marshal, sys; d = marshal.load(sys.stdin); print d["'$what'"]'
-}
-
-# Sleep a bit so that the top-most p4 change did not happen "now".  Then
-# import the repo and make sure that the initial import has the same time
-# as the top-most change.
-test_expect_success 'initial import time from top change time' '
-       p4change=$(p4 -G changes -m 1 //depot/... | marshal_dump change) &&
-       p4time=$(p4 -G changes -m 1 //depot/... | marshal_dump time) &&
-       sleep 3 &&
-       "$GITP4" clone --dest="$git" //depot &&
-       test_when_finished cleanup_git &&
-       cd "$git" &&
-       gittime=$(git show -s --raw --pretty=format:%at HEAD) &&
-       echo $p4time $gittime &&
-       test $p4time = $gittime
-'
-
-# Rename a file and confirm that rename is not detected in P4.
-# Rename the new file again with detectRenames option enabled and confirm that
-# this is detected in P4.
-# Rename the new file again adding an extra line, configure a big threshold in
-# detectRenames and confirm that rename is not detected in P4.
-# Repeat, this time with a smaller threshold and confirm that the rename is
-# detected in P4.
-test_expect_success 'detect renames' '
-       "$GITP4" clone --dest="$git" //depot@all &&
-       test_when_finished cleanup_git &&
-       cd "$git" &&
-       git config git-p4.skipSubmitEditCheck true &&
-
-       git mv file1 file4 &&
-       git commit -a -m "Rename file1 to file4" &&
-       git diff-tree -r -M HEAD &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file4 &&
-       ! p4 filelog //depot/file4 | grep -q "branch from" &&
-
-       git mv file4 file5 &&
-       git commit -a -m "Rename file4 to file5" &&
-       git diff-tree -r -M HEAD &&
-       git config git-p4.detectRenames true &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file5 &&
-       p4 filelog //depot/file5 | grep -q "branch from //depot/file4" &&
-
-       git mv file5 file6 &&
-       echo update >>file6 &&
-       git add file6 &&
-       git commit -a -m "Rename file5 to file6 with changes" &&
-       git diff-tree -r -M HEAD &&
-       level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
-       test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
-       git config git-p4.detectRenames $((level + 2)) &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file6 &&
-       ! p4 filelog //depot/file6 | grep -q "branch from" &&
-
-       git mv file6 file7 &&
-       echo update >>file7 &&
-       git add file7 &&
-       git commit -a -m "Rename file6 to file7 with changes" &&
-       git diff-tree -r -M HEAD &&
-       level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
-       test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
-       git config git-p4.detectRenames $((level - 2)) &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file7 &&
-       p4 filelog //depot/file7 | grep -q "branch from //depot/file6"
-'
-
-# Copy a file and confirm that copy is not detected in P4.
-# Copy a file with detectCopies option enabled and confirm that copy is not
-# detected in P4.
-# Modify and copy a file with detectCopies option enabled and confirm that copy
-# is detected in P4.
-# Copy a file with detectCopies and detectCopiesHarder options enabled and
-# confirm that copy is detected in P4.
-# Modify and copy a file, configure a bigger threshold in detectCopies and
-# confirm that copy is not detected in P4.
-# Modify and copy a file, configure a smaller threshold in detectCopies and
-# confirm that copy is detected in P4.
-test_expect_success 'detect copies' '
-       "$GITP4" clone --dest="$git" //depot@all &&
-       test_when_finished cleanup_git &&
-       cd "$git" &&
-       git config git-p4.skipSubmitEditCheck true &&
-
-       cp file2 file8 &&
-       git add file8 &&
-       git commit -a -m "Copy file2 to file8" &&
-       git diff-tree -r -C HEAD &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file8 &&
-       ! p4 filelog //depot/file8 | grep -q "branch from" &&
-
-       cp file2 file9 &&
-       git add file9 &&
-       git commit -a -m "Copy file2 to file9" &&
-       git diff-tree -r -C HEAD &&
-       git config git-p4.detectCopies true &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file9 &&
-       ! p4 filelog //depot/file9 | grep -q "branch from" &&
-
-       echo "file2" >>file2 &&
-       cp file2 file10 &&
-       git add file2 file10 &&
-       git commit -a -m "Modify and copy file2 to file10" &&
-       git diff-tree -r -C HEAD &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file10 &&
-       p4 filelog //depot/file10 | grep -q "branch from //depot/file" &&
-
-       cp file2 file11 &&
-       git add file11 &&
-       git commit -a -m "Copy file2 to file11" &&
-       git diff-tree -r -C --find-copies-harder HEAD &&
-       src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
-       test "$src" = file10 &&
-       git config git-p4.detectCopiesHarder true &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file11 &&
-       p4 filelog //depot/file11 | grep -q "branch from //depot/file" &&
-
-       cp file2 file12 &&
-       echo "some text" >>file12 &&
-       git add file12 &&
-       git commit -a -m "Copy file2 to file12 with changes" &&
-       git diff-tree -r -C --find-copies-harder HEAD &&
-       level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
-       test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
-       src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
-       test "$src" = file10 &&
-       git config git-p4.detectCopies $((level + 2)) &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file12 &&
-       ! p4 filelog //depot/file12 | grep -q "branch from" &&
-
-       cp file2 file13 &&
-       echo "different text" >>file13 &&
-       git add file13 &&
-       git commit -a -m "Copy file2 to file13 with changes" &&
-       git diff-tree -r -C --find-copies-harder HEAD &&
-       level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
-       test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
-       src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
-       test "$src" = file10 &&
-       git config git-p4.detectCopies $((level - 2)) &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file13 &&
-       p4 filelog //depot/file13 | grep -q "branch from //depot/file"
-'
-
-# Create a simple branch structure in P4 depot to check if it is correctly
-# cloned.
-test_expect_success 'add simple p4 branches' '
-       cd "$cli" &&
-       mkdir branch1 &&
-       cd branch1 &&
-       echo file1 >file1 &&
-       echo file2 >file2 &&
-       p4 add file1 file2 &&
-       p4 submit -d "branch1" &&
-       p4 integrate //depot/branch1/... //depot/branch2/... &&
-       p4 submit -d "branch2" &&
-       echo file3 >file3 &&
-       p4 add file3 &&
-       p4 submit -d "add file3 in branch1" &&
-       p4 open file2 &&
-       echo update >>file2 &&
-       p4 submit -d "update file2 in branch1" &&
-       p4 integrate //depot/branch1/... //depot/branch3/... &&
-       p4 submit -d "branch3" &&
-       cd "$TRASH_DIRECTORY"
-'
-
-# Configure branches through git-config and clone them.
-# All files are tested to make sure branches were cloned correctly.
-# Finally, make an update to branch1 on P4 side to check if it is imported
-# correctly by git-p4.
-test_expect_success 'git-p4 clone simple branches' '
-       test_when_finished cleanup_git &&
-       test_create_repo "$git" &&
-       cd "$git" &&
-       git config git-p4.branchList branch1:branch2 &&
-       git config --add git-p4.branchList branch1:branch3 &&
-       "$GITP4" clone --dest=. --detect-branches //depot@all &&
-       git log --all --graph --decorate --stat &&
-       git reset --hard p4/depot/branch1 &&
-       test -f file1 &&
-       test -f file2 &&
-       test -f file3 &&
-       grep -q update file2 &&
-       git reset --hard p4/depot/branch2 &&
-       test -f file1 &&
-       test -f file2 &&
-       test ! -f file3 &&
-       ! grep -q update file2 &&
-       git reset --hard p4/depot/branch3 &&
-       test -f file1 &&
-       test -f file2 &&
-       test -f file3 &&
-       grep -q update file2 &&
-       cd "$cli" &&
-       cd branch1 &&
-       p4 edit file2 &&
-       echo file2_ >>file2 &&
-       p4 submit -d "update file2 in branch1" &&
-       cd "$git" &&
-       git reset --hard p4/depot/branch1 &&
-       "$GITP4" rebase &&
-       grep -q file2_ file2
-'
-
-test_expect_success 'shutdown' '
-       pid=`pgrep -f p4d` &&
-       test -n "$pid" &&
-       test_debug "ps wl `echo $pid`" &&
-       kill $pid
-'
-
-test_done
diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh
new file mode 100755 (executable)
index 0000000..a25f18d
--- /dev/null
@@ -0,0 +1,233 @@
+#!/bin/sh
+
+test_description='git-p4 p4 branching tests'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+#
+# 1: //depot/main/f1
+# 2: //depot/main/f2
+# 3: integrate //depot/main/... -> //depot/branch1/...
+# 4: //depot/main/f4
+# 5: //depot/branch1/f5
+# .: named branch branch2
+# 6: integrate -b branch2
+# 7: //depot/branch2/f7
+# 8: //depot/main/f8
+#
+test_expect_success 'basic p4 branches' '
+       (
+               cd "$cli" &&
+               mkdir -p main &&
+
+               echo f1 >main/f1 &&
+               p4 add main/f1 &&
+               p4 submit -d "main/f1" &&
+
+               echo f2 >main/f2 &&
+               p4 add main/f2 &&
+               p4 submit -d "main/f2" &&
+
+               p4 integrate //depot/main/... //depot/branch1/... &&
+               p4 submit -d "integrate main to branch1" &&
+
+               echo f4 >main/f4 &&
+               p4 add main/f4 &&
+               p4 submit -d "main/f4" &&
+
+               echo f5 >branch1/f5 &&
+               p4 add branch1/f5 &&
+               p4 submit -d "branch1/f5" &&
+
+               p4 branch -i <<-EOF &&
+               Branch: branch2
+               View: //depot/main/... //depot/branch2/...
+               EOF
+
+               p4 integrate -b branch2 &&
+               p4 submit -d "integrate main to branch2" &&
+
+               echo f7 >branch2/f7 &&
+               p4 add branch2/f7 &&
+               p4 submit -d "branch2/f7" &&
+
+               echo f8 >main/f8 &&
+               p4 add main/f8 &&
+               p4 submit -d "main/f8"
+       )
+'
+
+test_expect_success 'import main, no branch detection' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot/main@all &&
+       (
+               cd "$git" &&
+               git log --oneline --graph --decorate --all &&
+               git rev-list master >wc &&
+               test_line_count = 4 wc
+       )
+'
+
+test_expect_success 'import branch1, no branch detection' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot/branch1@all &&
+       (
+               cd "$git" &&
+               git log --oneline --graph --decorate --all &&
+               git rev-list master >wc &&
+               test_line_count = 2 wc
+       )
+'
+
+test_expect_success 'import branch2, no branch detection' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot/branch2@all &&
+       (
+               cd "$git" &&
+               git log --oneline --graph --decorate --all &&
+               git rev-list master >wc &&
+               test_line_count = 2 wc
+       )
+'
+
+test_expect_success 'import depot, no branch detection' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot@all &&
+       (
+               cd "$git" &&
+               git log --oneline --graph --decorate --all &&
+               git rev-list master >wc &&
+               test_line_count = 8 wc
+       )
+'
+
+test_expect_success 'import depot, branch detection' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" --detect-branches //depot@all &&
+       (
+               cd "$git" &&
+
+               git log --oneline --graph --decorate --all &&
+
+               # 4 main commits
+               git rev-list master >wc &&
+               test_line_count = 4 wc &&
+
+               # 3 main, 1 integrate, 1 on branch2
+               git rev-list p4/depot/branch2 >wc &&
+               test_line_count = 5 wc &&
+
+               # no branch1, since no p4 branch created for it
+               test_must_fail git show-ref p4/depot/branch1
+       )
+'
+
+test_expect_success 'import depot, branch detection, branchList branch definition' '
+       test_when_finished cleanup_git &&
+       test_create_repo "$git" &&
+       (
+               cd "$git" &&
+               git config git-p4.branchList main:branch1 &&
+               "$GITP4" clone --dest=. --detect-branches //depot@all &&
+
+               git log --oneline --graph --decorate --all &&
+
+               # 4 main commits
+               git rev-list master >wc &&
+               test_line_count = 4 wc &&
+
+               # 3 main, 1 integrate, 1 on branch2
+               git rev-list p4/depot/branch2 >wc &&
+               test_line_count = 5 wc &&
+
+               # 2 main, 1 integrate, 1 on branch1
+               git rev-list p4/depot/branch1 >wc &&
+               test_line_count = 4 wc
+       )
+'
+
+test_expect_success 'restart p4d' '
+       kill_p4d &&
+       start_p4d
+'
+
+#
+# 1: //depot/branch1/file1
+#    //depot/branch1/file2
+# 2: integrate //depot/branch1/... -> //depot/branch2/...
+# 3: //depot/branch1/file3
+# 4: //depot/branch1/file2 (edit)
+# 5: integrate //depot/branch1/... -> //depot/branch3/...
+#
+## Create a simple branch structure in P4 depot.
+test_expect_success 'add simple p4 branches' '
+       (
+               cd "$cli" &&
+               mkdir branch1 &&
+               cd branch1 &&
+               echo file1 >file1 &&
+               echo file2 >file2 &&
+               p4 add file1 file2 &&
+               p4 submit -d "branch1" &&
+               p4 integrate //depot/branch1/... //depot/branch2/... &&
+               p4 submit -d "branch2" &&
+               echo file3 >file3 &&
+               p4 add file3 &&
+               p4 submit -d "add file3 in branch1" &&
+               p4 open file2 &&
+               echo update >>file2 &&
+               p4 submit -d "update file2 in branch1" &&
+               p4 integrate //depot/branch1/... //depot/branch3/... &&
+               p4 submit -d "branch3"
+       )
+'
+
+# Configure branches through git-config and clone them.
+# All files are tested to make sure branches were cloned correctly.
+# Finally, make an update to branch1 on P4 side to check if it is imported
+# correctly by git-p4.
+test_expect_success 'git-p4 clone simple branches' '
+       test_when_finished cleanup_git &&
+       test_create_repo "$git" &&
+       (
+               cd "$git" &&
+               git config git-p4.branchList branch1:branch2 &&
+               git config --add git-p4.branchList branch1:branch3 &&
+               "$GITP4" clone --dest=. --detect-branches //depot@all &&
+               git log --all --graph --decorate --stat &&
+               git reset --hard p4/depot/branch1 &&
+               test -f file1 &&
+               test -f file2 &&
+               test -f file3 &&
+               grep -q update file2 &&
+               git reset --hard p4/depot/branch2 &&
+               test -f file1 &&
+               test -f file2 &&
+               test ! -f file3 &&
+               test_must_fail grep -q update file2 &&
+               git reset --hard p4/depot/branch3 &&
+               test -f file1 &&
+               test -f file2 &&
+               test -f file3 &&
+               grep -q update file2 &&
+               cd "$cli" &&
+               cd branch1 &&
+               p4 edit file2 &&
+               echo file2_ >>file2 &&
+               p4 submit -d "update file2 in branch3" &&
+               cd "$git" &&
+               git reset --hard p4/depot/branch1 &&
+               "$GITP4" rebase &&
+               grep -q file2_ file2
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh
new file mode 100755 (executable)
index 0000000..992bb8c
--- /dev/null
@@ -0,0 +1,139 @@
+#!/bin/sh
+
+test_description='git-p4 p4 filetype tests'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'utf-16 file create' '
+       (
+               cd "$cli" &&
+
+               # p4 saves this verbatim
+               printf "three\nline\ntext\n" >f-ascii &&
+               p4 add -t text f-ascii &&
+
+               # p4 adds \377\376 header
+               cp f-ascii f-ascii-as-utf16 &&
+               p4 add -t utf16 f-ascii-as-utf16 &&
+
+               # p4 saves this exactly as iconv produced it
+               printf "three\nline\ntext\n" | iconv -f ascii -t utf-16 >f-utf16 &&
+               p4 add -t utf16 f-utf16 &&
+
+               # this also is unchanged
+               cp f-utf16 f-utf16-as-text &&
+               p4 add -t text f-utf16-as-text &&
+
+               p4 submit -d "f files" &&
+
+               # force update of client files
+               p4 sync -f
+       )
+'
+
+test_expect_success 'utf-16 file test' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot@all &&
+       (
+               cd "$git" &&
+
+               test_cmp "$cli/f-ascii" f-ascii &&
+               test_cmp "$cli/f-ascii-as-utf16" f-ascii-as-utf16 &&
+               test_cmp "$cli/f-utf16" f-utf16 &&
+               test_cmp "$cli/f-utf16-as-text" f-utf16-as-text
+       )
+'
+
+test_expect_success 'keyword file create' '
+       (
+               cd "$cli" &&
+
+               printf "id\n\$Id\$\n\$Author\$\ntext\n" >k-text-k &&
+               p4 add -t text+k k-text-k &&
+
+               cp k-text-k k-text-ko &&
+               p4 add -t text+ko k-text-ko &&
+
+               cat k-text-k | iconv -f ascii -t utf-16 >k-utf16-k &&
+               p4 add -t utf16+k k-utf16-k &&
+
+               cp k-utf16-k k-utf16-ko &&
+               p4 add -t utf16+ko k-utf16-ko &&
+
+               p4 submit -d "k files" &&
+               p4 sync -f
+       )
+'
+
+build_smush() {
+       cat >k_smush.py <<-\EOF &&
+       import re, sys
+       sys.stdout.write(re.sub(r'(?i)\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$', r'$\1$', sys.stdin.read()))
+       EOF
+       cat >ko_smush.py <<-\EOF
+       import re, sys
+       sys.stdout.write(re.sub(r'(?i)\$(Id|Header):[^$]*\$', r'$\1$', sys.stdin.read()))
+       EOF
+}
+
+test_expect_success 'keyword file test' '
+       build_smush &&
+       test_when_finished rm -f k_smush.py ko_smush.py &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot@all &&
+       (
+               cd "$git" &&
+
+               # text, ensure unexpanded
+               "$PYTHON_PATH" "$TRASH_DIRECTORY/k_smush.py" <"$cli/k-text-k" >cli-k-text-k-smush &&
+               test_cmp cli-k-text-k-smush k-text-k &&
+               "$PYTHON_PATH" "$TRASH_DIRECTORY/ko_smush.py" <"$cli/k-text-ko" >cli-k-text-ko-smush &&
+               test_cmp cli-k-text-ko-smush k-text-ko &&
+
+               # utf16, even though p4 expands keywords, git-p4 does not
+               # try to undo that
+               test_cmp "$cli/k-utf16-k" k-utf16-k &&
+               test_cmp "$cli/k-utf16-ko" k-utf16-ko
+       )
+'
+
+build_gendouble() {
+       cat >gendouble.py <<-\EOF
+       import sys
+       import struct
+       import array
+
+       s = array.array("c", '\0' * 26)
+       struct.pack_into(">L", s,  0, 0x00051607)  # AppleDouble
+       struct.pack_into(">L", s,  4, 0x00020000)  # version 2
+       s.tofile(sys.stdout)
+       EOF
+}
+
+test_expect_success 'ignore apple' '
+       test_when_finished rm -f gendouble.py &&
+       build_gendouble &&
+       (
+               cd "$cli" &&
+               test-genrandom apple 1024 >double.png &&
+               "$PYTHON_PATH" "$TRASH_DIRECTORY/gendouble.py" >%double.png &&
+               p4 add -t apple double.png &&
+               p4 submit -d appledouble
+       ) &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot@all &&
+       (
+               cd "$git" &&
+               test ! -f double.png
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9803-git-p4-shell-metachars.sh b/t/t9803-git-p4-shell-metachars.sh
new file mode 100755 (executable)
index 0000000..db04375
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+test_description='git-p4 transparency to shell metachars in filenames'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "file1"
+       )
+'
+
+test_expect_success 'shell metachars in filenames' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               echo f1 >foo\$bar &&
+               git add foo\$bar &&
+               echo f2 >"file with spaces" &&
+               git add "file with spaces" &&
+               git commit -m "add files" &&
+               P4EDITOR=touch "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               p4 sync ... &&
+               test -e "file with spaces" &&
+               test -e "foo\$bar"
+       )
+'
+
+test_expect_success 'deleting with shell metachars' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               git rm foo\$bar &&
+               git rm file\ with\ spaces &&
+               git commit -m "remove files" &&
+               P4EDITOR=touch "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               p4 sync ... &&
+               test ! -e "file with spaces" &&
+               test ! -e foo\$bar
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9805-git-p4-skip-submit-edit.sh b/t/t9805-git-p4-skip-submit-edit.sh
new file mode 100755 (executable)
index 0000000..df929e0
--- /dev/null
@@ -0,0 +1,104 @@
+#!/bin/sh
+
+test_description='git-p4 skipSubmitEdit config variables'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "change 1"
+       )
+'
+
+# this works because EDITOR is set to :
+test_expect_success 'no config, unedited, say yes' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               echo line >>file1 &&
+               git commit -a -m "change 2" &&
+               echo y | "$GITP4" submit &&
+               p4 changes //depot/... >wc &&
+               test_line_count = 2 wc
+       )
+'
+
+test_expect_success 'no config, unedited, say no' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               echo line >>file1 &&
+               git commit -a -m "change 3 (not really)" &&
+               printf "bad response\nn\n" | "$GITP4" submit &&
+               p4 changes //depot/... >wc &&
+               test_line_count = 2 wc
+       )
+'
+
+test_expect_success 'skipSubmitEdit' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               # will fail if editor is even invoked
+               git config core.editor /bin/false &&
+               echo line >>file1 &&
+               git commit -a -m "change 3" &&
+               "$GITP4" submit &&
+               p4 changes //depot/... >wc &&
+               test_line_count = 3 wc
+       )
+'
+
+test_expect_success 'skipSubmitEditCheck' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               echo line >>file1 &&
+               git commit -a -m "change 4" &&
+               "$GITP4" submit &&
+               p4 changes //depot/... >wc &&
+               test_line_count = 4 wc
+       )
+'
+
+# check the normal case, where the template really is edited
+test_expect_success 'no config, edited' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       ed="$TRASH_DIRECTORY/ed.sh" &&
+       test_when_finished "rm \"$ed\"" &&
+       cat >"$ed" <<-EOF &&
+               #!$SHELL_PATH
+               sleep 1
+               touch "\$1"
+               exit 0
+       EOF
+       chmod 755 "$ed" &&
+       (
+               cd "$git" &&
+               echo line >>file1 &&
+               git commit -a -m "change 5" &&
+               EDITOR="\"$ed\"" "$GITP4" submit &&
+               p4 changes //depot/... >wc &&
+               test_line_count = 5 wc
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9806-git-p4-options.sh b/t/t9806-git-p4-options.sh
new file mode 100755 (executable)
index 0000000..1f1952a
--- /dev/null
@@ -0,0 +1,170 @@
+#!/bin/sh
+
+test_description='git-p4 options'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "change 1" &&
+               echo file2 >file2 &&
+               p4 add file2 &&
+               p4 submit -d "change 2" &&
+               echo file3 >file3 &&
+               p4 add file3 &&
+               p4 submit -d "change 3"
+       )
+'
+
+test_expect_success 'clone no --git-dir' '
+       test_must_fail "$GITP4" clone --git-dir=xx //depot
+'
+
+test_expect_success 'clone --branch' '
+       "$GITP4" clone --branch=refs/remotes/p4/sb --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git ls-files >files &&
+               test_line_count = 0 files &&
+               test_path_is_file .git/refs/remotes/p4/sb
+       )
+'
+
+test_expect_success 'clone --changesfile' '
+       cf="$TRASH_DIRECTORY/cf" &&
+       test_when_finished "rm \"$cf\"" &&
+       printf "1\n3\n" >"$cf" &&
+       "$GITP4" clone --changesfile="$cf" --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git log --oneline p4/master >lines &&
+               test_line_count = 2 lines
+               test_path_is_file file1 &&
+               test_path_is_missing file2 &&
+               test_path_is_file file3
+       )
+'
+
+test_expect_success 'clone --changesfile, @all' '
+       cf="$TRASH_DIRECTORY/cf" &&
+       test_when_finished "rm \"$cf\"" &&
+       printf "1\n3\n" >"$cf" &&
+       test_must_fail "$GITP4" clone --changesfile="$cf" --dest="$git" //depot@all
+'
+
+# imports both master and p4/master in refs/heads
+# requires --import-local on sync to find p4 refs/heads
+# does not update master on sync, just p4/master
+test_expect_success 'clone/sync --import-local' '
+       "$GITP4" clone --import-local --dest="$git" //depot@1,2 &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git log --oneline refs/heads/master >lines &&
+               test_line_count = 2 lines &&
+               git log --oneline refs/heads/p4/master >lines &&
+               test_line_count = 2 lines &&
+               test_must_fail "$GITP4" sync &&
+
+               "$GITP4" sync --import-local &&
+               git log --oneline refs/heads/master >lines &&
+               test_line_count = 2 lines &&
+               git log --oneline refs/heads/p4/master >lines &&
+               test_line_count = 3 lines
+       )
+'
+
+test_expect_success 'clone --max-changes' '
+       "$GITP4" clone --dest="$git" --max-changes 2 //depot@all &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git log --oneline refs/heads/master >lines &&
+               test_line_count = 2 lines
+       )
+'
+
+test_expect_success 'clone --keep-path' '
+       (
+               cd "$cli" &&
+               mkdir -p sub/dir &&
+               echo f4 >sub/dir/f4 &&
+               p4 add sub/dir/f4 &&
+               p4 submit -d "change 4"
+       ) &&
+       "$GITP4" clone --dest="$git" --keep-path //depot/sub/dir@all &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               test_path_is_missing f4 &&
+               test_path_is_file sub/dir/f4
+       ) &&
+       cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot/sub/dir@all &&
+       (
+               cd "$git" &&
+               test_path_is_file f4 &&
+               test_path_is_missing sub/dir/f4
+       )
+'
+
+# clone --use-client-spec must still specify a depot path
+# if given, it should rearrange files according to client spec
+# when it has view lines that match the depot path
+# XXX: should clone/sync just use the client spec exactly, rather
+# than needing depot paths?
+test_expect_success 'clone --use-client-spec' '
+       (
+               # big usage message
+               exec >/dev/null &&
+               test_must_fail "$GITP4" clone --dest="$git" --use-client-spec
+       ) &&
+       cli2="$TRASH_DIRECTORY/cli2" &&
+       mkdir -p "$cli2" &&
+       test_when_finished "rmdir \"$cli2\"" &&
+       (
+               cd "$cli2" &&
+               p4 client -i <<-EOF
+               Client: client2
+               Description: client2
+               Root: $cli2
+               View: //depot/sub/... //client2/bus/...
+               EOF
+       ) &&
+       P4CLIENT=client2 &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" --use-client-spec //depot/... &&
+       (
+               cd "$git" &&
+               test_path_is_file bus/dir/f4 &&
+               test_path_is_file file1
+       ) &&
+       cleanup_git &&
+
+       # same thing again, this time with variable instead of option
+       mkdir "$git" &&
+       (
+               cd "$git" &&
+               git init &&
+               git config git-p4.useClientSpec true &&
+               "$GITP4" sync //depot/... &&
+               git checkout -b master p4/master &&
+               test_path_is_file bus/dir/f4 &&
+               test_path_is_file file1
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh
new file mode 100755 (executable)
index 0000000..b1f61e3
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='git-p4 submit'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "change 1"
+       )
+'
+
+test_expect_success 'submit with no client dir' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo file2 >file2 &&
+               git add file2 &&
+               git commit -m "git commit 2" &&
+               rm -rf "$cli" &&
+               git config git-p4.skipSubmitEdit true &&
+               "$GITP4" submit
+       )
+'
+
+# make two commits, but tell it to apply only from HEAD^
+test_expect_success 'submit --origin' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               test_commit "file3" &&
+               test_commit "file4" &&
+               git config git-p4.skipSubmitEdit true &&
+               "$GITP4" submit --origin=HEAD^
+       ) &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               test_path_is_missing "file3.t" &&
+               test_path_is_file "file4.t"
+       )
+'
+
+test_expect_success 'submit with allowSubmit' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               test_commit "file5" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.allowSubmit "nobranch" &&
+               test_must_fail "$GITP4" submit &&
+               git config git-p4.allowSubmit "nobranch,master" &&
+               "$GITP4" submit
+       )
+'
+
+test_expect_success 'submit with master branch name from argv' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               test_commit "file6" &&
+               git config git-p4.skipSubmitEdit true &&
+               test_must_fail "$GITP4" submit nobranch &&
+               git branch otherbranch &&
+               git reset --hard HEAD^ &&
+               test_commit "file7" &&
+               "$GITP4" submit otherbranch
+       ) &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               test_path_is_file "file6.t" &&
+               test_path_is_missing "file7.t"
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9808-git-p4-chdir.sh b/t/t9808-git-p4-chdir.sh
new file mode 100755 (executable)
index 0000000..f002283
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+test_description='git-p4 relative chdir'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "change 1"
+       )
+'
+
+# P4 reads from P4CONFIG file to find its server params, if the
+# environment variable is set
+test_expect_success 'P4CONFIG and absolute dir clone' '
+       printf "P4PORT=$P4PORT\nP4CLIENT=$P4CLIENT\n" >p4config &&
+       test_when_finished "rm \"$TRASH_DIRECTORY/p4config\"" &&
+       test_when_finished cleanup_git &&
+       (
+               P4CONFIG=p4config && export P4CONFIG &&
+               sane_unset P4PORT P4CLIENT &&
+               "$GITP4" clone --verbose --dest="$git" //depot
+       )
+'
+
+# same thing, but with relative directory name, note missing $ on --dest
+test_expect_success 'P4CONFIG and relative dir clone' '
+       printf "P4PORT=$P4PORT\nP4CLIENT=$P4CLIENT\n" >p4config &&
+       test_when_finished "rm \"$TRASH_DIRECTORY/p4config\"" &&
+       test_when_finished cleanup_git &&
+       (
+               P4CONFIG=p4config && export P4CONFIG &&
+               sane_unset P4PORT P4CLIENT &&
+               "$GITP4" clone --verbose --dest="git" //depot
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9809-git-p4-client-view.sh b/t/t9809-git-p4-client-view.sh
new file mode 100755 (executable)
index 0000000..b0c6d43
--- /dev/null
@@ -0,0 +1,427 @@
+#!/bin/sh
+
+test_description='git-p4 client view'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+#
+# Construct a client with this list of View lines
+#
+client_view() {
+       (
+               cat <<-EOF &&
+               Client: client
+               Description: client
+               Root: $cli
+               View:
+               EOF
+               for arg ; do
+                       printf "\t$arg\n"
+               done
+       ) | p4 client -i
+}
+
+#
+# Verify these files exist, exactly.  Caller creates
+# a list of files in file "files".
+#
+check_files_exist() {
+       ok=0 &&
+       num=$# &&
+       for arg ; do
+               test_path_is_file "$arg" &&
+               ok=$(($ok + 1))
+       done &&
+       test $ok -eq $num &&
+       test_line_count = $num files
+}
+
+#
+# Sync up the p4 client, make sure the given files (and only
+# those) exist.
+#
+client_verify() {
+       (
+               cd "$cli" &&
+               p4 sync &&
+               find . -type f ! -name files >files &&
+               check_files_exist "$@"
+       )
+}
+
+#
+# Make sure the named files, exactly, exist.
+#
+git_verify() {
+       (
+               cd "$git" &&
+               git ls-files >files &&
+               check_files_exist "$@"
+       )
+}
+
+# //depot
+#   - dir1
+#     - file11
+#     - file12
+#   - dir2
+#     - file21
+#     - file22
+init_depot() {
+       for d in 1 2 ; do
+               mkdir -p dir$d &&
+               for f in 1 2 ; do
+                       echo dir$d/file$d$f >dir$d/file$d$f &&
+                       p4 add dir$d/file$d$f &&
+                       p4 submit -d "dir$d/file$d$f"
+               done
+       done &&
+       find . -type f ! -name files >files &&
+       check_files_exist dir1/file11 dir1/file12 \
+                         dir2/file21 dir2/file22
+}
+
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               init_depot
+       )
+'
+
+# double % for printf
+test_expect_success 'unsupported view wildcard %%n' '
+       client_view "//depot/%%%%1/sub/... //client/sub/%%%%1/..." &&
+       test_when_finished cleanup_git &&
+       test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+'
+
+test_expect_success 'unsupported view wildcard *' '
+       client_view "//depot/*/bar/... //client/*/bar/..." &&
+       test_when_finished cleanup_git &&
+       test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+'
+
+test_expect_success 'wildcard ... only supported at end of spec' '
+       client_view "//depot/.../file11 //client/.../file11" &&
+       test_when_finished cleanup_git &&
+       test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+'
+
+test_expect_success 'basic map' '
+       client_view "//depot/dir1/... //client/cli1/..." &&
+       files="cli1/file11 cli1/file12" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'client view with no mappings' '
+       client_view &&
+       client_verify &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify
+'
+
+test_expect_success 'single file map' '
+       client_view "//depot/dir1/file11 //client/file11" &&
+       files="file11" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'later mapping takes precedence (entire repo)' '
+       client_view "//depot/dir1/... //client/cli1/..." \
+                   "//depot/... //client/cli2/..." &&
+       files="cli2/dir1/file11 cli2/dir1/file12
+              cli2/dir2/file21 cli2/dir2/file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'later mapping takes precedence (partial repo)' '
+       client_view "//depot/dir1/... //client/..." \
+                   "//depot/dir2/... //client/..." &&
+       files="file21 file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+# Reading the view backwards,
+#   dir2 goes to cli12
+#   dir1 cannot go to cli12 since it was filled by dir2
+#   dir1 also does not go to cli3, since the second rule
+#     noticed that it matched, but was already filled
+test_expect_success 'depot path matching rejected client path' '
+       client_view "//depot/dir1/... //client/cli3/..." \
+                   "//depot/dir1/... //client/cli12/..." \
+                   "//depot/dir2/... //client/cli12/..." &&
+       files="cli12/file21 cli12/file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+# since both have the same //client/..., the exclusion
+# rule keeps everything out
+test_expect_success 'exclusion wildcard, client rhs same (odd)' '
+       client_view "//depot/... //client/..." \
+                   "-//depot/dir2/... //client/..." &&
+       client_verify &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify
+'
+
+test_expect_success 'exclusion wildcard, client rhs different (normal)' '
+       client_view "//depot/... //client/..." \
+                   "-//depot/dir2/... //client/dir2/..." &&
+       files="dir1/file11 dir1/file12" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'exclusion single file' '
+       client_view "//depot/... //client/..." \
+                   "-//depot/dir2/file22 //client/file22" &&
+       files="dir1/file11 dir1/file12 dir2/file21" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'overlay wildcard' '
+       client_view "//depot/dir1/... //client/cli/..." \
+                   "+//depot/dir2/... //client/cli/...\n" &&
+       files="cli/file11 cli/file12 cli/file21 cli/file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'overlay single file' '
+       client_view "//depot/dir1/... //client/cli/..." \
+                   "+//depot/dir2/file21 //client/cli/file21" &&
+       files="cli/file11 cli/file12 cli/file21" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'exclusion with later inclusion' '
+       client_view "//depot/... //client/..." \
+                   "-//depot/dir2/... //client/dir2/..." \
+                   "//depot/dir2/... //client/dir2incl/..." &&
+       files="dir1/file11 dir1/file12 dir2incl/file21 dir2incl/file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'quotes on rhs only' '
+       client_view "//depot/dir1/... \"//client/cdir 1/...\"" &&
+       client_verify "cdir 1/file11" "cdir 1/file12" &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify "cdir 1/file11" "cdir 1/file12"
+'
+
+#
+# Submit tests
+#
+
+# clone sets variable
+test_expect_success 'clone --use-client-spec sets useClientSpec' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config --bool git-p4.useClientSpec >actual &&
+               echo true >true &&
+               test_cmp actual true
+       )
+'
+
+# clone just a subdir of the client spec
+test_expect_success 'subdir clone' '
+       client_view "//depot/... //client/..." &&
+       files="dir1/file11 dir1/file12 dir2/file21 dir2/file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       git_verify dir1/file11 dir1/file12
+'
+
+#
+# submit back, see what happens:  five cases
+#
+test_expect_success 'subdir clone, submit modify' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               echo line >>dir1/file12 &&
+               git add dir1/file12 &&
+               git commit -m dir1/file12 &&
+               "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file dir1/file12 &&
+               test_line_count = 2 dir1/file12
+       )
+'
+
+test_expect_success 'subdir clone, submit add' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               echo file13 >dir1/file13 &&
+               git add dir1/file13 &&
+               git commit -m dir1/file13 &&
+               "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file dir1/file13
+       )
+'
+
+test_expect_success 'subdir clone, submit delete' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git rm dir1/file12 &&
+               git commit -m "delete dir1/file12" &&
+               "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing dir1/file12
+       )
+'
+
+test_expect_success 'subdir clone, submit copy' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.detectCopies true &&
+               cp dir1/file11 dir1/file11a &&
+               git add dir1/file11a &&
+               git commit -m "copy to dir1/file11a" &&
+               "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file dir1/file11a
+       )
+'
+
+test_expect_success 'subdir clone, submit rename' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.detectRenames true &&
+               git mv dir1/file13 dir1/file13a &&
+               git commit -m "rename dir1/file13 to dir1/file13a" &&
+               "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing dir1/file13 &&
+               test_path_is_file dir1/file13a
+       )
+'
+
+test_expect_success 'reinit depot' '
+       (
+               cd "$cli" &&
+               p4 sync -f &&
+               rm files &&
+               p4 delete */* &&
+               p4 submit -d "delete all files" &&
+               init_depot
+       )
+'
+
+#
+# Rename directories to test quoting in depot-side mappings
+# //depot
+#    - "dir 1"
+#       - file11
+#       - file12
+#    - "dir 2"
+#       - file21
+#       - file22
+#
+test_expect_success 'rename files to introduce spaces' '
+       client_view "//depot/... //client/..." &&
+       client_verify dir1/file11 dir1/file12 \
+                     dir2/file21 dir2/file22 &&
+       (
+               cd "$cli" &&
+               p4 open dir1/... &&
+               p4 move dir1/... "dir 1"/... &&
+               p4 open dir2/... &&
+               p4 move dir2/... "dir 2"/... &&
+               p4 submit -d "rename with spaces"
+       ) &&
+       client_verify "dir 1/file11" "dir 1/file12" \
+                     "dir 2/file21" "dir 2/file22"
+'
+
+test_expect_success 'quotes on lhs only' '
+       client_view "\"//depot/dir 1/...\" //client/cdir1/..." &&
+       files="cdir1/file11 cdir1/file12" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       client_verify $files
+'
+
+test_expect_success 'quotes on both sides' '
+       client_view "\"//depot/dir 1/...\" \"//client/cdir 1/...\"" &&
+       client_verify "cdir 1/file11" "cdir 1/file12" &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify "cdir 1/file11" "cdir 1/file12"
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9901-git-web--browse.sh b/t/t9901-git-web--browse.sh
new file mode 100755 (executable)
index 0000000..b0a6bad
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+#
+
+test_description='git web--browse basic tests
+
+This test checks that git web--browse can handle various valid URLs.'
+
+. ./test-lib.sh
+
+test_web_browse () {
+       # browser=$1 url=$2
+       git web--browse --browser="$1" "$2" >actual &&
+       tr -d '\015' <actual >text &&
+       test_cmp expect text
+}
+
+test_expect_success \
+       'URL with an ampersand in it' '
+       echo http://example.com/foo\&bar >expect &&
+       git config browser.custom.cmd echo &&
+       test_web_browse custom http://example.com/foo\&bar
+'
+
+test_expect_success \
+       'URL with a semi-colon in it' '
+       echo http://example.com/foo\;bar >expect &&
+       git config browser.custom.cmd echo &&
+       test_web_browse custom http://example.com/foo\;bar
+'
+
+test_expect_success \
+       'URL with a hash in it' '
+       echo http://example.com/foo#bar >expect &&
+       git config browser.custom.cmd echo &&
+       test_web_browse custom http://example.com/foo#bar
+'
+
+test_expect_success \
+       'browser paths are properly quoted' '
+       echo fake: http://example.com/foo >expect &&
+       cat >"fake browser" <<-\EOF &&
+       #!/bin/sh
+       echo fake: "$@"
+       EOF
+       chmod +x "fake browser" &&
+       git config browser.w3m.path "`pwd`/fake browser" &&
+       test_web_browse w3m http://example.com/foo
+'
+
+test_expect_success \
+       'browser command allows arbitrary shell code' '
+       echo "arg: http://example.com/foo" >expect &&
+       git config browser.custom.cmd "
+               f() {
+                       for i in \"\$@\"; do
+                               echo arg: \$i
+                       done
+               }
+               f" &&
+       test_web_browse custom http://example.com/foo
+'
+
+test_done
index bdd9513b84301275330d3dd7e49af05081ef9cd7..c0d04c494ae5892b539eed13f9717db27d1ec860 100644 (file)
@@ -42,9 +42,11 @@ TZ=UTC
 TERM=dumb
 export LANG LC_ALL PAGER TERM TZ
 EDITOR=:
-unset VISUAL
-unset EMAIL
-unset $(perl -e '
+# A call to "unset" with no arguments causes at least Solaris 10
+# /usr/xpg4/bin/sh and /bin/ksh to bail out.  So keep the unsets
+# deriving from the command substitution clustered with the other
+# ones.
+unset VISUAL EMAIL LANGUAGE $(perl -e '
        my @env = keys %ENV;
        my $ok = join("|", qw(
                TRACE
@@ -191,6 +193,7 @@ then
 fi
 
 exec 5>&1
+exec 6<&0
 if test "$verbose" = "t"
 then
        exec 4>&2 3>&1
@@ -379,6 +382,20 @@ test_config () {
        git config "$@"
 }
 
+
+test_config_global () {
+       test_when_finished "test_unconfig --global '$1'" &&
+       git config --global "$@"
+}
+
+write_script () {
+       {
+               echo "#!${2-"$SHELL_PATH"}" &&
+               cat
+       } >"$1" &&
+       chmod +x "$1"
+}
+
 # Use test_set_prereq to tell that a particular prerequisite is available.
 # The prerequisite can later be checked for in two ways:
 #
@@ -469,7 +486,7 @@ test_debug () {
 test_eval_ () {
        # This is a separate function because some tests use
        # "return" to end a test_expect_success block early.
-       eval >&3 2>&4 "$*"
+       eval </dev/null >&3 2>&4 "$*"
 }
 
 test_run_ () {
@@ -1113,12 +1130,14 @@ esac
 test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PYTHON" && test_set_prereq PYTHON
 test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE
+test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
 
 # Can we rely on git's output in the C locale?
 if test -n "$GETTEXT_POISON"
 then
        GIT_GETTEXT_POISON=YesPlease
        export GIT_GETTEXT_POISON
+       test_set_prereq GETTEXT_POISON
 else
        test_set_prereq C_LOCALE_OUTPUT
 fi
index ee01eb957e97594ce24a61d4cac15a3a4e5a114d..10172aee18292b50aa24b3a8694b5383c1cfd98e 100755 (executable)
@@ -69,6 +69,10 @@ if ($#ARGV < 1) {
 }
 my $master_out = new IO::Pty;
 my $master_err = new IO::Pty;
+$master_out->set_raw();
+$master_err->set_raw();
+$master_out->slave->set_raw();
+$master_err->slave->set_raw();
 my $pid = start_child(\@ARGV, $master_out->slave, $master_err->slave);
 close $master_out->slave;
 close $master_err->slave;
diff --git a/tag.c b/tag.c
index 7d38cc0f4de1c16b5b52725ba7a6a361650a6b41..3aa186df628331e74e8a84d3cc2d313f4518a626 100644 (file)
--- a/tag.c
+++ b/tag.c
@@ -139,6 +139,11 @@ int parse_tag(struct tag *item)
        return ret;
 }
 
+/*
+ * Look at a signed tag object, and return the offset where
+ * the embedded detached signature begins, or the end of the
+ * data when there is no such signature.
+ */
 size_t parse_signature(const char *buf, unsigned long size)
 {
        char *eol;
diff --git a/test-credential.c b/test-credential.c
new file mode 100644 (file)
index 0000000..dee200e
--- /dev/null
@@ -0,0 +1,38 @@
+#include "cache.h"
+#include "credential.h"
+#include "string-list.h"
+
+static const char usage_msg[] =
+"test-credential <fill|approve|reject> [helper...]";
+
+int main(int argc, const char **argv)
+{
+       const char *op;
+       struct credential c = CREDENTIAL_INIT;
+       int i;
+
+       op = argv[1];
+       if (!op)
+               usage(usage_msg);
+       for (i = 2; i < argc; i++)
+               string_list_append(&c.helpers, argv[i]);
+
+       if (credential_read(&c, stdin) < 0)
+               die("unable to read credential from stdin");
+
+       if (!strcmp(op, "fill")) {
+               credential_fill(&c);
+               if (c.username)
+                       printf("username=%s\n", c.username);
+               if (c.password)
+                       printf("password=%s\n", c.password);
+       }
+       else if (!strcmp(op, "approve"))
+               credential_approve(&c);
+       else if (!strcmp(op, "reject"))
+               credential_reject(&c);
+       else
+               usage(usage_msg);
+
+       return 0;
+}
index 033c74911ed035ed36b16b0e2bf7327d6cdc5823..707a821f03d59b1b3685b380fa4f3e83535c5342 100644 (file)
@@ -1,78 +1,42 @@
 #include "cache.h"
 
+static int rc;
 
-static int test_isdigit(int c)
+static void report_error(const char *class, int ch)
 {
-       return isdigit(c);
+       printf("%s classifies char %d (0x%02x) wrongly\n", class, ch, ch);
+       rc = 1;
 }
 
-static int test_isspace(int c)
+static int is_in(const char *s, int ch)
 {
-       return isspace(c);
+       /* We can't find NUL using strchr.  It's classless anyway. */
+       if (ch == '\0')
+               return 0;
+       return !!strchr(s, ch);
 }
 
-static int test_isalpha(int c)
-{
-       return isalpha(c);
-}
-
-static int test_isalnum(int c)
-{
-       return isalnum(c);
-}
-
-static int test_is_glob_special(int c)
-{
-       return is_glob_special(c);
-}
-
-static int test_is_regex_special(int c)
-{
-       return is_regex_special(c);
+#define TEST_CLASS(t,s) {                      \
+       int i;                                  \
+       for (i = 0; i < 256; i++) {             \
+               if (is_in(s, i) != t(i))        \
+                       report_error(#t, i);    \
+       }                                       \
 }
 
 #define DIGIT "0123456789"
 #define LOWER "abcdefghijklmnopqrstuvwxyz"
 #define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 
-static const struct ctype_class {
-       const char *name;
-       int (*test_fn)(int);
-       const char *members;
-} classes[] = {
-       { "isdigit", test_isdigit, DIGIT },
-       { "isspace", test_isspace, " \n\r\t" },
-       { "isalpha", test_isalpha, LOWER UPPER },
-       { "isalnum", test_isalnum, LOWER UPPER DIGIT },
-       { "is_glob_special", test_is_glob_special, "*?[\\" },
-       { "is_regex_special", test_is_regex_special, "$()*+.?[\\^{|" },
-       { NULL }
-};
-
-static int test_class(const struct ctype_class *test)
-{
-       int i, rc = 0;
-
-       for (i = 0; i < 256; i++) {
-               int expected = i ? !!strchr(test->members, i) : 0;
-               int actual = test->test_fn(i);
-
-               if (actual != expected) {
-                       rc = 1;
-                       printf("%s classifies char %d (0x%02x) wrongly\n",
-                              test->name, i, i);
-               }
-       }
-       return rc;
-}
-
 int main(int argc, char **argv)
 {
-       const struct ctype_class *test;
-       int rc = 0;
-
-       for (test = classes; test->name; test++)
-               rc |= test_class(test);
+       TEST_CLASS(isdigit, DIGIT);
+       TEST_CLASS(isspace, " \n\r\t");
+       TEST_CLASS(isalpha, LOWER UPPER);
+       TEST_CLASS(isalnum, LOWER UPPER DIGIT);
+       TEST_CLASS(is_glob_special, "*?[\\");
+       TEST_CLASS(is_regex_special, "$()*+.?[\\^{|");
+       TEST_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
 
        return rc;
 }
index 1f73f1ea7dfa6a14dedf384c99751e86c8121ff4..e6c292385f9492ab8a58a693e854025a11b9b045 100644 (file)
@@ -59,6 +59,6 @@ int main(int ac, char **av)
        struct cache_tree *another = cache_tree();
        if (read_cache() < 0)
                die("unable to read index file");
-       cache_tree_update(another, active_cache, active_nr, 0, 1);
+       cache_tree_update(another, active_cache, active_nr, 0, 1, 0);
        return dump_cache_tree(active_cache_tree, another, "");
 }
index 91a5701657556e07b4f4daafa947346d3b197cd1..36487c402b264433a3bfda7f84ef9c9a8df8cbb7 100644 (file)
@@ -54,6 +54,7 @@ int main(int argc, const char **argv)
                OPT_STRING(0, "string2", &string, "str", "get another string"),
                OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"),
                OPT_STRING('o', NULL, &string, "str", "get another string"),
+               OPT_NOOP_NOARG(0, "obsolete"),
                OPT_SET_PTR(0, "default-string", &string,
                        "set string to default", (unsigned long)"default"),
                OPT_STRING_LIST(0, "list", &list, "str", "add str to list"),
diff --git a/test-scrap-cache-tree.c b/test-scrap-cache-tree.c
new file mode 100644 (file)
index 0000000..4728013
--- /dev/null
@@ -0,0 +1,17 @@
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+static struct lock_file index_lock;
+
+int main(int ac, char **av)
+{
+       int fd = hold_locked_index(&index_lock, 1);
+       if (read_cache() < 0)
+               die("unable to read index file");
+       active_cache_tree = NULL;
+       if (write_cache(fd, active_cache, active_nr)
+           || commit_lock_file(&index_lock))
+               die("unable to write index file");
+       return 0;
+}
index ab8c951c6eb39f54a9885dd15c173f974d7357d5..294d7ee2737099c94a7d768d47c6a316a6860cd7 100644 (file)
@@ -31,7 +31,7 @@ static void strtonode(struct int_node *item, const char *s)
 int main(int argc, char *argv[])
 {
        struct strbuf sb = STRBUF_INIT;
-       struct trp_root root = { ~0 };
+       struct trp_root root = { ~0U };
        uint32_t item;
 
        if (argc != 1)
index 07131261fefeab2066636a7f83bce05796329a54..f6b3b1fb79468ef85eb9581c7d44ff04c1bb61bc 100644 (file)
@@ -9,6 +9,7 @@
 #include "remote.h"
 #include "string-list.h"
 #include "thread-utils.h"
+#include "sigchain.h"
 
 static int debug;
 
@@ -220,15 +221,21 @@ static struct child_process *get_helper(struct transport *transport)
 static int disconnect_helper(struct transport *transport)
 {
        struct helper_data *data = transport->data;
-       struct strbuf buf = STRBUF_INIT;
        int res = 0;
 
        if (data->helper) {
                if (debug)
                        fprintf(stderr, "Debug: Disconnecting.\n");
                if (!data->no_disconnect_req) {
-                       strbuf_addf(&buf, "\n");
-                       sendline(data, &buf);
+                       /*
+                        * Ignore write errors; there's nothing we can do,
+                        * since we're about to close the pipe anyway. And the
+                        * most likely error is EPIPE due to the helper dying
+                        * to report an error itself.
+                        */
+                       sigchain_push(SIGPIPE, SIG_IGN);
+                       xwrite(data->helper->in, "\n", 1);
+                       sigchain_pop(SIGPIPE);
                }
                close(data->helper->in);
                close(data->helper->out);
@@ -446,8 +453,10 @@ static int fetch_with_import(struct transport *transport,
                        private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name);
                else
                        private = xstrdup(posn->name);
-               read_ref(private, posn->old_sha1);
-               free(private);
+               if (private) {
+                       read_ref(private, posn->old_sha1);
+                       free(private);
+               }
        }
        strbuf_release(&buf);
        return 0;
index e1940615e28ebc3b16f80b8f647f216031f64ee8..401b8dd35ce9acd88f38440fc52b908898119a81 100644 (file)
@@ -163,7 +163,7 @@ static void set_upstreams(struct transport *transport, struct ref *refs,
                /* Follow symbolic refs (mainly for HEAD). */
                localname = ref->peer_ref->name;
                remotename = ref->name;
-               tmp = resolve_ref(localname, sha, 1, &flag);
+               tmp = resolve_ref_unsafe(localname, sha, 1, &flag);
                if (tmp && flag & REF_ISSYMREF &&
                        !prefixcmp(tmp, "refs/heads/"))
                        localname = tmp;
@@ -215,7 +215,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
        rsync.argv = args;
        rsync.stdout_to_stderr = 1;
        args[0] = "rsync";
-       args[1] = (transport->verbose > 0) ? "-rv" : "-r";
+       args[1] = (transport->verbose > 1) ? "-rv" : "-r";
        args[2] = buf.buf;
        args[3] = temp_dir.buf;
        args[4] = NULL;
@@ -268,7 +268,7 @@ static int fetch_objs_via_rsync(struct transport *transport,
        rsync.argv = args;
        rsync.stdout_to_stderr = 1;
        args[0] = "rsync";
-       args[1] = (transport->verbose > 0) ? "-rv" : "-r";
+       args[1] = (transport->verbose > 1) ? "-rv" : "-r";
        args[2] = "--ignore-existing";
        args[3] = "--exclude";
        args[4] = "info";
@@ -351,7 +351,7 @@ static int rsync_transport_push(struct transport *transport,
        args[i++] = "-a";
        if (flags & TRANSPORT_PUSH_DRY_RUN)
                args[i++] = "--dry-run";
-       if (transport->verbose > 0)
+       if (transport->verbose > 1)
                args[i++] = "-v";
        args[i++] = "--ignore-existing";
        args[i++] = "--exclude";
@@ -474,8 +474,12 @@ static int set_git_option(struct git_transport_options *opts,
        } else if (!strcmp(name, TRANS_OPT_DEPTH)) {
                if (!value)
                        opts->depth = 0;
-               else
-                       opts->depth = atoi(value);
+               else {
+                       char *end;
+                       opts->depth = strtol(value, &end, 0);
+                       if (*end)
+                               die("transport: invalid depth option '%s'", value);
+               }
                return 0;
        }
        return 1;
@@ -502,7 +506,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
        struct ref *refs;
 
        connect_setup(transport, for_push, 0);
-       get_remote_heads(data->fd[0], &refs, 0, NULL,
+       get_remote_heads(data->fd[0], &refs,
                         for_push ? REF_NORMAL : 0, &data->extra_have);
        data->got_remote_heads = 1;
 
@@ -527,7 +531,7 @@ static int fetch_refs_via_pack(struct transport *transport,
        args.lock_pack = 1;
        args.use_thin_pack = data->options.thin;
        args.include_tag = data->options.followtags;
-       args.verbose = (transport->verbose > 0);
+       args.verbose = (transport->verbose > 1);
        args.quiet = (transport->verbose < 0);
        args.no_progress = !transport->progress;
        args.depth = data->options.depth;
@@ -537,7 +541,7 @@ static int fetch_refs_via_pack(struct transport *transport,
 
        if (!data->got_remote_heads) {
                connect_setup(transport, 0, 0);
-               get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL);
+               get_remote_heads(data->fd[0], &refs_tmp, 0, NULL);
                data->got_remote_heads = 1;
        }
 
@@ -755,18 +759,10 @@ void transport_verify_remote_names(int nr_heads, const char **heads)
                        continue;
 
                remote = remote ? (remote + 1) : local;
-               switch (check_ref_format(remote)) {
-               case 0: /* ok */
-               case CHECK_REF_FORMAT_ONELEVEL:
-                       /* ok but a single level -- that is fine for
-                        * a match pattern.
-                        */
-               case CHECK_REF_FORMAT_WILDCARD:
-                       /* ok but ends with a pattern-match character */
-                       continue;
-               }
-               die("remote part of refspec is not a valid name in %s",
-                   heads[i]);
+               if (check_refname_format(remote,
+                               REFNAME_ALLOW_ONELEVEL|REFNAME_REFSPEC_PATTERN))
+                       die("remote part of refspec is not a valid name in %s",
+                               heads[i]);
        }
 }
 
@@ -780,8 +776,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
                struct ref *tmp_refs;
                connect_setup(transport, 1, 0);
 
-               get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL,
-                                NULL);
+               get_remote_heads(data->fd[0], &tmp_refs, REF_NORMAL, NULL);
                data->got_remote_heads = 1;
        }
 
@@ -915,7 +910,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
                ret->fetch = fetch_objs_via_rsync;
                ret->push = rsync_transport_push;
                ret->smart_options = NULL;
-       } else if (is_local(url) && is_file(url)) {
+       } else if (is_local(url) && is_file(url) && is_bundle(url, 1)) {
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
                ret->get_refs_list = get_refs_from_bundle;
@@ -989,7 +984,7 @@ int transport_set_option(struct transport *transport,
 void transport_set_verbosity(struct transport *transport, int verbosity,
        int force_progress)
 {
-       if (verbosity >= 2)
+       if (verbosity >= 1)
                transport->verbose = verbosity <= 3 ? verbosity : 3;
        if (verbosity < 0)
                transport->verbose = -1;
@@ -998,11 +993,15 @@ void transport_set_verbosity(struct transport *transport, int verbosity,
         * Rules used to determine whether to report progress (processing aborts
         * when a rule is satisfied):
         *
-        *   1. Report progress, if force_progress is 1 (ie. --progress).
-        *   2. Don't report progress, if verbosity < 0 (ie. -q/--quiet ).
-        *   3. Report progress if isatty(2) is 1.
+        *   . Report progress, if force_progress is 1 (ie. --progress).
+        *   . Don't report progress, if force_progress is 0 (ie. --no-progress).
+        *   . Don't report progress, if verbosity < 0 (ie. -q/--quiet ).
+        *   . Report progress if isatty(2) is 1.
         **/
-       transport->progress = force_progress || (verbosity >= 0 && isatty(2));
+       if (force_progress >= 0)
+               transport->progress = !!force_progress;
+       else
+               transport->progress = verbosity >= 0 && isatty(2);
 }
 
 int transport_push(struct transport *transport,
@@ -1034,8 +1033,8 @@ int transport_push(struct transport *transport,
                if (flags & TRANSPORT_PUSH_MIRROR)
                        match_flags |= MATCH_REFS_MIRROR;
 
-               if (match_refs(local_refs, &remote_refs,
-                              refspec_nr, refspec, match_flags)) {
+               if (match_push_refs(local_refs, &remote_refs,
+                                   refspec_nr, refspec, match_flags)) {
                        return -1;
                }
 
index b3cc2e4753447d4734ed08a70e3396771257dced..28ad6db9ffa854c3ef9185ba85108e95edc44d51 100644 (file)
@@ -21,8 +21,8 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2,
        sha1 = tree_entry_extract(t1, &path1, &mode1);
        sha2 = tree_entry_extract(t2, &path2, &mode2);
 
-       pathlen1 = tree_entry_len(path1, sha1);
-       pathlen2 = tree_entry_len(path2, sha2);
+       pathlen1 = tree_entry_len(&t1->entry);
+       pathlen2 = tree_entry_len(&t2->entry);
        cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
        if (cmp < 0) {
                show_entry(opt, "-", t1, base);
@@ -64,14 +64,14 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2,
 static void show_tree(struct diff_options *opt, const char *prefix,
                      struct tree_desc *desc, struct strbuf *base)
 {
-       int match = 0;
+       enum interesting match = entry_not_interesting;
        for (; desc->size; update_tree_entry(desc)) {
-               if (match != 2) {
+               if (match != all_entries_interesting) {
                        match = tree_entry_interesting(&desc->entry, base, 0,
                                                       &opt->pathspec);
-                       if (match < 0)
+                       if (match == all_entries_not_interesting)
                                break;
-                       if (match == 0)
+                       if (match == entry_not_interesting)
                                continue;
                }
                show_entry(opt, prefix, desc, base);
@@ -85,7 +85,7 @@ static void show_entry(struct diff_options *opt, const char *prefix,
        unsigned mode;
        const char *path;
        const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
-       int pathlen = tree_entry_len(path, sha1);
+       int pathlen = tree_entry_len(&desc->entry);
        int old_baselen = base->len;
 
        strbuf_add(base, path, pathlen);
@@ -114,12 +114,13 @@ static void show_entry(struct diff_options *opt, const char *prefix,
 }
 
 static void skip_uninteresting(struct tree_desc *t, struct strbuf *base,
-                              struct diff_options *opt, int *match)
+                              struct diff_options *opt,
+                              enum interesting *match)
 {
        while (t->size) {
                *match = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec);
                if (*match) {
-                       if (*match < 0)
+                       if (*match == all_entries_not_interesting)
                                t->size = 0;
                        break;
                }
@@ -132,7 +133,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
 {
        struct strbuf base;
        int baselen = strlen(base_str);
-       int t1_match = 0, t2_match = 0;
+       enum interesting t1_match = entry_not_interesting;
+       enum interesting t2_match = entry_not_interesting;
 
        /* Enable recursion indefinitely */
        opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
@@ -207,6 +209,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diff_opts.single_follow = opt->pathspec.raw[0];
        diff_opts.break_opt = opt->break_opt;
+       diff_opts.rename_score = opt->rename_score;
        paths[0] = NULL;
        diff_tree_setup_paths(paths, &diff_opts);
        if (diff_setup_done(&diff_opts) < 0)
index 808bb55ba3f21d113e6358f6874e2e20cb33bbea..492c7cd744d5f0739c8393ab67705c0be1254783 100644 (file)
@@ -116,7 +116,7 @@ void setup_traverse_info(struct traverse_info *info, const char *base)
 
 char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n)
 {
-       int len = tree_entry_len(n->path, n->sha1);
+       int len = tree_entry_len(n);
        int pathlen = info->pathlen;
 
        path[pathlen + len] = 0;
@@ -126,7 +126,7 @@ char *make_traverse_path(char *path, const struct traverse_info *info, const str
                        break;
                path[--pathlen] = '/';
                n = &info->name;
-               len = tree_entry_len(n->path, n->sha1);
+               len = tree_entry_len(n);
                info = info->prev;
                pathlen -= len;
        }
@@ -253,7 +253,7 @@ static void extended_entry_extract(struct tree_desc_x *t,
         * The caller wants "first" from this tree, or nothing.
         */
        path = a->path;
-       len = tree_entry_len(a->path, a->sha1);
+       len = tree_entry_len(a);
        switch (check_entry_match(first, first_len, path, len)) {
        case -1:
                entry_clear(a);
@@ -271,7 +271,7 @@ static void extended_entry_extract(struct tree_desc_x *t,
        while (probe.size) {
                entry_extract(&probe, a);
                path = a->path;
-               len = tree_entry_len(a->path, a->sha1);
+               len = tree_entry_len(a);
                switch (check_entry_match(first, first_len, path, len)) {
                case -1:
                        entry_clear(a);
@@ -344,7 +344,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
                unsigned long mask, dirmask;
                const char *first = NULL;
                int first_len = 0;
-               struct name_entry *e;
+               struct name_entry *e = NULL;
                int len;
 
                for (i = 0; i < n; i++) {
@@ -362,7 +362,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
                        e = entry + i;
                        if (!e->path)
                                continue;
-                       len = tree_entry_len(e->path, e->sha1);
+                       len = tree_entry_len(e);
                        if (!first) {
                                first = e->path;
                                first_len = len;
@@ -381,7 +381,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
                                /* Cull the ones that are not the earliest */
                                if (!e->path)
                                        continue;
-                               len = tree_entry_len(e->path, e->sha1);
+                               len = tree_entry_len(e);
                                if (name_compare(e->path, len, first, first_len))
                                        entry_clear(e);
                        }
@@ -434,8 +434,8 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char
                int entrylen, cmp;
 
                sha1 = tree_entry_extract(t, &entry, mode);
+               entrylen = tree_entry_len(&t->entry);
                update_tree_entry(t);
-               entrylen = tree_entry_len(entry, sha1);
                if (entrylen > namelen)
                        continue;
                cmp = memcmp(name, entry, entrylen);
@@ -465,7 +465,6 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
        int retval;
        void *tree;
        unsigned long size;
-       struct tree_desc t;
        unsigned char root[20];
 
        tree = read_object_with_reference(tree_sha1, tree_type, &size, root);
@@ -478,8 +477,13 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
                return 0;
        }
 
-       init_tree_desc(&t, tree, size);
-       retval = find_tree_entry(&t, name, sha1, mode);
+       if (!size) {
+               retval = -1;
+       } else {
+               struct tree_desc t;
+               init_tree_desc(&t, tree, size);
+               retval = find_tree_entry(&t, name, sha1, mode);
+       }
        free(tree);
        return retval;
 }
@@ -549,7 +553,7 @@ static int match_entry(const struct name_entry *entry, int pathlen,
        return 0;
 }
 
-static int match_dir_prefix(const char *base, int baselen,
+static int match_dir_prefix(const char *base,
                            const char *match, int matchlen)
 {
        if (strncmp(base, match, matchlen))
@@ -573,30 +577,26 @@ static int match_dir_prefix(const char *base, int baselen,
  *
  * Pre-condition: either baselen == base_offset (i.e. empty path)
  * or base[baselen-1] == '/' (i.e. with trailing slash).
- *
- * Return:
- *  - 2 for "yes, and all subsequent entries will be"
- *  - 1 for yes
- *  - zero for no
- *  - negative for "no, and no subsequent entries will be either"
  */
-int tree_entry_interesting(const struct name_entry *entry,
-                          struct strbuf *base, int base_offset,
-                          const struct pathspec *ps)
+enum interesting tree_entry_interesting(const struct name_entry *entry,
+                                       struct strbuf *base, int base_offset,
+                                       const struct pathspec *ps)
 {
        int i;
        int pathlen, baselen = base->len - base_offset;
-       int never_interesting = ps->has_wildcard ? 0 : -1;
+       int never_interesting = ps->has_wildcard ?
+               entry_not_interesting : all_entries_not_interesting;
 
        if (!ps->nr) {
                if (!ps->recursive || ps->max_depth == -1)
-                       return 2;
-               return !!within_depth(base->buf + base_offset, baselen,
-                                     !!S_ISDIR(entry->mode),
-                                     ps->max_depth);
+                       return all_entries_interesting;
+               return within_depth(base->buf + base_offset, baselen,
+                                   !!S_ISDIR(entry->mode),
+                                   ps->max_depth) ?
+                       entry_interesting : entry_not_interesting;
        }
 
-       pathlen = tree_entry_len(entry->path, entry->sha1);
+       pathlen = tree_entry_len(entry);
 
        for (i = ps->nr - 1; i >= 0; i--) {
                const struct pathspec_item *item = ps->items+i;
@@ -606,42 +606,43 @@ int tree_entry_interesting(const struct name_entry *entry,
 
                if (baselen >= matchlen) {
                        /* If it doesn't match, move along... */
-                       if (!match_dir_prefix(base_str, baselen, match, matchlen))
+                       if (!match_dir_prefix(base_str, match, matchlen))
                                goto match_wildcards;
 
                        if (!ps->recursive || ps->max_depth == -1)
-                               return 2;
+                               return all_entries_interesting;
 
-                       return !!within_depth(base_str + matchlen + 1,
-                                             baselen - matchlen - 1,
-                                             !!S_ISDIR(entry->mode),
-                                             ps->max_depth);
+                       return within_depth(base_str + matchlen + 1,
+                                           baselen - matchlen - 1,
+                                           !!S_ISDIR(entry->mode),
+                                           ps->max_depth) ?
+                               entry_interesting : entry_not_interesting;
                }
 
-               /* Does the base match? */
-               if (!strncmp(base_str, match, baselen)) {
+               /* Either there must be no base, or the base must match. */
+               if (baselen == 0 || !strncmp(base_str, match, baselen)) {
                        if (match_entry(entry, pathlen,
                                        match + baselen, matchlen - baselen,
                                        &never_interesting))
-                               return 1;
+                               return entry_interesting;
 
-                       if (ps->items[i].use_wildcard) {
+                       if (item->use_wildcard) {
                                if (!fnmatch(match + baselen, entry->path, 0))
-                                       return 1;
+                                       return entry_interesting;
 
                                /*
                                 * Match all directories. We'll try to
                                 * match files later on.
                                 */
                                if (ps->recursive && S_ISDIR(entry->mode))
-                                       return 1;
+                                       return entry_interesting;
                        }
 
                        continue;
                }
 
 match_wildcards:
-               if (!ps->items[i].use_wildcard)
+               if (!item->use_wildcard)
                        continue;
 
                /*
@@ -653,16 +654,19 @@ match_wildcards:
 
                if (!fnmatch(match, base->buf + base_offset, 0)) {
                        strbuf_setlen(base, base_offset + baselen);
-                       return 1;
+                       return entry_interesting;
                }
                strbuf_setlen(base, base_offset + baselen);
 
                /*
                 * Match all directories. We'll try to match files
                 * later on.
+                * max_depth is ignored but we may consider support it
+                * in future, see
+                * http://thread.gmane.org/gmane.comp.version-control.git/163757/focus=163840
                 */
                if (ps->recursive && S_ISDIR(entry->mode))
-                       return 1;
+                       return entry_interesting;
        }
        return never_interesting; /* No matches */
 }
index 0089581e1dd55800302799a7381d4a7ad01bd79d..2bf0db9814a5c9f77fe00c97fa4f723986a7aac1 100644 (file)
@@ -20,9 +20,9 @@ static inline const unsigned char *tree_entry_extract(struct tree_desc *desc, co
        return desc->entry.sha1;
 }
 
-static inline int tree_entry_len(const char *name, const unsigned char *sha1)
+static inline int tree_entry_len(const struct name_entry *ne)
 {
-       return (const char *)sha1 - name - 1;
+       return (const char *)ne->sha1 - ne->path - 1;
 }
 
 void update_tree_entry(struct tree_desc *);
@@ -58,9 +58,19 @@ extern void setup_traverse_info(struct traverse_info *info, const char *base);
 
 static inline int traverse_path_len(const struct traverse_info *info, const struct name_entry *n)
 {
-       return info->pathlen + tree_entry_len(n->path, n->sha1);
+       return info->pathlen + tree_entry_len(n);
 }
 
-extern int tree_entry_interesting(const struct name_entry *, struct strbuf *, int, const struct pathspec *ps);
+/* in general, positive means "kind of interesting" */
+enum interesting {
+       all_entries_not_interesting = -1, /* no, and no subsequent entries will be either */
+       entry_not_interesting = 0,
+       entry_interesting = 1,
+       all_entries_interesting = 2 /* yes, and all subsequent entries will be */
+};
+
+extern enum interesting tree_entry_interesting(const struct name_entry *,
+                                              struct strbuf *, int,
+                                              const struct pathspec *ps);
 
 #endif
diff --git a/tree.c b/tree.c
index 698ecf7af13871cf9639e969f368ba5d7b2e940a..676e9f710ca8d5a568e0c6ea2fa88132da81b48c 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -52,7 +52,8 @@ static int read_tree_1(struct tree *tree, struct strbuf *base,
        struct tree_desc desc;
        struct name_entry entry;
        unsigned char sha1[20];
-       int len, retval = 0, oldlen = base->len;
+       int len, oldlen = base->len;
+       enum interesting retval = entry_not_interesting;
 
        if (parse_tree(tree))
                return -1;
@@ -60,11 +61,11 @@ static int read_tree_1(struct tree *tree, struct strbuf *base,
        init_tree_desc(&desc, tree->buffer, tree->size);
 
        while (tree_entry(&desc, &entry)) {
-               if (retval != 2) {
+               if (retval != all_entries_interesting) {
                        retval = tree_entry_interesting(&entry, base, 0, pathspec);
-                       if (retval < 0)
+                       if (retval == all_entries_not_interesting)
                                break;
-                       if (retval == 0)
+                       if (retval == entry_not_interesting)
                                continue;
                }
 
@@ -99,7 +100,7 @@ static int read_tree_1(struct tree *tree, struct strbuf *base,
                else
                        continue;
 
-               len = tree_entry_len(entry.path, entry.sha1);
+               len = tree_entry_len(&entry);
                strbuf_add(base, entry.path, len);
                strbuf_addch(base, '/');
                retval = read_tree_1(lookup_tree(sha1),
diff --git a/unix-socket.c b/unix-socket.c
new file mode 100644 (file)
index 0000000..01f119f
--- /dev/null
@@ -0,0 +1,122 @@
+#include "cache.h"
+#include "unix-socket.h"
+
+static int unix_stream_socket(void)
+{
+       int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+       if (fd < 0)
+               die_errno("unable to create socket");
+       return fd;
+}
+
+static int chdir_len(const char *orig, int len)
+{
+       char *path = xmemdupz(orig, len);
+       int r = chdir(path);
+       free(path);
+       return r;
+}
+
+struct unix_sockaddr_context {
+       char orig_dir[PATH_MAX];
+};
+
+static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)
+{
+       if (!ctx->orig_dir[0])
+               return;
+       /*
+        * If we fail, we can't just return an error, since we have
+        * moved the cwd of the whole process, which could confuse calling
+        * code.  We are better off to just die.
+        */
+       if (chdir(ctx->orig_dir) < 0)
+               die("unable to restore original working directory");
+}
+
+static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,
+                             struct unix_sockaddr_context *ctx)
+{
+       int size = strlen(path) + 1;
+
+       ctx->orig_dir[0] = '\0';
+       if (size > sizeof(sa->sun_path)) {
+               const char *slash = find_last_dir_sep(path);
+               const char *dir;
+
+               if (!slash) {
+                       errno = ENAMETOOLONG;
+                       return -1;
+               }
+
+               dir = path;
+               path = slash + 1;
+               size = strlen(path) + 1;
+               if (size > sizeof(sa->sun_path)) {
+                       errno = ENAMETOOLONG;
+                       return -1;
+               }
+
+               if (!getcwd(ctx->orig_dir, sizeof(ctx->orig_dir))) {
+                       errno = ENAMETOOLONG;
+                       return -1;
+               }
+               if (chdir_len(dir, slash - dir) < 0)
+                       return -1;
+       }
+
+       memset(sa, 0, sizeof(*sa));
+       sa->sun_family = AF_UNIX;
+       memcpy(sa->sun_path, path, size);
+       return 0;
+}
+
+int unix_stream_connect(const char *path)
+{
+       int fd, saved_errno;
+       struct sockaddr_un sa;
+       struct unix_sockaddr_context ctx;
+
+       if (unix_sockaddr_init(&sa, path, &ctx) < 0)
+               return -1;
+       fd = unix_stream_socket();
+       if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
+               goto fail;
+       unix_sockaddr_cleanup(&ctx);
+       return fd;
+
+fail:
+       saved_errno = errno;
+       unix_sockaddr_cleanup(&ctx);
+       close(fd);
+       errno = saved_errno;
+       return -1;
+}
+
+int unix_stream_listen(const char *path)
+{
+       int fd, saved_errno;
+       struct sockaddr_un sa;
+       struct unix_sockaddr_context ctx;
+
+       if (unix_sockaddr_init(&sa, path, &ctx) < 0)
+               return -1;
+       fd = unix_stream_socket();
+
+       unlink(path);
+       if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
+               goto fail;
+
+       if (listen(fd, 5) < 0)
+               goto fail;
+
+       unix_sockaddr_cleanup(&ctx);
+       return fd;
+
+fail:
+       saved_errno = errno;
+       unix_sockaddr_cleanup(&ctx);
+       close(fd);
+       errno = saved_errno;
+       return -1;
+}
diff --git a/unix-socket.h b/unix-socket.h
new file mode 100644 (file)
index 0000000..e271aee
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef UNIX_SOCKET_H
+#define UNIX_SOCKET_H
+
+int unix_stream_connect(const char *path);
+int unix_stream_listen(const char *path);
+
+#endif /* UNIX_SOCKET_H */
index 237aed8c766638c55d8d6b870c86ef2ad13a74d3..7c9ecf665d062d79e9208875d9bf2577e98f4fb2 100644 (file)
@@ -446,7 +446,7 @@ static int traverse_trees_recursive(int n, unsigned long dirmask,
        newinfo.prev = info;
        newinfo.pathspec = info->pathspec;
        newinfo.name = *p;
-       newinfo.pathlen += tree_entry_len(p->path, p->sha1) + 1;
+       newinfo.pathlen += tree_entry_len(p) + 1;
        newinfo.conflicts |= df_conflicts;
 
        for (i = 0; i < n; i++, dirmask >>= 1) {
@@ -495,7 +495,7 @@ static int do_compare_entry(const struct cache_entry *ce, const struct traverse_
        ce_len -= pathlen;
        ce_name = ce->name + pathlen;
 
-       len = tree_entry_len(n->path, n->sha1);
+       len = tree_entry_len(n);
        return df_name_compare(ce_name, ce_len, S_IFREG, n->path, len, n->mode);
 }
 
@@ -626,7 +626,7 @@ static int find_cache_pos(struct traverse_info *info,
        struct unpack_trees_options *o = info->data;
        struct index_state *index = o->src_index;
        int pfxlen = info->pathlen;
-       int p_len = tree_entry_len(p->path, p->sha1);
+       int p_len = tree_entry_len(p);
 
        for (pos = o->cache_bottom; pos < index->cache_nr; pos++) {
                struct cache_entry *ce = index->cache[pos];
@@ -1091,6 +1091,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                 */
                mark_new_skip_worktree(o->el, &o->result, CE_ADDED, CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
 
+               ret = 0;
                for (i = 0; i < o->result.cache_nr; i++) {
                        struct cache_entry *ce = o->result.cache[i];
 
@@ -1103,19 +1104,30 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                         * correct CE_NEW_SKIP_WORKTREE
                         */
                        if (ce->ce_flags & CE_ADDED &&
-                           verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
-                                       return -1;
+                           verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) {
+                               if (!o->show_all_errors)
+                                       goto return_failed;
+                               ret = -1;
+                       }
 
                        if (apply_sparse_checkout(ce, o)) {
+                               if (!o->show_all_errors)
+                                       goto return_failed;
                                ret = -1;
-                               goto done;
                        }
                        if (!ce_skip_worktree(ce))
                                empty_worktree = 0;
 
                }
+               if (ret < 0)
+                       goto return_failed;
+               /*
+                * Sparse checkout is meant to narrow down checkout area
+                * but it does not make sense to narrow down to empty working
+                * tree. This is usually a mistake in sparse checkout rules.
+                * Do not allow users to do that.
+                */
                if (o->result.cache_nr && empty_worktree) {
-                       /* dubious---why should this fail??? */
                        ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory");
                        goto done;
                }
index 8739bfacdfd4e01e16a614dd419388fa93a904b6..0d11e21a68b227101d6c7f6f4d7b305d08f77d95 100644 (file)
@@ -84,22 +84,11 @@ static void show_commit(struct commit *commit, void *data)
        commit->buffer = NULL;
 }
 
-static void show_object(struct object *obj, const struct name_path *path, const char *component)
+static void show_object(struct object *obj,
+                       const struct name_path *path, const char *component,
+                       void *cb_data)
 {
-       /* An object with name "foo\n0000000..." can be used to
-        * confuse downstream git-pack-objects very badly.
-        */
-       const char *name = path_name(path, component);
-       const char *ep = strchr(name, '\n');
-       if (ep) {
-               fprintf(pack_pipe, "%s %.*s\n", sha1_to_hex(obj->sha1),
-                      (int) (ep - name),
-                      name);
-       }
-       else
-               fprintf(pack_pipe, "%s %s\n",
-                               sha1_to_hex(obj->sha1), name);
-       free((char *)name);
+       show_object_with_name(pack_pipe, obj, path, component);
 }
 
 static void show_edge(struct commit *commit)
@@ -596,6 +585,7 @@ static void receive_needs(void)
                write_str_in_full(debug_fd, "#S\n");
        for (;;) {
                struct object *o;
+               const char *features;
                unsigned char sha1_buf[20];
                len = packet_read_line(0, line, sizeof(line));
                reset_timeout();
@@ -627,23 +617,26 @@ 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_detailed"))
+
+               features = line + 45;
+
+               if (parse_feature_request(features, "multi_ack_detailed"))
                        multi_ack = 2;
-               else if (strstr(line+45, "multi_ack"))
+               else if (parse_feature_request(features, "multi_ack"))
                        multi_ack = 1;
-               if (strstr(line+45, "no-done"))
+               if (parse_feature_request(features, "no-done"))
                        no_done = 1;
-               if (strstr(line+45, "thin-pack"))
+               if (parse_feature_request(features, "thin-pack"))
                        use_thin_pack = 1;
-               if (strstr(line+45, "ofs-delta"))
+               if (parse_feature_request(features, "ofs-delta"))
                        use_ofs_delta = 1;
-               if (strstr(line+45, "side-band-64k"))
+               if (parse_feature_request(features, "side-band-64k"))
                        use_sideband = LARGE_PACKET_MAX;
-               else if (strstr(line+45, "side-band"))
+               else if (parse_feature_request(features, "side-band"))
                        use_sideband = DEFAULT_PACKET_MAX;
-               if (strstr(line+45, "no-progress"))
+               if (parse_feature_request(features, "no-progress"))
                        no_progress = 1;
-               if (strstr(line+45, "include-tag"))
+               if (parse_feature_request(features, "include-tag"))
                        use_include_tag = 1;
 
                o = lookup_object(sha1_buf);
@@ -795,6 +788,8 @@ int main(int argc, char **argv)
        int i;
        int strict = 0;
 
+       git_setup_gettext();
+
        packet_trace_identity("upload-pack");
        git_extract_argv0_path(argv[0]);
        read_replace_refs = 0;
diff --git a/url.c b/url.c
index 3e06fd34c46a00b41f4843ea87a151314ce0789c..335d97d3f74e5b7d7139e223fbe1d19837a72e81 100644 (file)
--- a/url.c
+++ b/url.c
@@ -18,35 +18,15 @@ int is_urlschemechar(int first_flag, int ch)
 
 int is_url(const char *url)
 {
-       const char *url2, *first_slash;
-
-       if (!url)
-               return 0;
-       url2 = url;
-       first_slash = strchr(url, '/');
-
-       /* Input with no slash at all or slash first can't be URL. */
-       if (!first_slash || first_slash == url)
+       /* Is "scheme" part reasonable? */
+       if (!url || !is_urlschemechar(1, *url++))
                return 0;
-       /* Character before must be : and next must be /. */
-       if (first_slash[-1] != ':' || first_slash[1] != '/')
-               return 0;
-       /* There must be something before the :// */
-       if (first_slash == url + 1)
-               return 0;
-       /*
-        * Check all characters up to first slash - 1. Only alphanum
-        * is allowed.
-        */
-       url2 = url;
-       while (url2 < first_slash - 1) {
-               if (!is_urlschemechar(url2 == url, (unsigned char)*url2))
+       while (*url && *url != ':') {
+               if (!is_urlschemechar(0, *url++))
                        return 0;
-               url2++;
        }
-
-       /* Valid enough. */
-       return 1;
+       /* We've seen "scheme"; we want colon-slash-slash */
+       return (url[0] == ':' && url[1] == '/' && url[2] == '/');
 }
 
 static int url_decode_char(const char *q)
@@ -68,18 +48,20 @@ static int url_decode_char(const char *q)
        return val;
 }
 
-static char *url_decode_internal(const char **query, const char *stop_at,
-                                struct strbuf *out, int decode_plus)
+static char *url_decode_internal(const char **query, int len,
+                                const char *stop_at, struct strbuf *out,
+                                int decode_plus)
 {
        const char *q = *query;
 
-       do {
+       while (len) {
                unsigned char c = *q;
 
                if (!c)
                        break;
                if (stop_at && strchr(stop_at, c)) {
                        q++;
+                       len--;
                        break;
                }
 
@@ -88,6 +70,7 @@ static char *url_decode_internal(const char **query, const char *stop_at,
                        if (0 <= val) {
                                strbuf_addch(out, val);
                                q += 3;
+                               len -= 3;
                                continue;
                        }
                }
@@ -97,34 +80,41 @@ static char *url_decode_internal(const char **query, const char *stop_at,
                else
                        strbuf_addch(out, c);
                q++;
-       } while (1);
+               len--;
+       }
        *query = q;
        return strbuf_detach(out, NULL);
 }
 
 char *url_decode(const char *url)
+{
+       return url_decode_mem(url, strlen(url));
+}
+
+char *url_decode_mem(const char *url, int len)
 {
        struct strbuf out = STRBUF_INIT;
-       const char *colon = strchr(url, ':');
+       const char *colon = memchr(url, ':', len);
 
        /* Skip protocol part if present */
        if (colon && url < colon) {
                strbuf_add(&out, url, colon - url);
+               len -= colon - url;
                url = colon;
        }
-       return url_decode_internal(&url, NULL, &out, 0);
+       return url_decode_internal(&url, len, NULL, &out, 0);
 }
 
 char *url_decode_parameter_name(const char **query)
 {
        struct strbuf out = STRBUF_INIT;
-       return url_decode_internal(query, "&=", &out, 1);
+       return url_decode_internal(query, -1, "&=", &out, 1);
 }
 
 char *url_decode_parameter_value(const char **query)
 {
        struct strbuf out = STRBUF_INIT;
-       return url_decode_internal(query, "&", &out, 1);
+       return url_decode_internal(query, -1, "&", &out, 1);
 }
 
 void end_url_with_slash(struct strbuf *buf, const char *url)
diff --git a/url.h b/url.h
index 7100e3215a97b234c8ab93b76f7f86518f7c3382..abdaf6fa30b68767f48b056c977e498f9cfe7de2 100644 (file)
--- a/url.h
+++ b/url.h
@@ -4,6 +4,7 @@
 extern int is_url(const char *url);
 extern int is_urlschemechar(int first_flag, int ch);
 extern char *url_decode(const char *url);
+extern char *url_decode_mem(const char *url, int len);
 extern char *url_decode_parameter_name(const char **query);
 extern char *url_decode_parameter_value(const char **query);
 
index bf553ad91b55de8a762d56a6ffc6c86e959e878c..76109da4bcb7abc26ed20508692051a40e8addd7 100644 (file)
@@ -37,6 +37,9 @@ PATTERNS("java",
         "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
         "|[-+*/<>%&^|=!]="
         "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"),
+PATTERNS("matlab",
+        "^[[:space:]]*((classdef|function)[[:space:]].*)$|^%%[[:space:]].*$",
+        "[a-zA-Z_][a-zA-Z0-9_]*|[-+0-9.e]+|[=~<>]=|\\.[*/\\^']|\\|\\||&&"),
 PATTERNS("objc",
         /* Negate C statements that can look like functions */
         "!^[ \t]*(do|for|if|else|return|switch|while)\n"
@@ -115,7 +118,7 @@ PATTERNS("cpp",
         /* Jump targets or access declarations */
         "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n"
         /* C/++ functions/methods at top level */
-        "^([A-Za-z_][A-Za-z_0-9]*([ \t]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n"
+        "^([A-Za-z_][A-Za-z_0-9]*([ \t*]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n"
         /* compound type at top level */
         "^((struct|class|enum)[^;]*)$",
         /* -- */
index a21d89de97404479ecf2ca46824f61acb7af56e6..c3f198d29a4b90ee5959be750de0b764b5ea59e6 100644 (file)
@@ -109,7 +109,7 @@ static struct repo_dirent *repo_read_dirent(uint32_t revision,
 static void repo_write_dirent(const uint32_t *path, uint32_t mode,
                              uint32_t content_offset, uint32_t del)
 {
-       uint32_t name, revision, dir_o = ~0, parent_dir_o = ~0;
+       uint32_t name, revision, dir_o = ~0U, parent_dir_o = ~0U;
        struct repo_dir *dir;
        struct repo_dirent *key;
        struct repo_dirent *dent = NULL;
index 8af8d54d6ef41a2e84e500bd0d1a5523e59f8a32..1b63b19a383c570f69b9456c66170e592c49d9ba 100644 (file)
@@ -8,7 +8,7 @@
 #include "obj_pool.h"
 #include "string_pool.h"
 
-static struct trp_root tree = { ~0 };
+static struct trp_root tree = { ~0U };
 
 struct node {
        uint32_t offset;
@@ -78,7 +78,7 @@ void pool_print_seq(uint32_t len, uint32_t *seq, char delim, FILE *stream)
 uint32_t pool_tok_seq(uint32_t sz, uint32_t *seq, const char *delim, char *str)
 {
        char *context = NULL;
-       uint32_t token = ~0;
+       uint32_t token = ~0U;
        uint32_t length;
 
        if (sz == 0)
index dce7128daf8487a61e8d2f35cf15fca618964f86..be389dc9bf5161c31be29e3a72264fd6120a0bbc 100644 (file)
--- a/walker.c
+++ b/walker.c
@@ -190,7 +190,7 @@ static int interpret_target(struct walker *walker, char *target, unsigned char *
 {
        if (!get_sha1_hex(target, sha1))
                return 0;
-       if (!check_ref_format(target)) {
+       if (!check_refname_format(target, 0)) {
                struct ref *ref = alloc_ref(target);
                if (!walker->fetch_ref(walker, ref)) {
                        hashcpy(sha1, ref->old_sha1);
index 09feb1f7373367866668f3ac7c121e49bdde8096..53a8dd0a3f9a123eae16415d6bcd50b1fbcba6a4 100644 (file)
@@ -15,7 +15,8 @@ else
        export GIT_TEMPLATE_DIR
 fi
 GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib'
+GIT_TEXTDOMAINDIR='@@BUILD_DIR@@/po/build/locale'
 PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH"
-export GIT_EXEC_PATH GITPERLLIB PATH
+export GIT_EXEC_PATH GITPERLLIB PATH GIT_TEXTDOMAINDIR
 
 exec "${GIT_EXEC_PATH}/@@PROG@@" "$@"
index 70fdb76ff2b5100c6d20f9f3b758e1ac8df314e1..9ffc535f1ab0296fd0a3330b1f136d69f9ac9bb2 100644 (file)
@@ -111,7 +111,6 @@ void status_printf_more(struct wt_status *s, const char *color,
 void wt_status_prepare(struct wt_status *s)
 {
        unsigned char sha1[20];
-       const char *head;
 
        memset(s, 0, sizeof(*s));
        memcpy(s->color_palette, default_wt_status_colors,
@@ -119,8 +118,7 @@ void wt_status_prepare(struct wt_status *s)
        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->branch = resolve_refdup("HEAD", sha1, 0, NULL);
        s->reference = "HEAD";
        s->fp = stdout;
        s->index_file = get_index_file();
index 4beb10c678702a34b14914f8b292dd65b5c273cd..00d36c3ac7a7642831d2ffc49647caf77a4d066c 100644 (file)
@@ -43,6 +43,7 @@ extern "C" {
 
 #define XDL_EMIT_FUNCNAMES (1 << 0)
 #define XDL_EMIT_COMMON (1 << 1)
+#define XDL_EMIT_FUNCCONTEXT (1 << 2)
 
 #define XDL_MMB_READONLY (1 << 0)
 
index 277e2eec5b4cced5882f37ac42af7f22efe92902..2e669c3e2570332ee50a72e06a77ad4a356ecfd6 100644 (file)
@@ -100,14 +100,40 @@ static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
        return 0;
 }
 
+struct func_line {
+       long len;
+       char buf[80];
+};
+
+static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
+                         struct func_line *func_line, long start, long limit)
+{
+       find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff;
+       long l, size, step = (start > limit) ? -1 : 1;
+       char *buf, dummy[1];
+
+       buf = func_line ? func_line->buf : dummy;
+       size = func_line ? sizeof(func_line->buf) : sizeof(dummy);
+
+       for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) {
+               const char *rec;
+               long reclen = xdl_get_rec(&xe->xdf1, l, &rec);
+               long len = ff(rec, reclen, buf, size, xecfg->find_func_priv);
+               if (len >= 0) {
+                       if (func_line)
+                               func_line->len = len;
+                       return l;
+               }
+       }
+       return -1;
+}
+
 int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                  xdemitconf_t const *xecfg) {
        long s1, s2, e1, e2, lctx;
        xdchange_t *xch, *xche;
-       char funcbuf[80];
-       long funclen = 0;
        long funclineprev = -1;
-       find_func_t ff = xecfg->find_func ?  xecfg->find_func : def_ff;
+       struct func_line func_line = { 0 };
 
        if (xecfg->flags & XDL_EMIT_COMMON)
                return xdl_emit_common(xe, xscr, ecb, xecfg);
@@ -118,6 +144,17 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
                s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
 
+               if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+                       long fs1 = get_func_line(xe, xecfg, NULL, xch->i1, -1);
+                       if (fs1 < 0)
+                               fs1 = 0;
+                       if (fs1 < s1) {
+                               s2 -= s1 - fs1;
+                               s1 = fs1;
+                       }
+               }
+
+ again:
                lctx = xecfg->ctxlen;
                lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1));
                lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2));
@@ -125,27 +162,43 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                e1 = xche->i1 + xche->chg1 + lctx;
                e2 = xche->i2 + xche->chg2 + lctx;
 
+               if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+                       long fe1 = get_func_line(xe, xecfg, NULL,
+                                                xche->i1 + xche->chg1,
+                                                xe->xdf1.nrec);
+                       if (fe1 < 0)
+                               fe1 = xe->xdf1.nrec;
+                       if (fe1 > e1) {
+                               e2 += fe1 - e1;
+                               e1 = fe1;
+                       }
+
+                       /*
+                        * Overlap with next change?  Then include it
+                        * in the current hunk and start over to find
+                        * its new end.
+                        */
+                       if (xche->next) {
+                               long l = xche->next->i1;
+                               if (l <= e1 ||
+                                   get_func_line(xe, xecfg, NULL, l, e1) < 0) {
+                                       xche = xche->next;
+                                       goto again;
+                               }
+                       }
+               }
+
                /*
                 * Emit current hunk header.
                 */
 
                if (xecfg->flags & XDL_EMIT_FUNCNAMES) {
-                       long l;
-                       for (l = s1 - 1; l >= 0 && l > funclineprev; l--) {
-                               const char *rec;
-                               long reclen = xdl_get_rec(&xe->xdf1, l, &rec);
-                               long newfunclen = ff(rec, reclen, funcbuf,
-                                                    sizeof(funcbuf),
-                                                    xecfg->find_func_priv);
-                               if (newfunclen >= 0) {
-                                       funclen = newfunclen;
-                                       break;
-                               }
-                       }
+                       get_func_line(xe, xecfg, &func_line,
+                                     s1 - 1, funclineprev);
                        funclineprev = s1 - 1;
                }
                if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2,
-                                     funcbuf, funclen, ecb) < 0)
+                                     func_line.buf, func_line.len, ecb) < 0)
                        return -1;
 
                /*
diff --git a/zlib.c b/zlib.c
index 3c63d480c75e9939fb3a047f595b032e9509d681..2b2c0c780e3fca6217c688298053a01aad724505 100644 (file)
--- a/zlib.c
+++ b/zlib.c
@@ -188,13 +188,20 @@ void git_deflate_init_gzip(git_zstream *strm, int level)
            strm->z.msg ? strm->z.msg : "no message");
 }
 
-void git_deflate_end(git_zstream *strm)
+int git_deflate_abort(git_zstream *strm)
 {
        int status;
 
        zlib_pre_call(strm);
        status = deflateEnd(&strm->z);
        zlib_post_call(strm);
+       return status;
+}
+
+void git_deflate_end(git_zstream *strm)
+{
+       int status = git_deflate_abort(strm);
+
        if (status == Z_OK)
                return;
        error("deflateEnd: %s (%s)", zerr_to_string(status),