Code

Merge branch 'jk/git-dir-lookup'
authorJunio C Hamano <gitster@pobox.com>
Tue, 14 Feb 2012 20:57:18 +0000 (12:57 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 14 Feb 2012 20:57:18 +0000 (12:57 -0800)
* jk/git-dir-lookup:
  standardize and improve lookup rules for external local repos

524 files changed:
.gitignore
Documentation/CodingGuidelines
Documentation/Makefile
Documentation/RelNotes/1.7.10.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.6.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.6.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.7.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.7.6.txt [new file with mode: 0644]
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.9.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.9.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/git-am.txt
Documentation/git-branch.txt
Documentation/git-clone.txt
Documentation/git-commit-tree.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-fsck.txt
Documentation/git-grep.txt
Documentation/git-mailinfo.txt
Documentation/git-merge.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-sh-setup.txt
Documentation/git-show-ref.txt
Documentation/git-stripspace.txt
Documentation/git-submodule.txt
Documentation/git-symbolic-ref.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/howto/using-signed-tag-in-pull-request.txt [new file with mode: 0644]
Documentation/merge-options.txt
Documentation/pretty-formats.txt
Documentation/technical/api-credentials.txt [new file with mode: 0644]
GIT-VERSION-GEN
INSTALL
Makefile
RelNotes
advice.c
advice.h
archive.c
attr.c
branch.c
branch.h
builtin.h
builtin/add.c
builtin/apply.c
builtin/blame.c
builtin/branch.c
builtin/cat-file.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/mailinfo.c
builtin/merge.c
builtin/mv.c
builtin/notes.c
builtin/pack-objects.c
builtin/prune.c
builtin/receive-pack.c
builtin/reflog.c
builtin/remote.c
builtin/replace.c
builtin/reset.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
cache-tree.c
cache-tree.h
cache.h
combine-diff.c
commit.c
commit.h
compat/inet_ntop.c
compat/inet_pton.c
compat/mingw.c
compat/mingw.h
compat/msvc.h
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]
config.c
config.mak.in
configure.ac
connect.c
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/fast-import/git-p4
contrib/fast-import/git-p4.txt [deleted file]
contrib/svn-fe/svn-fe.txt
convert.c
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-lib.c
diff.c
diff.h
dir.c
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-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
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-mergetool.sh
git-pull.sh
git-rebase--interactive.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.c
git.spec.in
gitk-git/gitk
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
http-backend.c
http-fetch.c
http-push.c
http.c
http.h
imap-send.c
list-objects.c
log-tree.c
mailmap.c
merge-recursive.c
mergetools/meld
notes-cache.c
notes-merge.c
notes-merge.h
object.c
pack-check.c
pack-refs.c
pack-write.c
pack.h
perl/.gitignore
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
revision.c
revision.h
run-command.c
run-command.h
sequencer.c
sequencer.h
sha1_file.c
shell.c
show-index.c
strbuf.c
strbuf.h
submodule.c
t/Makefile
t/README
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-daemon.sh [new file with mode: 0644]
t/lib-httpd/apache.conf
t/t0003-attributes.sh
t/t0080-vcs-svn.sh [deleted file]
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/t1013-loose-object-format.sh
t/t1050-large.sh
t/t1200-tutorial.sh
t/t1300-repo-config.sh
t/t1412-reflog-loop.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/t2023-checkout-m.sh [new file with mode: 0755]
t/t2203-add-intent.sh
t/t3030-merge-recursive.sh
t/t3040-subprojects-basic.sh
t/t3200-branch.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/t3508-cherry-pick-many-commits.sh
t/t3510-cherry-pick-sequence.sh
t/t3900-i18n-commit.sh
t/t3903-stash.sh
t/t3904-stash-patch.sh
t/t3905-stash-include-untracked.sh
t/t4010-diff-pathspec.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/t4015-diff-whitespace.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/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/t5150-request-pull.sh
t/t5300-pack-object.sh
t/t5302-pack-index.sh
t/t5500-fetch-pack.sh
t/t5510-fetch.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/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/t5570-git-daemon.sh [new file with mode: 0755]
t/t5601-clone.sh
t/t5700-clone-reference.sh
t/t5704-bundle.sh
t/t5706-clone-branch.sh
t/t6006-rev-list-format.sh
t/t6012-rev-list-simplify.sh
t/t6032-merge-large-rename.sh
t/t7004-tag.sh
t/t7006-pager.sh
t/t7008-grep-binary.sh
t/t7106-reset-sequence.sh [deleted file]
t/t7406-submodule-update.sh
t/t7501-commit.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/t7810-grep.sh
t/t8006-blame-textconv.sh
t/t9010-svn-fe.sh
t/t9011-svn-da.sh [new file with mode: 0755]
t/t9130-git-svn-authors-file.sh
t/t9200-git-cvsexportcommit.sh
t/t9301-fast-import-notes.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9800-git-p4-basic.sh [new file with mode: 0755]
t/t9800-git-p4.sh [deleted file]
t/t9801-git-p4-branch.sh
t/t9803-git-p4-shell-metachars.sh [new file with mode: 0755]
t/t9803-git-shell-metachars.sh [deleted file]
t/t9804-git-p4-label.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/test-lib.sh
t/test-terminal.perl
tag.c
tag.h
test-credential.c [new file with mode: 0644]
test-dump-cache-tree.c
test-obj-pool.c [deleted file]
test-scrap-cache-tree.c [new file with mode: 0644]
test-string-pool.c [deleted file]
test-svn-fe.c
test-treap.c [deleted file]
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
userdiff.c
vcs-svn/LICENSE
vcs-svn/fast_export.c
vcs-svn/fast_export.h
vcs-svn/line_buffer.c
vcs-svn/line_buffer.h
vcs-svn/obj_pool.h [deleted file]
vcs-svn/repo_tree.c
vcs-svn/repo_tree.h
vcs-svn/sliding_window.c [new file with mode: 0644]
vcs-svn/sliding_window.h [new file with mode: 0644]
vcs-svn/string_pool.c [deleted file]
vcs-svn/string_pool.h [deleted file]
vcs-svn/string_pool.txt [deleted file]
vcs-svn/svndiff.c [new file with mode: 0644]
vcs-svn/svndiff.h [new file with mode: 0644]
vcs-svn/svndump.c
vcs-svn/trp.h [deleted file]
vcs-svn/trp.txt [deleted file]
wrap-for-bin.sh
wt-status.c
xdiff/xemit.c
zlib.c

index 8572c8c0b0199589a8c1875825f5b2e7e4dc4a86..87fcc5f6ff2e180280ff767fd291247739c7d0fa 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
 /test-match-trees
 /test-mktemp
-/test-obj-pool
 /test-parse-options
 /test-path-utils
 /test-run-command
 /test-sha1
 /test-sigchain
-/test-string-pool
 /test-subprocess
 /test-svn-fe
-/test-treap
 /common-cmds.h
 *.tar.gz
 *.dsc
index fe1c1e5bc26e683540cb9fe5f43320192be9185d..483008699f923be17926e8ed938ae17868f6ddf5 100644 (file)
@@ -81,6 +81,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 +148,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 304b31edee2e5c4e998c48cc571c66e8264cc581..d40e211f22dbe6c102a102041ac9f0779c7cb837 100644 (file)
@@ -7,6 +7,7 @@ MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.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.10.txt b/Documentation/RelNotes/1.7.10.txt
new file mode 100644 (file)
index 0000000..be3001a
--- /dev/null
@@ -0,0 +1,104 @@
+Git v1.7.10 Release Notes
+=========================
+
+Updates since v1.7.9
+--------------------
+
+UI, Workflows & Features
+
+ * Improved handling of views, labels and branches in git-p4 (in contrib).
+
+ * Updated command line arguments completion script for zsh (in contrib).
+
+ * "vcs-svn"/"svn-fe" learned to read dumps with svn-deltas and
+   support incremental imports.
+
+ * "git am" learned to pass "-b" option to underlying "git mailinfo", so
+   that bracketed string other than "PATCH" at the beginning can be kept.
+
+ * "git clone" learned "--single-branch" option to limit cloning to a
+   single branch (surprise!).
+
+ * "git clone" learned to detach the HEAD in the resulting repository
+   when the source repository's HEAD does not point to a branch.
+
+ * The commands in the "git diff" family and "git apply --stat" that
+   count the number of files changed and the number of lines
+   inserted/deleted have been updated to match the output from
+   "diffstat".  This also opens the door to i18n this line.
+
+ * When showing a patch while ignoring whitespace changes, the context
+   lines are taken from the postimage, in order to make it easier to
+   view the output.
+
+ * "git merge" in an interactive session learned to spawn the editor
+   by default to let the user edit the auto-generated merge message,
+   to encourage people to explain their merges better. Legacy scripts
+   can export MERGE_AUTOEDIT=no to retain the historical behaviour.
+
+ * "gitweb" allows intermediate entries in the directory hierarchy
+   that leads to a projects to be clicked, which in turn shows the
+   list of projects inside that directory.
+
+Performance
+
+ * During "git upload-pack" in respose to "git fetch", unnecessary calls
+   to parse_object() have been eliminated, to help performance in
+   repositories with excessive number of refs.
+
+Internal Implementation
+
+ * Recursive call chains in "git index-pack" to deal with long delta
+   chains have been flattened, to reduce the stack footprint.
+
+ * Use of add_extra_ref() API is slowly getting removed, to make it
+   possible to cleanly restructure the overall refs API.
+
+ * The test suite supports the new "test_pause" helper function.
+
+ * t/Makefile is adjusted to prevent newer versions of GNU make from
+   running tests in seemingly random order.
+
+Also contains minor documentation updates and code clean-ups.
+
+
+Fixes since v1.7.9
+------------------
+
+Unless otherwise noted, all the fixes since v1.7.9 in the maintenance
+releases are contained in this release (see release notes to them for
+details).
+
+ * The error message emitted when we see an empty loose object was
+   not phrased correctly.
+   (merge 33e42de mm/empty-loose-error-message later to maint).
+
+ * "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.
+   (merge 3f6d56d jc/maint-commit-ignore-i-t-a later to maint).
+
+ * Search box in "gitweb" did not accept non-ASCII characters correctly.
+   (merge 84d9e2d jn/gitweb-search-utf-8 later to maint).
+
+ * 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).
+   (merge 84d7273 jk/prompt-fallback-to-tty later to maint).
+
+ * 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.
+   (merge f026358 jc/maint-mailmap-output later to maint).
+
+ * "checkout -b" did not allow switching out of an unborn branch.
+   (merge abe1998 jc/checkout-out-of-unborn later to maint).
+
+---
+exec >/var/tmp/1
+O=v1.7.9-249-gaa47ec9
+echo O=$(git describe)
+git log --first-parent --oneline ^maint $O..
+echo
+git shortlog --no-merges ^maint $O..
diff --git a/Documentation/RelNotes/1.7.6.5.txt b/Documentation/RelNotes/1.7.6.5.txt
new file mode 100644 (file)
index 0000000..6713132
--- /dev/null
@@ -0,0 +1,26 @@
+Git v1.7.6.5 Release Notes
+==========================
+
+Fixes since v1.7.6.4
+--------------------
+
+ * The date parser did not accept timezone designators that lack minutes
+   part and also has a colon between "hh:mm".
+
+ * After fetching from a remote that has very long refname, the reporting
+   output could have corrupted by overrunning a static buffer.
+
+ * "git mergetool" did not use its arguments as pathspec, but as a path to
+   the file that may not even have any conflict.
+
+ * "git name-rev --all" tried to name all _objects_, naturally failing to
+   describe many blobs and trees, instead of showing only commits as
+   advertised in its documentation.
+
+ * "git remote rename $a $b" were not careful to match the remote name
+   against $a (i.e. source side of the remote nickname).
+
+ * "gitweb" used to produce a non-working link while showing the contents
+   of a blob, when JavaScript actions are enabled.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.6.6.txt b/Documentation/RelNotes/1.7.6.6.txt
new file mode 100644 (file)
index 0000000..5343e00
--- /dev/null
@@ -0,0 +1,16 @@
+Git v1.7.6.6 Release Notes
+==========================
+
+Fixes since v1.7.6.5
+--------------------
+
+ * 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.
+
+ * 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.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.7.5.txt b/Documentation/RelNotes/1.7.7.5.txt
new file mode 100644 (file)
index 0000000..7b09319
--- /dev/null
@@ -0,0 +1,14 @@
+Git v1.7.7.5 Release Notes
+==========================
+
+Fixes since v1.7.7.4
+--------------------
+
+ * After fetching from a remote that has very long refname, the reporting
+   output could have corrupted by overrunning a static buffer.
+
+ * "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.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.7.6.txt b/Documentation/RelNotes/1.7.7.6.txt
new file mode 100644 (file)
index 0000000..8df606d
--- /dev/null
@@ -0,0 +1,20 @@
+Git v1.7.7.6 Release Notes
+==========================
+
+Fixes since v1.7.7.5
+--------------------
+
+ * 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.
+
+Also contains minor fixes and documentation updates.
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.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.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 5a841da6d4cd86c4340abf001f735a37035d198f..abeb82b2c6d40e8557f7a5f8ad4c5e98b3a26a62 100644 (file)
@@ -115,35 +115,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::
@@ -677,10 +674,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
@@ -832,6 +831,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::
@@ -1101,6 +1123,17 @@ grep.lineNumber::
 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.
@@ -1590,6 +1623,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.
@@ -1740,10 +1783,11 @@ rerere.autoupdate::
 
 rerere.enabled::
        Activate recording of resolved conflicts, so that identical
-       conflict hunks can be resolved automatically, should they
-       be encountered again.  linkgit:git-rerere[1] command is by
-       default enabled if you create `rr-cache` directory under
-       `$GIT_DIR`, but can be disabled by setting this option to false.
+       conflict hunks can be resolved automatically, should they be
+       encountered again.  By default, linkgit:git-rerere[1] is
+       enabled if there is an `rr-cache` directory under the
+       `$GIT_DIR`, e.g. if "rerere" was previously used in the
+       repository.
 
 sendemail.identity::
        A configuration identity. When given, causes values in the
index 887466d7777bb3cedce976b55d446f0f7803c423..ee6cca2e1333eb26b0913eb5c4eaf9f38e5855d9 100644 (file)
@@ -40,6 +40,9 @@ OPTIONS
 --keep::
        Pass `-k` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--keep-non-patch::
+       Pass `-b` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+
 --keep-cr::
 --no-keep-cr::
        With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1])
index f46013c91fcbbe4eecffe09d9b43c132daea093f..0427e80a35601e689a47299dce9c5517959bddf8 100644 (file)
@@ -14,6 +14,7 @@ SYNOPSIS
 '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
 -----------
@@ -158,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 4b8b26b75e63cc56e679d2e2c6d8fd1240010419..6e22522c4f7e97dbab42b3cbb5b538cdea3b4d74 100644 (file)
@@ -13,7 +13,8 @@ SYNOPSIS
          [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
          [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
          [--separate-git-dir <git dir>]
-         [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
+         [--depth <depth>] [--[no-]single-branch]
+         [--recursive|--recurse-submodules] [--] <repository>
          [<directory>]
 
 DESCRIPTION
@@ -146,8 +147,9 @@ objects from the source repository into a pack in the cloned repository.
 -b <name>::
        Instead of pointing the newly created HEAD to the branch pointed
        to by the cloned repository's HEAD, point to `<name>` branch
-       instead. In a non-bare repository, this is the branch that will
-       be checked out.
+       instead. `--branch` can also take tags and treat them like
+       detached HEAD. In a non-bare repository, this is the branch
+       that will be checked out.
 
 --upload-pack <upload-pack>::
 -u <upload-pack>::
@@ -179,6 +181,14 @@ objects from the source repository into a pack in the cloned repository.
        with a long history, and would want to send in fixes
        as patches.
 
+--single-branch::
+       Clone only the history leading to the tip of a single branch,
+       either specified by the `--branch` option or the primary
+       branch remote's `HEAD` points at. When creating a shallow
+       clone with the `--depth` option, this is the default, unless
+       `--no-single-branch` is given to fetch the histories near the
+       tips of all branches.
+
 --recursive::
 --recurse-submodules::
        After the clone is created, initialize all submodules within,
index 02133d5fc9a14df7f4fe24d97bdf9980e77f807a..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
 ------------------
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 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 15d6711d46754e9659561b4372b46ec3c6654d6b..6a8b1e3a7d06218903ce5614f51c4e7cbba73b17 100644 (file)
@@ -79,6 +79,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 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 e2e6aba17e7bde2bacb7f29ec4c54e5f9587dac6..3ceefb8a1f3b17f4301d89f8eec1cc4bb103a24a 100644 (file)
@@ -9,7 +9,7 @@ git-merge - Join two or more development histories together
 SYNOPSIS
 --------
 [verse]
-'git merge' [-n] [--stat] [--no-commit] [--squash]
+'git merge' [-n] [--stat] [--no-commit] [--squash] [--[no-]edit]
        [-s <strategy>] [-X <strategy-option>]
        [--[no-]rerere-autoupdate] [-m <msg>] [<commit>...]
 'git merge' <msg> HEAD <commit>...
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..8b92cc0
--- /dev/null
@@ -0,0 +1,498 @@
+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.  Git-p4 can consult the client
+spec when given the '--use-client-spec' option or useClientSpec
+variable.
+
+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.
+
+Bugs in the implementation of overlap mappings exist.  If multiple depot
+paths map through overlays to the same location in the repository,
+git-p4 can choose the wrong one.  This is hard to solve without
+dedicating a client spec just for git-p4.
+
+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 5375549820bd6b9fecf3540b1e60ae50e74da0e6..c4bde6509e5a2f652dca9345a3a28e9e674c216a 100644 (file)
@@ -83,11 +83,10 @@ OPTIONS
 
 --prefix=<prefix>/::
        Keep the current index contents, and read the contents
-       of the named tree-ish under the directory at `<prefix>`. The
-       original index file cannot have anything at the path
-       `<prefix>` itself, nor anything in the `<prefix>/`
-       directory.  Note that the `<prefix>/` value must end
-       with a slash.
+       of the named tree-ish under the directory at `<prefix>`.
+       The command will refuse to overwrite entries that already
+       existed in the original index file. Note that the `<prefix>/`
+       value must end with a slash.
 
 --exclude-per-directory=<gitignore>::
        When running the command with `-u` and `-m` options, the
@@ -342,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 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 6ec3fef0799222e67cb176d00aae2f583004032d..b72964947afcc39af3b97c5b9eeb1969b44e0d05 100644 (file)
@@ -79,7 +79,12 @@ to exist in the superproject. If <path> is not given, the
 <repository> is the URL of the new submodule's origin repository.
 This may be either an absolute URL, or (if it begins with ./
 or ../), the location relative to the superproject's origin
-repository. If the superproject doesn't have an origin configured
+repository (Please note that to specify a repository 'foo.git'
+which is located right next to a superproject 'bar.git', you'll
+have to use '../foo.git' instead of './foo.git' - as one might expect
+when following the rules for relative URLs - because the evaluation
+of relative URLs in Git is identical to that of relative directories).
+If the superproject doesn't have an origin configured
 the superproject is its own authoritative upstream and the current
 working directory is used instead.
 +
index 75b1ae5061b78524383221641aee5edd292028e6..a45d4c4f29635a4fdcdeb2d15950925313920741 100644 (file)
@@ -43,12 +43,9 @@ In the past, `.git/HEAD` was a symbolic link pointing at
 `refs/heads/master`.  When we wanted to switch to another branch,
 we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we wanted
 to find out which branch we are on, we did `readlink .git/HEAD`.
-This was fine, and internally that is what still happens by
-default, but on platforms that do not have working symlinks,
-or that do not have the `readlink(1)` command, this was a bit
-cumbersome.  On some platforms, `ln -sf` does not even work as
-advertised (horrors).  Therefore symbolic links are now deprecated
-and symbolic refs are used by default.
+But symbolic links are not entirely portable, so they are now
+deprecated and symbolic refs (as described above) are used by
+default.
 
 'git symbolic-ref' will exit with status 0 if the contents of the
 symbolic ref were printed correctly, with status 1 if the requested
index c83cb13de67943813edc99725b87cfe94beba87e..53ff5f6cf7b9420933b022accace1355db6337c6 100644 (file)
@@ -38,7 +38,9 @@ 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
 -------
@@ -48,11 +50,11 @@ OPTIONS
 
 -s::
 --sign::
-       Make a GPG-signed tag, using the default e-mail address's key
+       Make a GPG-signed tag, using the default e-mail address's key.
 
 -u <key-id>::
 --local-user=<key-id>::
-       Make a GPG-signed tag, using the given key
+       Make a GPG-signed tag, using the given key.
 
 -f::
 --force::
@@ -99,6 +101,13 @@ OPTIONS
        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 e869032fc0703aed18d3cb1417eda4341c29e093..a12d15fb98eae16c1b88e8e46ca65fe1af9c1dfb 100644 (file)
@@ -44,20 +44,37 @@ 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.8/git.html[documentation for release 1.7.8]
+* link:v1.7.9.1/git.html[documentation for release 1.7.9.1]
 
 * release notes for
+  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.1/git.html[documentation for release 1.7.7.1]
+* 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],
+  link:RelNotes/1.7.7.2.txt[1.7.7.2],
   link:RelNotes/1.7.7.1.txt[1.7.7.1],
   link:RelNotes/1.7.7.txt[1.7.7].
 
-* link:v1.7.6.4/git.html[documentation for release 1.7.6.4]
+* link:v1.7.6.6/git.html[documentation for release 1.7.6.6]
 
 * release notes for
+  link:RelNotes/1.7.6.6.txt[1.7.6.6],
+  link:RelNotes/1.7.6.5.txt[1.7.6.5],
   link:RelNotes/1.7.6.4.txt[1.7.6.4],
   link:RelNotes/1.7.6.3.txt[1.7.6.3],
   link:RelNotes/1.7.6.2.txt[1.7.6.2],
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/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 6bd0b041c3c1c0af5d03fce43d8016706e9256b1..f2f1d0f51ceffd4f36a408886cee222a37cf4126 100644 (file)
@@ -8,10 +8,20 @@ failed and do not autocommit, to give the user a chance to
 inspect and further tweak the merge result before committing.
 
 --edit::
--e::
+--no-edit::
+       Invoke an editor before committing successful mechanical merge to
+       further edit the auto-generated merge message, so that the user
+       can explain and justify the merge. The `--no-edit` option can be
+       used to accept the auto-generated message (this is generally
+       discouraged). The `--edit` option is still useful if you are
+       giving a draft message with the `-m` option from the command line
+       and want to edit it in the editor.
 +
-       Invoke editor before committing successful merge to further
-       edit the default merge message.
+Older scripts may depend on the historical behaviour of not allowing the
+user to edit the merge log message. They will see an editor opened when
+they run `git merge`. To make it easier to adjust such scripts to the
+updated behaviour, the environment variable `GIT_MERGE_AUTOEDIT` can be
+set to `no` at the beginning of them.
 
 --ff::
 --no-ff::
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
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 a04c8f32b767d757066526fc4f4685df812f3825..c25fd2a374d66d1f0da55a387c7c47993001fd4f 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.8
+DEF_VER=v1.7.9.GIT
 
 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 b1c80a678b5fdb80ec71f0a1ad567a384feba4e8..a0de4e9c6b1f8d7df104505f9b8edd3940c900f0 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,29 @@ 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 USE_GETTEXT_SCHEME and set it to 'fallthrough', if you don't trust
+# the installed gettext translation of the shell scripts output.
+#
+# 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 +80,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 +166,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 +252,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
@@ -307,6 +335,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
@@ -315,9 +344,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
@@ -328,6 +357,7 @@ RPMBUILD = rpmbuild
 TCL_PATH = tclsh
 TCLTK_PATH = wish
 XGETTEXT = xgettext
+MSGFMT = msgfmt
 PTHREAD_LIBS = -lpthread
 PTHREAD_CFLAGS =
 GCOV = gcov
@@ -351,6 +381,11 @@ BUILTIN_OBJS =
 BUILT_INS =
 COMPAT_CFLAGS =
 COMPAT_OBJS =
+XDIFF_H =
+XDIFF_OBJS =
+VCSSVN_H =
+VCSSVN_OBJS =
+VCSSVN_TEST_OBJS =
 EXTRA_CPPFLAGS =
 LIB_H =
 LIB_OBJS =
@@ -427,29 +462,32 @@ 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
 TEST_PROGRAMS_NEED_X += test-match-trees
 TEST_PROGRAMS_NEED_X += test-mktemp
-TEST_PROGRAMS_NEED_X += test-obj-pool
 TEST_PROGRAMS_NEED_X += test-parse-options
 TEST_PROGRAMS_NEED_X += test-path-utils
 TEST_PROGRAMS_NEED_X += test-run-command
 TEST_PROGRAMS_NEED_X += test-sha1
 TEST_PROGRAMS_NEED_X += test-sigchain
-TEST_PROGRAMS_NEED_X += test-string-pool
 TEST_PROGRAMS_NEED_X += test-subprocess
 TEST_PROGRAMS_NEED_X += test-svn-fe
-TEST_PROGRAMS_NEED_X += test-treap
 
 TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
 
@@ -511,6 +549,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
@@ -519,12 +558,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/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
@@ -532,9 +573,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
@@ -558,6 +601,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
@@ -598,17 +642,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
@@ -628,6 +675,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
@@ -663,6 +712,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
@@ -824,12 +874,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
@@ -884,6 +937,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
@@ -896,6 +950,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
@@ -1019,6 +1074,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
@@ -1098,6 +1154,7 @@ ifeq ($(uname_S),Windows)
        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
@@ -1191,6 +1248,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        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
@@ -1235,6 +1293,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
@@ -1423,6 +1482,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
@@ -1465,9 +1529,12 @@ 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
+       USE_GETTEXT_SCHEME ?= fallthrough
+endif
 ifdef NO_STRCASESTR
        COMPAT_CFLAGS += -DNO_STRCASESTR
        COMPAT_OBJS += compat/strcasestr.o
@@ -1478,7 +1545,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
@@ -1568,6 +1635,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
@@ -1630,6 +1703,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
@@ -1650,6 +1732,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
@@ -1680,6 +1766,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=
@@ -1695,6 +1782,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))
@@ -1706,6 +1813,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))
@@ -1750,7 +1858,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
@@ -1761,7 +1879,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
@@ -1811,7 +1929,9 @@ 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 's/@@USE_GETTEXT_SCHEME@@/$(USE_GETTEXT_SCHEME)/g' \
     -e $(BROKEN_PATH_FIX) \
     $@.sh >$@+
 endef
@@ -1913,12 +2033,24 @@ GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
 ifndef NO_CURL
        GIT_OBJS += http.o http-walker.o remote-curl.o
 endif
-XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
-       xdiff/xmerge.o xdiff/xpatience.o xdiff/xhistogram.o
-VCSSVN_OBJS = vcs-svn/string_pool.o vcs-svn/line_buffer.o \
-       vcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o
-VCSSVN_TEST_OBJS = test-obj-pool.o test-string-pool.o \
-       test-line-buffer.o test-treap.o
+
+XDIFF_OBJS += xdiff/xdiffi.o
+XDIFF_OBJS += xdiff/xprepare.o
+XDIFF_OBJS += xdiff/xutils.o
+XDIFF_OBJS += xdiff/xemit.o
+XDIFF_OBJS += xdiff/xmerge.o
+XDIFF_OBJS += xdiff/xpatience.o
+XDIFF_OBJS += xdiff/xhistogram.o
+
+VCSSVN_OBJS += vcs-svn/line_buffer.o
+VCSSVN_OBJS += vcs-svn/sliding_window.o
+VCSSVN_OBJS += vcs-svn/repo_tree.o
+VCSSVN_OBJS += vcs-svn/fast_export.o
+VCSSVN_OBJS += vcs-svn/svndiff.o
+VCSSVN_OBJS += vcs-svn/svndump.o
+
+VCSSVN_TEST_OBJS += test-line-buffer.o
+
 OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS)
 
 dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
@@ -2037,16 +2169,25 @@ connect.o transport.o url.o http-backend.o: url.h
 http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
 http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
 
-xdiff-interface.o $(XDIFF_OBJS): \
-       xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
-       xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
+XDIFF_H += xdiff/xinclude.h
+XDIFF_H += xdiff/xmacros.h
+XDIFF_H += xdiff/xdiff.h
+XDIFF_H += xdiff/xtypes.h
+XDIFF_H += xdiff/xutils.h
+XDIFF_H += xdiff/xprepare.h
+XDIFF_H += xdiff/xdiffi.h
+XDIFF_H += xdiff/xemit.h
 
-$(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) \
-       vcs-svn/obj_pool.h vcs-svn/trp.h vcs-svn/string_pool.h \
-       vcs-svn/line_buffer.h vcs-svn/repo_tree.h vcs-svn/fast_export.h \
-       vcs-svn/svndump.h
+xdiff-interface.o $(XDIFF_OBJS): $(XDIFF_H)
 
-test-svn-fe.o: vcs-svn/svndump.h
+VCSSVN_H += vcs-svn/line_buffer.h
+VCSSVN_H += vcs-svn/sliding_window.h
+VCSSVN_H += vcs-svn/repo_tree.h
+VCSSVN_H += vcs-svn/fast_export.h
+VCSSVN_H += vcs-svn/svndiff.h
+VCSSVN_H += vcs-svn/svndump.h
+
+$(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) $(VCSSVN_H)
 endif
 
 exec_cmd.sp exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \
@@ -2063,6 +2204,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)"'
 
@@ -2136,17 +2280,37 @@ 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 \) )
@@ -2165,7 +2329,8 @@ cscope:
 
 ### 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):$(USE_GETTEXT_SCHEME)
 
 GIT-CFLAGS: FORCE
        @FLAGS='$(TRACK_CFLAGS)'; \
@@ -2202,7 +2367,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
@@ -2248,8 +2415,6 @@ test-line-buffer$X: vcs-svn/lib.a
 
 test-parse-options$X: parse-options.o parse-options-cb.o
 
-test-string-pool$X: vcs-svn/lib.a
-
 test-svn-fe$X: vcs-svn/lib.a
 
 .PRECIOUS: $(TEST_OBJS)
@@ -2317,6 +2482,11 @@ install: all
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
        $(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
@@ -2446,13 +2616,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
@@ -2475,7 +2650,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
 
@@ -2585,18 +2760,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
index 7d9276973af3a8f455778d2ce3ced66167f46801..2c2a16955519b3c314ebdb3d2f0c08d544666ca1 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.7.8.txt
\ No newline at end of file
+Documentation/RelNotes/1.7.10.txt
\ No newline at end of file
index e02e632df380a8e9772d9cd9b1282204c56a7d4e..01130e54e7b270df7f535fb815dba25ddb72ec1a 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -21,11 +21,21 @@ static struct {
 
 void advise(const char *advice, ...)
 {
+       struct strbuf buf = STRBUF_INIT;
        va_list params;
+       const char *cp, *np;
 
        va_start(params, advice);
-       vreportf("hint: ", advice, params);
+       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)
@@ -46,16 +56,15 @@ int git_default_advice_config(const char *var, const char *value)
 int error_resolve_conflict(const char *me)
 {
        error("'%s' is not possible because you have unmerged files.", me);
-       if (advice_resolve_conflict) {
+       if (advice_resolve_conflict)
                /*
                 * Message used both when 'git commit' fails and when
                 * other commands doing a merge do.
                 */
-               advise("Fix them up in the work tree,");
-               advise("and then use 'git add/rm <file>' as");
-               advise("appropriate to mark resolution and make a commit,");
-               advise("or use 'git commit -a'.");
-       }
+               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;
 }
 
@@ -64,3 +73,17 @@ void NORETURN die_resolve_conflict(const char *me)
        error_resolve_conflict(me);
        die("Exiting because of an unresolved conflict.");
 }
+
+void detach_advice(const char *new_name)
+{
+       const char fmt[] =
+       "Note: checking out '%s'.\n\n"
+       "You are in 'detached HEAD' state. You can look around, make experimental\n"
+       "changes and commit them, and you can discard any commits you make in this\n"
+       "state without impacting any branches by performing another checkout.\n\n"
+       "If you want to create a new branch to retain commits you create, you may\n"
+       "do so (now or later) by using -b with the checkout command again. Example:\n\n"
+       "  git checkout -b new_branch_name\n\n";
+
+       fprintf(stderr, fmt, new_name);
+}
index e5d0af782b1445b48b49cd58f481a593268c3384..7bda45b83e34b8417e5c20219c7424bb35b3d681 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -14,5 +14,6 @@ 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);
+void detach_advice(const char *new_name);
 
 #endif /* ADVICE_H */
index 2ae740a71e6d43ee81afdeddcb53f983f10a8fff..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");
 
@@ -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);
diff --git a/attr.c b/attr.c
index 76b079f0f530e1372b2866f40cce21ec5266394c..303751f6c2bd4d558cffbb928c636581efb2b310 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -301,6 +301,7 @@ static void free_attr_elem(struct attr_stack *e)
                }
                free(a);
        }
+       free(e->attrs);
        free(e);
 }
 
@@ -495,47 +496,48 @@ static int git_attr_system(void)
 
 static void bootstrap_attr_stack(void)
 {
-       if (!attr_stack) {
-               struct attr_stack *elem;
+       struct attr_stack *elem;
 
-               elem = read_attr_from_array(builtin_attr);
-               elem->origin = NULL;
-               elem->prev = attr_stack;
-               attr_stack = elem;
+       if (attr_stack)
+               return;
 
-               if (git_attr_system()) {
-                       elem = read_attr_from_file(git_etc_gitattributes(), 1);
-                       if (elem) {
-                               elem->origin = NULL;
-                               elem->prev = attr_stack;
-                               attr_stack = elem;
-                       }
-               }
+       elem = read_attr_from_array(builtin_attr);
+       elem->origin = NULL;
+       elem->prev = attr_stack;
+       attr_stack = elem;
 
-               if (git_attributes_file) {
-                       elem = read_attr_from_file(git_attributes_file, 1);
-                       if (elem) {
-                               elem->origin = NULL;
-                               elem->prev = attr_stack;
-                               attr_stack = elem;
-                       }
+       if (git_attr_system()) {
+               elem = read_attr_from_file(git_etc_gitattributes(), 1);
+               if (elem) {
+                       elem->origin = NULL;
+                       elem->prev = attr_stack;
+                       attr_stack = elem;
                }
+       }
 
-               if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
-                       elem = read_attr(GITATTRIBUTES_FILE, 1);
-                       elem->origin = xstrdup("");
+       if (git_attributes_file) {
+               elem = read_attr_from_file(git_attributes_file, 1);
+               if (elem) {
+                       elem->origin = NULL;
                        elem->prev = attr_stack;
                        attr_stack = elem;
-                       debug_push(elem);
                }
+       }
 
-               elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1);
-               if (!elem)
-                       elem = xcalloc(1, sizeof(*elem));
-               elem->origin = NULL;
+       if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
+               elem = read_attr(GITATTRIBUTES_FILE, 1);
+               elem->origin = xstrdup("");
                elem->prev = attr_stack;
                attr_stack = elem;
+               debug_push(elem);
        }
+
+       elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1);
+       if (!elem)
+               elem = xcalloc(1, sizeof(*elem));
+       elem->origin = NULL;
+       elem->prev = attr_stack;
+       attr_stack = elem;
 }
 
 static void prepare_attr_stack(const char *path)
@@ -575,14 +577,17 @@ static void prepare_attr_stack(const char *path)
 
        /*
         * Pop the ones from directories that are not the prefix of
-        * the path we are checking.
+        * the path we are checking. Break out of the loop when we see
+        * the root one (whose origin is an empty string "") or the builtin
+        * one (whose origin is NULL) without popping it.
         */
-       while (attr_stack && attr_stack->origin) {
+       while (attr_stack->origin) {
                int namelen = strlen(attr_stack->origin);
 
                elem = attr_stack;
                if (namelen <= dirlen &&
-                   !strncmp(elem->origin, path, namelen))
+                   !strncmp(elem->origin, path, namelen) &&
+                   (!namelen || path[namelen] == '/'))
                        break;
 
                debug_pop(elem);
@@ -594,8 +599,15 @@ static void prepare_attr_stack(const char *path)
         * Read from parent directories and push them down
         */
        if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
+               /*
+                * bootstrap_attr_stack() should have added, and the
+                * above loop should have stopped before popping, the
+                * root element whose attr_stack->origin is set to an
+                * empty string.
+                */
                struct strbuf pathbuf = STRBUF_INIT;
 
+               assert(attr_stack->origin);
                while (1) {
                        len = strlen(attr_stack->origin);
                        if (dirlen <= len)
index 025a97be0281b9fd3ccff2b36c03495b005ac4fe..9971820a184d9713126c3c9f763dd8f6ec1b1a50 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -3,7 +3,6 @@
 #include "refs.h"
 #include "remote.h"
 #include "commit.h"
-#include "sequencer.h"
 
 struct tracking {
        struct refspec spec;
@@ -136,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)
 {
@@ -151,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.");
        }
@@ -160,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;
@@ -175,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
@@ -247,5 +279,4 @@ void remove_branch_state(void)
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("MERGE_MODE"));
        unlink(git_path("SQUASH_MSG"));
-       remove_sequencer_state(0);
 }
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 84a8a0b52136c4d1e43ec10f9ef5ed76b7d3c12f..389898f13364eb640077c1d82fefea98d9d3755f 100644 (file)
@@ -14,6 +14,7 @@
 #include "builtin.h"
 #include "string-list.h"
 #include "dir.h"
+#include "diff.h"
 #include "parse-options.h"
 
 /*
@@ -250,9 +251,6 @@ static int fuzzy_matchlines(const char *s1, size_t n1,
        const char *last2 = s2 + n2 - 1;
        int result = 0;
 
-       if (n1 < 0 || n2 < 0)
-               return 0;
-
        /* ignore line endings */
        while ((*last1 == '\r') || (*last1 == '\n'))
                last1--;
@@ -3244,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)
@@ -3590,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) {
@@ -3724,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;
 
@@ -3741,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)
index 80febbe420db1c75bbcf7eda6a733e7e66549790..01956c80815a4f330ae27b77b4d21d263a887d1b 100644 (file)
@@ -1598,7 +1598,7 @@ static const char *format_time(unsigned long time, const char *tz_str,
        int tz;
 
        if (show_raw_time) {
-               sprintf(time_buf, "%lu %s", time, tz_str);
+               snprintf(time_buf, sizeof(time_buf), "%lu %s", time, tz_str);
        }
        else {
                tz = atoi(tz_str);
@@ -2050,14 +2050,8 @@ static int git_blame_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       switch (userdiff_config(var, value)) {
-       case 0:
-               break;
-       case -1:
+       if (userdiff_config(var, value) < 0)
                return -1;
-       default:
-               return 0;
-       }
 
        return git_default_config(var, value, cb);
 }
index 55cad766c7e3d284b1361b1beca8c5d51de96083..cb17bc367571a88b6e6bcac5020c1746c4385480 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)))
@@ -565,9 +567,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."));
@@ -577,13 +579,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);
@@ -623,11 +631,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, 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;
@@ -666,6 +712,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
                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,
@@ -689,10 +737,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 {
@@ -705,7 +752,7 @@ 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 && argc == 0)
+       if (!delete && !rename && !edit_description && argc == 0)
                list = 1;
 
        if (!!delete + !!rename + !!force_create + !!list > 1)
@@ -719,7 +766,34 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        else if (list)
                return print_ref_list(kinds, detached, verbose, abbrev,
                                      with_commit, argv);
-       else if (rename) {
+       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)
@@ -730,7 +804,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                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 07bd984084fbbfbb826ef5b784fc68069675e73c..8ed501f220424976cc30f4a4dbf3d59f979902be 100644 (file)
@@ -226,14 +226,8 @@ static const char * const cat_file_usage[] = {
 
 static int git_cat_file_config(const char *var, const char *value, void *cb)
 {
-       switch (userdiff_config(var, value)) {
-       case 0:
-               break;
-       case -1:
+       if (userdiff_config(var, value) < 0)
                return -1;
-       default:
-               return 0;
-       }
 
        return git_default_config(var, value, cb);
 }
index 2a8077242500d54ac50d5829a86b14803fc69126..6b9061f26f5f33ae1ded811891e933441c210fb0 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;
-               topts.dir->exclude_per_dir = ".gitignore";
+               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);
@@ -499,20 +514,6 @@ static void report_tracking(struct branch_info *new)
        strbuf_release(&sb);
 }
 
-static void detach_advice(const char *old_path, const char *new_name)
-{
-       const char fmt[] =
-       "Note: checking out '%s'.\n\n"
-       "You are in 'detached HEAD' state. You can look around, make experimental\n"
-       "changes and commit them, and you can discard any commits you make in this\n"
-       "state without impacting any branches by performing another checkout.\n\n"
-       "If you want to create a new branch to retain commits you create, you may\n"
-       "do so (now or later) by using -b with the checkout command again. Example:\n\n"
-       "  git checkout -b new_branch_name\n\n";
-
-       fprintf(stderr, fmt, new_name);
-}
-
 static void update_refs_for_switch(struct checkout_opts *opts,
                                   struct branch_info *old,
                                   struct branch_info *new)
@@ -540,7 +541,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);
        }
@@ -558,15 +561,19 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                           REF_NODEREF, DIE_ON_ERR);
                if (!opts->quiet) {
                        if (old->path && advice_detached_head)
-                               detach_advice(old->path, new->name);
+                               detach_advice(new->name);
                        describe_detached_head(_("HEAD is now at"), new->commit);
                }
        } else if (new->path) { /* Switch branches. */
                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);
@@ -696,15 +703,14 @@ 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/");
@@ -718,8 +724,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);
@@ -727,7 +735,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;
 }
 
@@ -866,7 +874,7 @@ static int parse_branchname_arg(int argc, const char **argv,
        setup_branch_path(new);
 
        if (!check_refname_format(new->path, 0) &&
-           resolve_ref(new->path, branch_rev, 1, NULL))
+           !read_ref(new->path, branch_rev))
                hashcpy(rev, branch_rev);
        else
                new->path = NULL; /* not an existing branch */
@@ -900,6 +908,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;
@@ -926,6 +945,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"),
@@ -937,6 +957,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);
@@ -1057,7 +1078,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);
        }
@@ -1068,5 +1090,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 b11dd6dd611b60a85d0a7c8d16a078c16630fa50..7559f62bc7a846ecb8f8d085e8bf5c3e6d4df2cd 100644 (file)
@@ -37,7 +37,7 @@ static const char * const builtin_clone_usage[] = {
        NULL
 };
 
-static int option_no_checkout, option_bare, option_mirror;
+static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
 static int option_local, option_no_hardlinks, option_shared, option_recursive;
 static char *option_template, *option_depth;
 static char *option_origin = NULL;
@@ -84,14 +84,16 @@ 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",
                   "path to git-upload-pack on the remote"),
        OPT_STRING(0, "depth", &option_depth, "depth",
                    "create a shallow clone of that depth"),
+       OPT_BOOL(0, "single-branch", &option_single_branch,
+                   "clone only one branch, HEAD or --branch"),
        OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
                   "separate git dir from working tree"),
        OPT_STRING_LIST('c', "config", &option_config, "key=value",
@@ -230,9 +232,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
 {
        char *ref_git;
        struct strbuf alternate = STRBUF_INIT;
-       struct remote *remote;
-       struct transport *transport;
-       const struct ref *extra;
 
        /* Beware: real_path() and mkpath() return static buffer */
        ref_git = xstrdup(real_path(item->string));
@@ -247,14 +246,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
        strbuf_addf(&alternate, "%s/objects", ref_git);
        add_to_alternates_file(alternate.buf);
        strbuf_release(&alternate);
-
-       remote = remote_get(ref_git);
-       transport = transport_get(remote, ref_git);
-       for (extra = transport_get_remote_refs(transport); extra;
-            extra = extra->next)
-               add_extra_ref(extra->name, extra->old_sha1, 0);
-
-       transport_disconnect(transport);
        free(ref_git);
        return 0;
 }
@@ -361,13 +352,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
        closedir(dir);
 }
 
-static const struct ref *clone_local(const char *src_repo,
-                                    const char *dest_repo)
+static void clone_local(const char *src_repo, const char *dest_repo)
 {
-       const struct ref *ret;
-       struct remote *remote;
-       struct transport *transport;
-
        if (option_shared) {
                struct strbuf alt = STRBUF_INIT;
                strbuf_addf(&alt, "%s/objects", src_repo);
@@ -383,13 +369,8 @@ static const struct ref *clone_local(const char *src_repo,
                strbuf_release(&dest);
        }
 
-       remote = remote_get(src_repo);
-       transport = transport_get(remote, src_repo);
-       ret = transport_get_remote_refs(transport);
-       transport_disconnect(transport);
        if (0 <= option_verbosity)
                printf(_("done.\n"));
-       return ret;
 }
 
 static const char *junk_work_tree;
@@ -420,6 +401,26 @@ static void remove_junk_on_signal(int signo)
        raise(signo);
 }
 
+static struct ref *find_remote_branch(const struct ref *refs, const char *branch)
+{
+       struct ref *ref;
+       struct strbuf head = STRBUF_INIT;
+       strbuf_addstr(&head, "refs/heads/");
+       strbuf_addstr(&head, branch);
+       ref = find_ref_by_name(refs, head.buf);
+       strbuf_release(&head);
+
+       if (ref)
+               return ref;
+
+       strbuf_addstr(&head, "refs/tags/");
+       strbuf_addstr(&head, branch);
+       ref = find_ref_by_name(refs, head.buf);
+       strbuf_release(&head);
+
+       return ref;
+}
+
 static struct ref *wanted_peer_refs(const struct ref *refs,
                struct refspec *refspec)
 {
@@ -427,8 +428,27 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
        struct ref *local_refs = head;
        struct ref **tail = head ? &head->next : &local_refs;
 
-       get_fetch_map(refs, refspec, &tail, 0);
-       if (!option_mirror)
+       if (option_single_branch) {
+               struct ref *remote_head = NULL;
+
+               if (!option_branch)
+                       remote_head = guess_remote_head(head, refs, 0);
+               else
+                       remote_head = find_remote_branch(refs, option_branch);
+
+               if (!remote_head && option_branch)
+                       warning(_("Could not find remote branch %s to clone."),
+                               option_branch);
+               else {
+                       get_fetch_map(remote_head, refspec, &tail, 0);
+
+                       /* if --branch=tag, pull the requested tag explicitly */
+                       get_fetch_map(remote_head, tag_refspec, &tail, 0);
+               }
+       } else
+               get_fetch_map(refs, refspec, &tail, 0);
+
+       if (!option_mirror && !option_single_branch)
                get_fetch_map(refs, tag_refspec, &tail, 0);
 
        return local_refs;
@@ -441,11 +461,134 @@ static void write_remote_refs(const struct ref *local_refs)
        for (r = local_refs; r; r = r->next) {
                if (!r->peer_ref)
                        continue;
-               add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
+               add_packed_ref(r->peer_ref->name, r->old_sha1);
        }
 
        pack_refs(PACK_REFS_ALL);
-       clear_extra_refs();
+}
+
+static void write_followtags(const struct ref *refs, const char *msg)
+{
+       const struct ref *ref;
+       for (ref = refs; ref; ref = ref->next) {
+               if (prefixcmp(ref->name, "refs/tags/"))
+                       continue;
+               if (!suffixcmp(ref->name, "^{}"))
+                       continue;
+               if (!has_sha1_file(ref->old_sha1))
+                       continue;
+               update_ref(msg, ref->name, ref->old_sha1,
+                          NULL, 0, DIE_ON_ERR);
+       }
+}
+
+static void update_remote_refs(const struct ref *refs,
+                              const struct ref *mapped_refs,
+                              const struct ref *remote_head_points_at,
+                              const char *branch_top,
+                              const char *msg)
+{
+       if (refs) {
+               write_remote_refs(mapped_refs);
+               if (option_single_branch)
+                       write_followtags(refs, msg);
+       }
+
+       if (remote_head_points_at && !option_bare) {
+               struct strbuf head_ref = STRBUF_INIT;
+               strbuf_addstr(&head_ref, branch_top);
+               strbuf_addstr(&head_ref, "HEAD");
+               create_symref(head_ref.buf,
+                             remote_head_points_at->peer_ref->name,
+                             msg);
+       }
+}
+
+static void update_head(const struct ref *our, const struct ref *remote,
+                       const char *msg)
+{
+       if (our && !prefixcmp(our->name, "refs/heads/")) {
+               /* Local default branch link */
+               create_symref("HEAD", our->name, NULL);
+               if (!option_bare) {
+                       const char *head = skip_prefix(our->name, "refs/heads/");
+                       update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
+                       install_branch_config(0, head, option_origin, our->name);
+               }
+       } else if (our) {
+               struct commit *c = lookup_commit_reference(our->old_sha1);
+               /* --branch specifies a non-branch (i.e. tags), detach HEAD */
+               update_ref(msg, "HEAD", c->object.sha1,
+                          NULL, REF_NODEREF, DIE_ON_ERR);
+       } else if (remote) {
+               /*
+                * We know remote HEAD points to a non-branch, or
+                * HEAD points to a branch but we don't know which one.
+                * Detach HEAD in all these cases.
+                */
+               update_ref(msg, "HEAD", remote->old_sha1,
+                          NULL, REF_NODEREF, DIE_ON_ERR);
+       }
+}
+
+static int checkout(void)
+{
+       unsigned char sha1[20];
+       char *head;
+       struct lock_file *lock_file;
+       struct unpack_trees_options opts;
+       struct tree *tree;
+       struct tree_desc t;
+       int err = 0, fd;
+
+       if (option_no_checkout)
+               return 0;
+
+       head = resolve_refdup("HEAD", sha1, 1, NULL);
+       if (!head) {
+               warning(_("remote HEAD refers to nonexistent ref, "
+                         "unable to checkout.\n"));
+               return 0;
+       }
+       if (!strcmp(head, "HEAD")) {
+               if (advice_detached_head)
+                       detach_advice(sha1_to_hex(sha1));
+       } else {
+               if (prefixcmp(head, "refs/heads/"))
+                       die(_("HEAD not found below refs/heads!"));
+       }
+       free(head);
+
+       /* We need to be in the new work tree for the checkout */
+       setup_work_tree();
+
+       lock_file = xcalloc(1, sizeof(struct lock_file));
+       fd = hold_locked_index(lock_file, 1);
+
+       memset(&opts, 0, sizeof opts);
+       opts.update = 1;
+       opts.merge = 1;
+       opts.fn = oneway_merge;
+       opts.verbose_update = (option_verbosity > 0);
+       opts.src_index = &the_index;
+       opts.dst_index = &the_index;
+
+       tree = parse_tree_indirect(sha1);
+       parse_tree(tree);
+       init_tree_desc(&t, tree->buffer, tree->size);
+       unpack_trees(1, &t, &opts);
+
+       if (write_cache(fd, active_cache, active_nr) ||
+           commit_locked_index(lock_file))
+               die(_("unable to write new index file"));
+
+       err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
+                       sha1_to_hex(sha1), "1", NULL);
+
+       if (!err && option_recursive)
+               err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
+
+       return err;
 }
 
 static int write_one_config(const char *key, const char *value, void *data)
@@ -475,11 +618,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        const struct ref *remote_head_points_at;
        const struct ref *our_head_points_at;
        struct ref *mapped_refs;
+       const struct ref *ref;
        struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
        struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
        struct transport *transport = NULL;
-       char *src_ref_prefix = "refs/heads/";
-       int err = 0;
+       const char *src_ref_prefix = "refs/heads/";
+       struct remote *remote;
+       int err = 0, complete_refs_before_fetch = 1;
 
        struct refspec *refspec;
        const char *fetch_pattern;
@@ -498,6 +643,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                usage_msg_opt(_("You must specify a repository to clone."),
                        builtin_clone_usage, builtin_clone_options);
 
+       if (option_single_branch == -1)
+               option_single_branch = option_depth ? 1 : 0;
+
        if (option_mirror)
                option_bare = 1;
 
@@ -630,13 +778,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        strbuf_reset(&value);
 
-       if (is_local) {
-               refs = clone_local(path, git_dir);
-               mapped_refs = wanted_peer_refs(refs, refspec);
-       } else {
-               struct remote *remote = remote_get(option_origin);
-               transport = transport_get(remote, remote->url[0]);
+       remote = remote_get(option_origin);
+       transport = transport_get(remote, remote->url[0]);
 
+       if (!is_local) {
                if (!transport->get_refs_list || !transport->fetch)
                        die(_("Don't know how to clone %s"), transport->url);
 
@@ -645,49 +790,57 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                if (option_depth)
                        transport_set_option(transport, TRANS_OPT_DEPTH,
                                             option_depth);
+               if (option_single_branch)
+                       transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
 
                transport_set_verbosity(transport, option_verbosity, option_progress);
 
                if (option_upload_pack)
                        transport_set_option(transport, TRANS_OPT_UPLOADPACK,
                                             option_upload_pack);
-
-               refs = transport_get_remote_refs(transport);
-               if (refs) {
-                       mapped_refs = wanted_peer_refs(refs, refspec);
-                       transport_fetch_refs(transport, mapped_refs);
-               }
        }
 
+       refs = transport_get_remote_refs(transport);
+
        if (refs) {
-               clear_extra_refs();
+               mapped_refs = wanted_peer_refs(refs, refspec);
+               /*
+                * transport_get_remote_refs() may return refs with null sha-1
+                * in mapped_refs (see struct transport->get_refs_list
+                * comment). In that case we need fetch it early because
+                * remote_head code below relies on it.
+                *
+                * for normal clones, transport_get_remote_refs() should
+                * return reliable ref set, we can delay cloning until after
+                * remote HEAD check.
+                */
+               for (ref = refs; ref; ref = ref->next)
+                       if (is_null_sha1(ref->old_sha1)) {
+                               complete_refs_before_fetch = 0;
+                               break;
+                       }
 
-               write_remote_refs(mapped_refs);
+               if (!is_local && !complete_refs_before_fetch)
+                       transport_fetch_refs(transport, mapped_refs);
 
                remote_head = find_ref_by_name(refs, "HEAD");
                remote_head_points_at =
                        guess_remote_head(remote_head, mapped_refs, 0);
 
                if (option_branch) {
-                       struct strbuf head = STRBUF_INIT;
-                       strbuf_addstr(&head, src_ref_prefix);
-                       strbuf_addstr(&head, option_branch);
                        our_head_points_at =
-                               find_ref_by_name(mapped_refs, head.buf);
-                       strbuf_release(&head);
-
-                       if (!our_head_points_at) {
-                               warning(_("Remote branch %s not found in "
-                                       "upstream %s, using HEAD instead"),
-                                       option_branch, option_origin);
-                               our_head_points_at = remote_head_points_at;
-                       }
+                               find_remote_branch(mapped_refs, option_branch);
+
+                       if (!our_head_points_at)
+                               die(_("Remote branch %s not found in upstream %s"),
+                                   option_branch, option_origin);
                }
                else
                        our_head_points_at = remote_head_points_at;
        }
        else {
                warning(_("You appear to have cloned an empty repository."));
+               mapped_refs = NULL;
                our_head_points_at = NULL;
                remote_head_points_at = NULL;
                remote_head = NULL;
@@ -697,84 +850,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                                              "refs/heads/master");
        }
 
-       if (remote_head_points_at && !option_bare) {
-               struct strbuf head_ref = STRBUF_INIT;
-               strbuf_addstr(&head_ref, branch_top.buf);
-               strbuf_addstr(&head_ref, "HEAD");
-               create_symref(head_ref.buf,
-                             remote_head_points_at->peer_ref->name,
-                             reflog_msg.buf);
-       }
+       if (is_local)
+               clone_local(path, git_dir);
+       else if (refs && complete_refs_before_fetch)
+               transport_fetch_refs(transport, mapped_refs);
 
-       if (our_head_points_at) {
-               /* Local default branch link */
-               create_symref("HEAD", our_head_points_at->name, NULL);
-               if (!option_bare) {
-                       const char *head = skip_prefix(our_head_points_at->name,
-                                                      "refs/heads/");
-                       update_ref(reflog_msg.buf, "HEAD",
-                                  our_head_points_at->old_sha1,
-                                  NULL, 0, DIE_ON_ERR);
-                       install_branch_config(0, head, option_origin,
-                                             our_head_points_at->name);
-               }
-       } else if (remote_head) {
-               /* Source had detached HEAD pointing somewhere. */
-               if (!option_bare) {
-                       update_ref(reflog_msg.buf, "HEAD",
-                                  remote_head->old_sha1,
-                                  NULL, REF_NODEREF, DIE_ON_ERR);
-                       our_head_points_at = remote_head;
-               }
-       } else {
-               /* Nothing to checkout out */
-               if (!option_no_checkout)
-                       warning(_("remote HEAD refers to nonexistent ref, "
-                               "unable to checkout.\n"));
-               option_no_checkout = 1;
-       }
+       update_remote_refs(refs, mapped_refs, remote_head_points_at,
+                          branch_top.buf, reflog_msg.buf);
 
-       if (transport) {
-               transport_unlock_pack(transport);
-               transport_disconnect(transport);
-       }
+       update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
-       if (!option_no_checkout) {
-               struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-               struct unpack_trees_options opts;
-               struct tree *tree;
-               struct tree_desc t;
-               int fd;
-
-               /* We need to be in the new work tree for the checkout */
-               setup_work_tree();
-
-               fd = hold_locked_index(lock_file, 1);
-
-               memset(&opts, 0, sizeof opts);
-               opts.update = 1;
-               opts.merge = 1;
-               opts.fn = oneway_merge;
-               opts.verbose_update = (option_verbosity > 0);
-               opts.src_index = &the_index;
-               opts.dst_index = &the_index;
-
-               tree = parse_tree_indirect(our_head_points_at->old_sha1);
-               parse_tree(tree);
-               init_tree_desc(&t, tree->buffer, tree->size);
-               unpack_trees(1, &t, &opts);
-
-               if (write_cache(fd, active_cache, active_nr) ||
-                   commit_locked_index(lock_file))
-                       die(_("unable to write new index file"));
-
-               err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
-                               sha1_to_hex(our_head_points_at->old_sha1), "1",
-                               NULL);
-
-               if (!err && option_recursive)
-                       err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
-       }
+       transport_unlock_pack(transport);
+       transport_disconnect(transport);
+
+       err = checkout();
 
        strbuf_release(&reflog_msg);
        strbuf_release(&branch_top);
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 8f2bebecf313a0e4b742f9327ec178c7e3547dd2..3714582e1988f7c286412afb779cbfefe4849657 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;
        }
 
@@ -394,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(WRITE_TREE_SILENT);
                if (write_cache(fd, active_cache, active_nr) ||
                    close_lock_file(&index_lock))
                        die(_("unable to write new_index file"));
@@ -414,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(WRITE_TREE_SILENT);
                        if (write_cache(fd, active_cache, active_nr) ||
                            commit_locked_index(&index_lock))
                                die(_("unable to write new_index file"));
@@ -535,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)
@@ -555,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) {
@@ -862,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;
        }
@@ -1020,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);
 
@@ -1259,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;
@@ -1304,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 :
@@ -1324,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);
@@ -1332,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);
 }
 
@@ -1382,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);
@@ -1425,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)
@@ -1436,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);
@@ -1483,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
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 1118689fb246b864ce758039543327c4304cdaa4..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(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 c6bc8eb0aa6f5a6bc35c69e7893118a17813db7d..0e8560f60f43dd6e971a38381838d217bdc28899 100644 (file)
@@ -58,9 +58,9 @@ static void rev_list_push(struct commit *commit, int mark)
        }
 }
 
-static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int rev_list_insert_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
-       struct object *o = deref_tag(parse_object(sha1), path, 0);
+       struct object *o = deref_tag(parse_object(sha1), refname, 0);
 
        if (o && o->type == OBJ_COMMIT)
                rev_list_push((struct commit *)o, SEEN);
@@ -68,9 +68,9 @@ static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int
        return 0;
 }
 
-static int clear_marks(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int clear_marks(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
-       struct object *o = deref_tag(parse_object(sha1), path, 0);
+       struct object *o = deref_tag(parse_object(sha1), refname, 0);
 
        if (o && o->type == OBJ_COMMIT)
                clear_commit_marks((struct commit *)o,
@@ -256,11 +256,6 @@ static void insert_one_alternate_ref(const struct ref *ref, void *unused)
        rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL);
 }
 
-static void insert_alternate_refs(void)
-{
-       for_each_alternate_ref(insert_one_alternate_ref, NULL);
-}
-
 #define INITIAL_FLUSH 16
 #define PIPESAFE_FLUSH 32
 #define LARGE_FLUSH 1024
@@ -295,7 +290,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
        marked = 1;
 
        for_each_ref(rev_list_insert_ref, NULL);
-       insert_alternate_refs();
+       for_each_alternate_ref(insert_one_alternate_ref, NULL);
 
        fetching = 0;
        for ( ; refs ; refs = refs->next) {
@@ -493,7 +488,7 @@ done:
 
 static struct commit_list *complete;
 
-static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int mark_complete(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *o = parse_object(sha1);
 
@@ -556,11 +551,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);
        }
@@ -581,6 +581,11 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
        *refs = newlist;
 }
 
+static void mark_alternate_complete(const struct ref *ref, void *unused)
+{
+       mark_complete(NULL, ref->old_sha1, 0, NULL);
+}
+
 static int everything_local(struct ref **refs, int nr_match, char **match)
 {
        struct ref *ref;
@@ -609,6 +614,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
 
        if (!args.depth) {
                for_each_ref(mark_complete, NULL);
+               for_each_alternate_ref(mark_alternate_complete, NULL);
                if (cutoff)
                        mark_recent_complete_commits(cutoff);
        }
@@ -976,7 +982,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 91731b909aeb22bf8d4e366b8b92281ac0f9ac0c..ab186332fa881e0f823b0042f2d650fda8f365a8 100644 (file)
@@ -240,23 +240,23 @@ static int s_update_ref(const char *action,
 
 static int update_local_ref(struct ref *ref,
                            const char *remote,
-                           char *display)
+                           struct strbuf *display)
 {
        struct commit *current = NULL, *updated;
        enum object_type type;
        struct branch *current_branch = branch_get(NULL);
        const char *pretty_ref = prettify_refname(ref->name);
 
-       *display = 0;
        type = sha1_object_info(ref->new_sha1, NULL);
        if (type < 0)
                die(_("object %s not found"), sha1_to_hex(ref->new_sha1));
 
        if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
                if (verbosity > 0)
-                       sprintf(display, "= %-*s %-*s -> %s", TRANSPORT_SUMMARY_WIDTH,
-                               _("[up to date]"), REFCOL_WIDTH, remote,
-                               pretty_ref);
+                       strbuf_addf(display, "= %-*s %-*s -> %s",
+                                   TRANSPORT_SUMMARY_WIDTH,
+                                   _("[up to date]"), REFCOL_WIDTH,
+                                   remote, pretty_ref);
                return 0;
        }
 
@@ -268,9 +268,10 @@ static int update_local_ref(struct ref *ref,
                 * If this is the head, and it's not okay to update
                 * the head, and the old value of the head isn't empty...
                 */
-               sprintf(display, _("! %-*s %-*s -> %s  (can't fetch in current branch)"),
-                       TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote,
-                       pretty_ref);
+               strbuf_addf(display,
+                           _("! %-*s %-*s -> %s  (can't fetch in current branch)"),
+                           TRANSPORT_SUMMARY_WIDTH, _("[rejected]"),
+                           REFCOL_WIDTH, remote, pretty_ref);
                return 1;
        }
 
@@ -278,9 +279,11 @@ static int update_local_ref(struct ref *ref,
            !prefixcmp(ref->name, "refs/tags/")) {
                int r;
                r = s_update_ref("updating tag", ref, 0);
-               sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-',
-                       TRANSPORT_SUMMARY_WIDTH, _("[tag update]"), REFCOL_WIDTH, remote,
-                       pretty_ref, r ? _("  (unable to update local ref)") : "");
+               strbuf_addf(display, "%c %-*s %-*s -> %s%s",
+                           r ? '!' : '-',
+                           TRANSPORT_SUMMARY_WIDTH, _("[tag update]"),
+                           REFCOL_WIDTH, remote, pretty_ref,
+                           r ? _("  (unable to update local ref)") : "");
                return r;
        }
 
@@ -303,9 +306,11 @@ static int update_local_ref(struct ref *ref,
                }
 
                r = s_update_ref(msg, ref, 0);
-               sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*',
-                       TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref,
-                       r ? _("  (unable to update local ref)") : "");
+               strbuf_addf(display, "%c %-*s %-*s -> %s%s",
+                           r ? '!' : '*',
+                           TRANSPORT_SUMMARY_WIDTH, what,
+                           REFCOL_WIDTH, remote, pretty_ref,
+                           r ? _("  (unable to update local ref)") : "");
                return r;
        }
 
@@ -319,9 +324,11 @@ static int update_local_ref(struct ref *ref,
                    (recurse_submodules != RECURSE_SUBMODULES_ON))
                        check_for_new_submodule_commits(ref->new_sha1);
                r = s_update_ref("fast-forward", ref, 1);
-               sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
-                       TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
-                       pretty_ref, r ? _("  (unable to update local ref)") : "");
+               strbuf_addf(display, "%c %-*s %-*s -> %s%s",
+                           r ? '!' : ' ',
+                           TRANSPORT_SUMMARY_WIDTH, quickref,
+                           REFCOL_WIDTH, remote, pretty_ref,
+                           r ? _("  (unable to update local ref)") : "");
                return r;
        } else if (force || ref->force) {
                char quickref[84];
@@ -333,15 +340,17 @@ static int update_local_ref(struct ref *ref,
                    (recurse_submodules != RECURSE_SUBMODULES_ON))
                        check_for_new_submodule_commits(ref->new_sha1);
                r = s_update_ref("forced-update", ref, 1);
-               sprintf(display, "%c %-*s %-*s -> %s  (%s)", r ? '!' : '+',
-                       TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
-                       pretty_ref,
-                       r ? _("unable to update local ref") : _("forced update"));
+               strbuf_addf(display, "%c %-*s %-*s -> %s  (%s)",
+                           r ? '!' : '+',
+                           TRANSPORT_SUMMARY_WIDTH, quickref,
+                           REFCOL_WIDTH, remote, pretty_ref,
+                           r ? _("unable to update local ref") : _("forced update"));
                return r;
        } else {
-               sprintf(display, "! %-*s %-*s -> %s  %s",
-                       TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote,
-                       pretty_ref, _("(non-fast-forward)"));
+               strbuf_addf(display, "! %-*s %-*s -> %s  %s",
+                           TRANSPORT_SUMMARY_WIDTH, _("[rejected]"),
+                           REFCOL_WIDTH, remote, pretty_ref,
+                           _("(non-fast-forward)"));
                return 1;
        }
 }
@@ -363,11 +372,12 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
 {
        FILE *fp;
        struct commit *commit;
-       int url_len, i, note_len, shown_url = 0, rc = 0;
-       char note[1024];
+       int url_len, i, shown_url = 0, rc = 0;
+       struct strbuf note = STRBUF_INIT;
        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)
@@ -384,84 +394,95 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                goto abort;
        }
 
-       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;
-               }
+       /*
+        * 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;
+                       }
 
-               commit = lookup_commit_reference_gently(rm->old_sha1, 1);
-               if (!commit)
-                       rm->merge = 0;
 
-               if (!strcmp(rm->name, "HEAD")) {
-                       kind = "";
-                       what = "";
-               }
-               else if (!prefixcmp(rm->name, "refs/heads/")) {
-                       kind = "branch";
-                       what = rm->name + 11;
-               }
-               else if (!prefixcmp(rm->name, "refs/tags/")) {
-                       kind = "tag";
-                       what = rm->name + 10;
-               }
-               else if (!prefixcmp(rm->name, "refs/remotes/")) {
-                       kind = "remote-tracking branch";
-                       what = rm->name + 13;
-               }
-               else {
-                       kind = "";
-                       what = rm->name;
-               }
+                       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;
-
-               note_len = 0;
-               if (*what) {
-                       if (*kind)
-                               note_len += sprintf(note + note_len, "%s ",
-                                                   kind);
-                       note_len += sprintf(note + note_len, "'%s' of ", what);
-               }
-               note[note_len] = '\0';
-               fprintf(fp, "%s\t%s\t%s",
-                       sha1_to_hex(commit ? commit->object.sha1 :
-                                   rm->old_sha1),
-                       rm->merge ? "" : "not-for-merge",
-                       note);
-               for (i = 0; i < url_len; ++i)
-                       if ('\n' == url[i])
-                               fputs("\\n", fp);
-                       else
-                               fputc(url[i], fp);
-               fputc('\n', fp);
-
-               if (ref) {
-                       rc |= update_local_ref(ref, what, note);
-                       free(ref);
-               } else
-                       sprintf(note, "* %-*s %-*s -> FETCH_HEAD",
-                               TRANSPORT_SUMMARY_WIDTH, *kind ? kind : "branch",
-                                REFCOL_WIDTH, *what ? what : "HEAD");
-               if (*note) {
-                       if (verbosity >= 0 && !shown_url) {
-                               fprintf(stderr, _("From %.*s\n"),
-                                               url_len, url);
-                               shown_url = 1;
+                       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);
                }
        }
 
@@ -471,6 +492,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                      "branches"), remote_name);
 
  abort:
+       strbuf_release(&note);
        free(url);
        fclose(fp);
        return rc;
@@ -563,7 +585,7 @@ static void find_non_local_tags(struct transport *transport,
 
        for_each_ref(add_existing, &existing_refs);
        for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
-               if (prefixcmp(ref->name, "refs/tags"))
+               if (prefixcmp(ref->name, "refs/tags/"))
                        continue;
 
                /*
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 d90e5d2b29f9ac72a104d6308c04497991a03c17..b01d76a24323e86e9c9cbf1cd3adc9c0d1b2c6d8 100644 (file)
@@ -628,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 3d7329d78c6e3ec31ed8ce03b8928c9ed24afb9e..e4ea90078384f198f5985596c64deb5f423e9e45 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;
 };
@@ -86,21 +72,6 @@ static inline void grep_unlock(void)
                pthread_mutex_unlock(&grep_mutex);
 }
 
-/* Used to serialize calls to read_sha1_file. */
-static pthread_mutex_t read_sha1_mutex;
-
-static inline void read_sha1_lock(void)
-{
-       if (use_threads)
-               pthread_mutex_lock(&read_sha1_mutex);
-}
-
-static inline void read_sha1_unlock(void)
-{
-       if (use_threads)
-               pthread_mutex_unlock(&read_sha1_mutex);
-}
-
 /* Signalled when a new work_item is added to todo. */
 static pthread_cond_t cond_add;
 
@@ -114,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();
 
@@ -122,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);
@@ -152,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;
@@ -193,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)
@@ -217,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);
@@ -255,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);
@@ -302,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)
 {
@@ -324,11 +265,8 @@ static int grep_config(const char *var, const char *value, void *cb)
        struct grep_opt *opt = cb;
        char *color = NULL;
 
-       switch (userdiff_config(var, value)) {
-       case 0: break;
-       case -1: return -1;
-       default: return 0;
-       }
+       if (userdiff_config(var, value) < 0)
+               return -1;
 
        if (!strcmp(var, "grep.extendedregexp")) {
                if (git_config_bool(var, value))
@@ -373,21 +311,9 @@ static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type
 {
        void *data;
 
-       read_sha1_lock();
+       grep_read_lock();
        data = read_sha1_file(sha1, type, size);
-       read_sha1_unlock();
-       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_unlock();
        return data;
 }
 
@@ -395,7 +321,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,
@@ -405,87 +330,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;
        }
 }
@@ -557,18 +446,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;
                }
 
@@ -613,10 +503,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));
@@ -1000,20 +890,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 */
@@ -1035,6 +911,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;
index 0945adbb3bb188b612341c31c8986fabb491928d..dd1c5c961db087d1a9f649463d41c0ccbd7a5199 100644 (file)
@@ -34,6 +34,8 @@ struct base_data {
        struct object_entry *obj;
        void *data;
        unsigned long size;
+       int ref_first, ref_last;
+       int ofs_first, ofs_last;
 };
 
 /*
@@ -172,10 +174,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)
@@ -221,6 +223,15 @@ static NORETURN void bad_object(unsigned long offset, const char *format, ...)
        die("pack has bad object at offset %lu: %s", offset, buf);
 }
 
+static struct base_data *alloc_base_data(void)
+{
+       struct base_data *base = xmalloc(sizeof(struct base_data));
+       memset(base, 0, sizeof(*base));
+       base->ref_last = -1;
+       base->ofs_last = -1;
+       return base;
+}
+
 static void free_base_data(struct base_data *c)
 {
        if (c->data) {
@@ -504,14 +515,52 @@ static int is_delta_type(enum object_type type)
        return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
 }
 
+/*
+ * This function is part of find_unresolved_deltas(). There are two
+ * walkers going in the opposite ways.
+ *
+ * The first one in find_unresolved_deltas() traverses down from
+ * parent node to children, deflating nodes along the way. However,
+ * memory for deflated nodes is limited by delta_base_cache_limit, so
+ * at some point parent node's deflated content may be freed.
+ *
+ * The second walker is this function, which goes from current node up
+ * to top parent if necessary to deflate the node. In normal
+ * situation, its parent node would be already deflated, so it just
+ * needs to apply delta.
+ *
+ * In the worst case scenario, parent node is no longer deflated because
+ * we're running out of delta_base_cache_limit; we need to re-deflate
+ * parents, possibly up to the top base.
+ *
+ * All deflated objects here are subject to be freed if we exceed
+ * delta_base_cache_limit, just like in find_unresolved_deltas(), we
+ * just need to make sure the last node is not freed.
+ */
 static void *get_base_data(struct base_data *c)
 {
        if (!c->data) {
                struct object_entry *obj = c->obj;
+               struct base_data **delta = NULL;
+               int delta_nr = 0, delta_alloc = 0;
 
-               if (is_delta_type(obj->type)) {
-                       void *base = get_base_data(c->base);
-                       void *raw = get_data_from_pack(obj);
+               while (is_delta_type(c->obj->type) && !c->data) {
+                       ALLOC_GROW(delta, delta_nr + 1, delta_alloc);
+                       delta[delta_nr++] = c;
+                       c = c->base;
+               }
+               if (!delta_nr) {
+                       c->data = get_data_from_pack(obj);
+                       c->size = obj->size;
+                       base_cache_used += c->size;
+                       prune_base_data(c);
+               }
+               for (; delta_nr > 0; delta_nr--) {
+                       void *base, *raw;
+                       c = delta[delta_nr - 1];
+                       obj = c->obj;
+                       base = get_base_data(c->base);
+                       raw = get_data_from_pack(obj);
                        c->data = patch_delta(
                                base, c->base->size,
                                raw, obj->size,
@@ -519,13 +568,10 @@ static void *get_base_data(struct base_data *c)
                        free(raw);
                        if (!c->data)
                                bad_object(obj->idx.offset, "failed to apply delta");
-               } else {
-                       c->data = get_data_from_pack(obj);
-                       c->size = obj->size;
+                       base_cache_used += c->size;
+                       prune_base_data(c);
                }
-
-               base_cache_used += c->size;
-               prune_base_data(c);
+               free(delta);
        }
        return c->data;
 }
@@ -553,58 +599,76 @@ static void resolve_delta(struct object_entry *delta_obj,
        nr_resolved_deltas++;
 }
 
-static void find_unresolved_deltas(struct base_data *base,
-                                  struct base_data *prev_base)
+static struct base_data *find_unresolved_deltas_1(struct base_data *base,
+                                                 struct base_data *prev_base)
 {
-       int i, ref_first, ref_last, ofs_first, ofs_last;
-
-       /*
-        * This is a recursive function. Those brackets should help reducing
-        * stack usage by limiting the scope of the delta_base union.
-        */
-       {
+       if (base->ref_last == -1 && base->ofs_last == -1) {
                union delta_base base_spec;
 
                hashcpy(base_spec.sha1, base->obj->idx.sha1);
                find_delta_children(&base_spec,
-                                   &ref_first, &ref_last, OBJ_REF_DELTA);
+                                   &base->ref_first, &base->ref_last, OBJ_REF_DELTA);
 
                memset(&base_spec, 0, sizeof(base_spec));
                base_spec.offset = base->obj->idx.offset;
                find_delta_children(&base_spec,
-                                   &ofs_first, &ofs_last, OBJ_OFS_DELTA);
-       }
+                                   &base->ofs_first, &base->ofs_last, OBJ_OFS_DELTA);
 
-       if (ref_last == -1 && ofs_last == -1) {
-               free(base->data);
-               return;
-       }
+               if (base->ref_last == -1 && base->ofs_last == -1) {
+                       free(base->data);
+                       return NULL;
+               }
 
-       link_base_data(prev_base, base);
+               link_base_data(prev_base, base);
+       }
 
-       for (i = ref_first; i <= ref_last; i++) {
-               struct object_entry *child = objects + deltas[i].obj_no;
-               struct base_data result;
+       if (base->ref_first <= base->ref_last) {
+               struct object_entry *child = objects + deltas[base->ref_first].obj_no;
+               struct base_data *result = alloc_base_data();
 
                assert(child->real_type == OBJ_REF_DELTA);
-               resolve_delta(child, base, &result);
-               if (i == ref_last && ofs_last == -1)
+               resolve_delta(child, base, result);
+               if (base->ref_first == base->ref_last && base->ofs_last == -1)
                        free_base_data(base);
-               find_unresolved_deltas(&result, base);
+
+               base->ref_first++;
+               return result;
        }
 
-       for (i = ofs_first; i <= ofs_last; i++) {
-               struct object_entry *child = objects + deltas[i].obj_no;
-               struct base_data result;
+       if (base->ofs_first <= base->ofs_last) {
+               struct object_entry *child = objects + deltas[base->ofs_first].obj_no;
+               struct base_data *result = alloc_base_data();
 
                assert(child->real_type == OBJ_OFS_DELTA);
-               resolve_delta(child, base, &result);
-               if (i == ofs_last)
+               resolve_delta(child, base, result);
+               if (base->ofs_first == base->ofs_last)
                        free_base_data(base);
-               find_unresolved_deltas(&result, base);
+
+               base->ofs_first++;
+               return result;
        }
 
        unlink_base_data(base);
+       return NULL;
+}
+
+static void find_unresolved_deltas(struct base_data *base)
+{
+       struct base_data *new_base, *prev_base = NULL;
+       for (;;) {
+               new_base = find_unresolved_deltas_1(base, prev_base);
+
+               if (new_base) {
+                       prev_base = base;
+                       base = new_base;
+               } else {
+                       free(base);
+                       base = prev_base;
+                       if (!base)
+                               return;
+                       prev_base = base->base;
+               }
+       }
 }
 
 static int compare_delta_entry(const void *a, const void *b)
@@ -684,13 +748,13 @@ static void parse_pack_objects(unsigned char *sha1)
                progress = start_progress("Resolving deltas", nr_deltas);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
-               struct base_data base_obj;
+               struct base_data *base_obj = alloc_base_data();
 
                if (is_delta_type(obj->type))
                        continue;
-               base_obj.obj = obj;
-               base_obj.data = NULL;
-               find_unresolved_deltas(&base_obj, NULL);
+               base_obj->obj = obj;
+               base_obj->data = NULL;
+               find_unresolved_deltas(base_obj);
                display_progress(progress, nr_resolved_deltas);
        }
 }
@@ -783,20 +847,20 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
        for (i = 0; i < n; i++) {
                struct delta_entry *d = sorted_by_pos[i];
                enum object_type type;
-               struct base_data base_obj;
+               struct base_data *base_obj = alloc_base_data();
 
                if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
                        continue;
-               base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
-               if (!base_obj.data)
+               base_obj->data = read_sha1_file(d->base.sha1, &type, &base_obj->size);
+               if (!base_obj->data)
                        continue;
 
-               if (check_sha1_signature(d->base.sha1, base_obj.data,
-                               base_obj.size, typename(type)))
+               if (check_sha1_signature(d->base.sha1, base_obj->data,
+                               base_obj->size, typename(type)))
                        die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
-               base_obj.obj = append_obj_to_pack(f, d->base.sha1,
-                                       base_obj.data, base_obj.size, type);
-               find_unresolved_deltas(&base_obj, NULL);
+               base_obj->obj = append_obj_to_pack(f, d->base.sha1,
+                                       base_obj->data, base_obj->size, type);
+               find_unresolved_deltas(base_obj);
                display_progress(progress, nr_resolved_deltas);
        }
        free(sorted_by_pos);
@@ -1122,8 +1186,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 bfb32b7233850a68bdc226038a9c0f973037499b..eaf9e157a3897c2442756911b906ff9d3ee2be90 100644 (file)
@@ -250,8 +250,17 @@ static void cleanup_subject(struct strbuf *subject)
                            (7 <= remove &&
                             memmem(subject->buf + at, remove, "PATCH", 5)))
                                strbuf_remove(subject, at, remove);
-                       else
+                       else {
                                at += remove;
+                               /*
+                                * If the input had a space after the ], keep
+                                * it.  We don't bother with finding the end of
+                                * the space, since we later normalize it
+                                * anyway.
+                                */
+                               if (isspace(subject->buf[at]))
+                                       at += 1;
+                       }
                        continue;
                }
                break;
index 2870a6af6fbc868455f2961483cfbfdb065a1a14..ed0f959ac49c02aaffa77bc748a9efc69f1fd339 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, option_edit;
+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,7 +193,7 @@ 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_BOOLEAN('e', "edit", &option_edit,
+       OPT_BOOL('e', "edit", &option_edit,
                "edit message before committing"),
        OPT_BOOLEAN(0, "ff", &allow_fast_forward,
                "allow fast-forward (default)"),
@@ -207,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()
 };
 
@@ -408,21 +415,11 @@ static void finish(struct commit *head_commit,
        strbuf_release(&reflog_message);
 }
 
-static struct object *want_commit(const char *name)
-{
-       struct object *obj;
-       unsigned char sha1[20];
-       if (get_sha1(name, sha1))
-               return NULL;
-       obj = parse_object(sha1);
-       return peel_to_type(name, 0, obj, OBJ_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;
@@ -433,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 = want_commit(remote);
+       remote_head = get_merge_parent(remote);
        if (!remote_head)
                die(_("'%s' does not point to a commit"), remote);
 
@@ -443,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);
@@ -481,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);
@@ -514,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);
@@ -542,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")) {
@@ -558,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;
@@ -579,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);
 }
 
@@ -718,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);
@@ -773,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;
-       dir.exclude_per_dir = ".gitignore";
-       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;
@@ -880,20 +885,30 @@ static void abort_commit(const char *err_msg)
        exit(1);
 }
 
+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)
 {
        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);
-       if (option_edit) {
+       if (0 < option_edit) {
                if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
                        abort_commit(NULL);
        }
        read_merge_msg(&msg);
-       stripspace(&msg, option_edit);
+       stripspace(&msg, 0 < option_edit);
        if (!msg.len)
                abort_commit(_("Empty commit message."));
        strbuf_release(&merge_msg);
@@ -913,7 +928,9 @@ static int merge_trivial(struct commit *head)
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
        prepare_to_commit();
-       commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
+       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;
@@ -944,7 +961,9 @@ static int finish_automerge(struct commit *head,
        strbuf_addch(&merge_msg, '\n');
        prepare_to_commit();
        free_commit_list(remoteheads);
-       commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
+       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);
@@ -1058,9 +1077,16 @@ static void write_merge_state(void)
        struct commit_list *j;
        struct strbuf buf = STRBUF_INIT;
 
-       for (j = remoteheads; j; j = j->next)
-               strbuf_addf(&buf, "%s\n",
-                       sha1_to_hex(j->item->object.sha1));
+       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)
@@ -1083,6 +1109,33 @@ static void write_merge_state(void)
        close(fd);
 }
 
+static int default_edit_option(void)
+{
+       static const char name[] = "GIT_MERGE_AUTOEDIT";
+       const char *e = getenv(name);
+       struct stat st_stdin, st_stdout;
+
+       if (have_message)
+               /* an explicit -m msg without --[no-]edit */
+               return 0;
+
+       if (e) {
+               int v = git_config_maybe_bool(name, e);
+               if (v < 0)
+                       die("Bad value '%s' in environment '%s'", e, name);
+               return v;
+       }
+
+       /* Use editor if stdin and stdout are the same and is a tty */
+       return (!fstat(0, &st_stdin) &&
+               !fstat(1, &st_stdout) &&
+               isatty(0) &&
+               st_stdin.st_dev == st_stdout.st_dev &&
+               st_stdin.st_ino == st_stdout.st_ino &&
+               st_stdin.st_mode == st_stdout.st_mode);
+}
+
+
 int cmd_merge(int argc, const char **argv, const char *prefix)
 {
        unsigned char result_tree[20];
@@ -1091,11 +1144,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);
@@ -1104,7 +1158,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))
@@ -1118,6 +1172,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;
@@ -1130,7 +1186,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())
@@ -1169,9 +1226,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)
@@ -1194,7 +1254,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.
@@ -1208,13 +1268,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 = want_commit(argv[0]);
+               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;
 
@@ -1222,19 +1282,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);
                }
@@ -1251,21 +1312,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 = want_commit(argv[i]);
-               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 = default_edit_option();
+
        if (!use_strategies) {
                if (!remoteheads->next)
                        add_strategies(pull_twohead, DEFAULT_TWOHEAD);
@@ -1301,13 +1368,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));
@@ -1321,16 +1388,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 = want_commit(sha1_to_hex(remoteheads->item->object.sha1));
-               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)
                ;
                /*
@@ -1348,8 +1420,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 {
@@ -1377,7 +1452,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;
                }
        }
 
@@ -1459,9 +1534,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
@@ -1475,7 +1552,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 {
@@ -1491,10 +1569,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        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 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 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 824ecee20b94c471083ad8c7f697b39fbb7cb2b8..e21e5af8f9753ada34837df2c5d4c95234c7c00a 100644 (file)
 #include "refs.h"
 #include "thread-utils.h"
 
-static const char pack_usage[] =
-  "git pack-objects [ -q | --progress | --all-progress ]\n"
-  "        [--all-progress-implied]\n"
-  "        [--max-pack-size=<n>] [--local] [--incremental]\n"
-  "        [--window=<n>] [--window-memory=<n>] [--depth=<n>]\n"
-  "        [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n"
-  "        [--threads=<n>] [--non-empty] [--revs [--unpacked | --all]]\n"
-  "        [--reflog] [--stdout | base-name] [--include-tag]\n"
-  "        [--keep-unreachable | --unpack-unreachable]\n"
-  "        [< ref-list | < object-list]";
+static const char *pack_usage[] = {
+       "git pack-objects --stdout [options...] [< ref-list | < object-list]",
+       "git pack-objects [options...] base-name [< ref-list | < object-list]",
+       NULL
+};
 
 struct object_entry {
        struct pack_idx_entry idx;
@@ -76,7 +71,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 +404,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 +461,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 +633,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 +646,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);
-               }
+               else
+                       f = create_tmp_packfile(&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);
+               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 +679,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 +688,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));
                }
@@ -1015,7 +1019,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;
@@ -1425,11 +1429,16 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
                return -1;
 
        /*
-        * We do not bother to try a delta that we discarded
-        * on an earlier try, but only when reusing delta data.
+        * We do not bother to try a delta that we discarded on an
+        * earlier try, but only when reusing delta data.  Note that
+        * src_entry that is marked as the preferred_base should always
+        * be considered, as even if we produce a suboptimal delta against
+        * it, we will still save the transfer cost, as we already know
+        * the other side has it and we won't send src_entry at all.
         */
        if (reuse_delta && trg_entry->in_pack &&
            trg_entry->in_pack == src_entry->in_pack &&
+           !src_entry->preferred_base &&
            trg_entry->in_pack_type != OBJ_REF_DELTA &&
            trg_entry->in_pack_type != OBJ_OFS_DELTA)
                return 0;
@@ -2067,10 +2076,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);
 }
 
@@ -2295,204 +2300,159 @@ static void get_object_list(int ac, const char **av)
                loosen_unused_packed_objects(&revs);
 }
 
+static int option_parse_index_version(const struct option *opt,
+                                     const char *arg, int unset)
+{
+       char *c;
+       const char *val = arg;
+       pack_idx_opts.version = strtoul(val, &c, 10);
+       if (pack_idx_opts.version > 2)
+               die(_("unsupported index version %s"), val);
+       if (*c == ',' && c[1])
+               pack_idx_opts.off32_limit = strtoul(c+1, &c, 0);
+       if (*c || pack_idx_opts.off32_limit & 0x80000000)
+               die(_("bad index version '%s'"), val);
+       return 0;
+}
+
+static int option_parse_ulong(const struct option *opt,
+                             const char *arg, int unset)
+{
+       if (unset)
+               die(_("option %s does not accept negative form"),
+                   opt->long_name);
+
+       if (!git_parse_ulong(arg, opt->value))
+               die(_("unable to parse value '%s' for option %s"),
+                   arg, opt->long_name);
+       return 0;
+}
+
+#define OPT_ULONG(s, l, v, h) \
+       { OPTION_CALLBACK, (s), (l), (v), "n", (h),     \
+         PARSE_OPT_NONEG, option_parse_ulong }
+
 int cmd_pack_objects(int argc, const char **argv, const char *prefix)
 {
        int use_internal_rev_list = 0;
        int thin = 0;
        int all_progress_implied = 0;
-       uint32_t i;
-       const char **rp_av;
-       int rp_ac_alloc = 64;
-       int rp_ac;
+       const char *rp_av[6];
+       int rp_ac = 0;
+       int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0;
+       struct option pack_objects_options[] = {
+               OPT_SET_INT('q', "quiet", &progress,
+                           "do not show progress meter", 0),
+               OPT_SET_INT(0, "progress", &progress,
+                           "show progress meter", 1),
+               OPT_SET_INT(0, "all-progress", &progress,
+                           "show progress meter during object writing phase", 2),
+               OPT_BOOL(0, "all-progress-implied",
+                        &all_progress_implied,
+                        "similar to --all-progress when progress meter is shown"),
+               { OPTION_CALLBACK, 0, "index-version", NULL, "version[,offset]",
+                 "write the pack index file in the specified idx format version",
+                 0, option_parse_index_version },
+               OPT_ULONG(0, "max-pack-size", &pack_size_limit,
+                         "maximum size of each output pack file"),
+               OPT_BOOL(0, "local", &local,
+                        "ignore borrowed objects from alternate object store"),
+               OPT_BOOL(0, "incremental", &incremental,
+                        "ignore packed objects"),
+               OPT_INTEGER(0, "window", &window,
+                           "limit pack window by objects"),
+               OPT_ULONG(0, "window-memory", &window_memory_limit,
+                         "limit pack window by memory in addition to object limit"),
+               OPT_INTEGER(0, "depth", &depth,
+                           "maximum length of delta chain allowed in the resulting pack"),
+               OPT_BOOL(0, "reuse-delta", &reuse_delta,
+                        "reuse existing deltas"),
+               OPT_BOOL(0, "reuse-object", &reuse_object,
+                        "reuse existing objects"),
+               OPT_BOOL(0, "delta-base-offset", &allow_ofs_delta,
+                        "use OFS_DELTA objects"),
+               OPT_INTEGER(0, "threads", &delta_search_threads,
+                           "use threads when searching for best delta matches"),
+               OPT_BOOL(0, "non-empty", &non_empty,
+                        "do not create an empty pack output"),
+               OPT_BOOL(0, "revs", &use_internal_rev_list,
+                        "read revision arguments from standard input"),
+               { OPTION_SET_INT, 0, "unpacked", &rev_list_unpacked, NULL,
+                 "limit the objects to those that are not yet packed",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+               { OPTION_SET_INT, 0, "all", &rev_list_all, NULL,
+                 "include objects reachable from any reference",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+               { OPTION_SET_INT, 0, "reflog", &rev_list_reflog, NULL,
+                 "include objects referred by reflog entries",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+               OPT_BOOL(0, "stdout", &pack_to_stdout,
+                        "output pack to stdout"),
+               OPT_BOOL(0, "include-tag", &include_tag,
+                        "include tag objects that refer to objects to be packed"),
+               OPT_BOOL(0, "keep-unreachable", &keep_unreachable,
+                        "keep unreachable objects"),
+               OPT_BOOL(0, "unpack-unreachable", &unpack_unreachable,
+                        "unpack unreachable objects"),
+               OPT_BOOL(0, "thin", &thin,
+                        "create thin packs"),
+               OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep,
+                        "ignore packs that have companion .keep file"),
+               OPT_INTEGER(0, "compression", &pack_compression_level,
+                           "pack compression level"),
+               OPT_SET_INT(0, "keep-true-parents", &grafts_replace_parents,
+                           "do not hide commits by grafts", 0),
+               OPT_END(),
+       };
 
        read_replace_refs = 0;
 
-       rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av));
-
-       rp_av[0] = "pack-objects";
-       rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
-       rp_ac = 2;
-
        reset_pack_idx_option(&pack_idx_opts);
        git_config(git_pack_config, NULL);
        if (!pack_compression_seen && core_compression_seen)
                pack_compression_level = core_compression_level;
 
        progress = isatty(2);
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (*arg != '-')
-                       break;
+       argc = parse_options(argc, argv, prefix, pack_objects_options,
+                            pack_usage, 0);
 
-               if (!strcmp("--non-empty", arg)) {
-                       non_empty = 1;
-                       continue;
-               }
-               if (!strcmp("--local", arg)) {
-                       local = 1;
-                       continue;
-               }
-               if (!strcmp("--incremental", arg)) {
-                       incremental = 1;
-                       continue;
-               }
-               if (!strcmp("--honor-pack-keep", arg)) {
-                       ignore_packed_keep = 1;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--compression=")) {
-                       char *end;
-                       int level = strtoul(arg+14, &end, 0);
-                       if (!arg[14] || *end)
-                               usage(pack_usage);
-                       if (level == -1)
-                               level = Z_DEFAULT_COMPRESSION;
-                       else if (level < 0 || level > Z_BEST_COMPRESSION)
-                               die("bad pack compression level %d", level);
-                       pack_compression_level = level;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--max-pack-size=")) {
-                       pack_size_limit_cfg = 0;
-                       if (!git_parse_ulong(arg+16, &pack_size_limit))
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--window=")) {
-                       char *end;
-                       window = strtoul(arg+9, &end, 0);
-                       if (!arg[9] || *end)
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--window-memory=")) {
-                       if (!git_parse_ulong(arg+16, &window_memory_limit))
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--threads=")) {
-                       char *end;
-                       delta_search_threads = strtoul(arg+10, &end, 0);
-                       if (!arg[10] || *end || delta_search_threads < 0)
-                               usage(pack_usage);
-#ifdef NO_PTHREADS
-                       if (delta_search_threads != 1)
-                               warning("no threads support, "
-                                       "ignoring %s", arg);
-#endif
-                       continue;
-               }
-               if (!prefixcmp(arg, "--depth=")) {
-                       char *end;
-                       depth = strtoul(arg+8, &end, 0);
-                       if (!arg[8] || *end)
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!strcmp("--progress", arg)) {
-                       progress = 1;
-                       continue;
-               }
-               if (!strcmp("--all-progress", arg)) {
-                       progress = 2;
-                       continue;
-               }
-               if (!strcmp("--all-progress-implied", arg)) {
-                       all_progress_implied = 1;
-                       continue;
-               }
-               if (!strcmp("-q", arg)) {
-                       progress = 0;
-                       continue;
-               }
-               if (!strcmp("--no-reuse-delta", arg)) {
-                       reuse_delta = 0;
-                       continue;
-               }
-               if (!strcmp("--no-reuse-object", arg)) {
-                       reuse_object = reuse_delta = 0;
-                       continue;
-               }
-               if (!strcmp("--delta-base-offset", arg)) {
-                       allow_ofs_delta = 1;
-                       continue;
-               }
-               if (!strcmp("--stdout", arg)) {
-                       pack_to_stdout = 1;
-                       continue;
-               }
-               if (!strcmp("--revs", arg)) {
-                       use_internal_rev_list = 1;
-                       continue;
-               }
-               if (!strcmp("--keep-unreachable", arg)) {
-                       keep_unreachable = 1;
-                       continue;
-               }
-               if (!strcmp("--unpack-unreachable", arg)) {
-                       unpack_unreachable = 1;
-                       continue;
-               }
-               if (!strcmp("--include-tag", arg)) {
-                       include_tag = 1;
-                       continue;
-               }
-               if (!strcmp("--unpacked", arg) ||
-                   !strcmp("--reflog", arg) ||
-                   !strcmp("--all", arg)) {
-                       use_internal_rev_list = 1;
-                       if (rp_ac >= rp_ac_alloc - 1) {
-                               rp_ac_alloc = alloc_nr(rp_ac_alloc);
-                               rp_av = xrealloc(rp_av,
-                                                rp_ac_alloc * sizeof(*rp_av));
-                       }
-                       rp_av[rp_ac++] = arg;
-                       continue;
-               }
-               if (!strcmp("--thin", arg)) {
-                       use_internal_rev_list = 1;
-                       thin = 1;
-                       rp_av[1] = "--objects-edge";
-                       continue;
-               }
-               if (!prefixcmp(arg, "--index-version=")) {
-                       char *c;
-                       pack_idx_opts.version = strtoul(arg + 16, &c, 10);
-                       if (pack_idx_opts.version > 2)
-                               die("bad %s", arg);
-                       if (*c == ',')
-                               pack_idx_opts.off32_limit = strtoul(c+1, &c, 0);
-                       if (*c || pack_idx_opts.off32_limit & 0x80000000)
-                               die("bad %s", arg);
-                       continue;
-               }
-               if (!strcmp(arg, "--keep-true-parents")) {
-                       grafts_replace_parents = 0;
-                       continue;
-               }
-               usage(pack_usage);
-       }
-
-       /* Traditionally "pack-objects [options] base extra" failed;
-        * we would however want to take refs parameter that would
-        * have been given to upstream rev-list ourselves, which means
-        * we somehow want to say what the base name is.  So the
-        * syntax would be:
-        *
-        * pack-objects [options] base <refs...>
-        *
-        * in other words, we would treat the first non-option as the
-        * base_name and send everything else to the internal revision
-        * walker.
-        */
+       if (argc) {
+               base_name = argv[0];
+               argc--;
+       }
+       if (pack_to_stdout != !base_name || argc)
+               usage_with_options(pack_usage, pack_objects_options);
 
-       if (!pack_to_stdout)
-               base_name = argv[i++];
+       rp_av[rp_ac++] = "pack-objects";
+       if (thin) {
+               use_internal_rev_list = 1;
+               rp_av[rp_ac++] = "--objects-edge";
+       } else
+               rp_av[rp_ac++] = "--objects";
 
-       if (pack_to_stdout != !base_name)
-               usage(pack_usage);
+       if (rev_list_all) {
+               use_internal_rev_list = 1;
+               rp_av[rp_ac++] = "--all";
+       }
+       if (rev_list_reflog) {
+               use_internal_rev_list = 1;
+               rp_av[rp_ac++] = "--reflog";
+       }
+       if (rev_list_unpacked) {
+               use_internal_rev_list = 1;
+               rp_av[rp_ac++] = "--unpacked";
+       }
 
+       if (!reuse_object)
+               reuse_delta = 0;
+       if (pack_compression_level == -1)
+               pack_compression_level = Z_DEFAULT_COMPRESSION;
+       else if (pack_compression_level < 0 || pack_compression_level > Z_BEST_COMPRESSION)
+               die("bad pack compression level %d", pack_compression_level);
+#ifdef NO_PTHREADS
+       if (delta_search_threads != 1)
+               warning("no threads support, ignoring %s", arg);
+#endif
        if (!pack_to_stdout && !pack_size_limit)
                pack_size_limit = pack_size_limit_cfg;
        if (pack_to_stdout && pack_size_limit)
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 7ec68a1e8089f74ce7c70fecd86d5c18264ce4bc..fa7448be5aaf9d2830168b77f9a555e9b6740993 100644 (file)
@@ -33,10 +33,12 @@ 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)
@@ -114,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);
        /*
@@ -140,15 +141,33 @@ 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 {
@@ -571,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))
@@ -695,7 +714,8 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
 
        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)
@@ -729,10 +749,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);
@@ -786,8 +809,10 @@ 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 (quiet)
+                       unpacker[i++] = "-q";
                if (fsck_objects)
                        unpacker[i++] = "--strict";
                unpacker[i++] = hdr_arg;
@@ -867,25 +892,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;
@@ -901,6 +907,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;
@@ -935,12 +946,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;
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 c810643815e38f4ada805e2af49973e4e0051801..f54a89adc795fe86f76d60cff4de9487bc792bd3 100644 (file)
@@ -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));
@@ -535,7 +534,7 @@ static int add_branch_for_removal(const char *refname,
        }
 
        /* don't delete non-remote-tracking refs */
-       if (prefixcmp(refname, "refs/remotes")) {
+       if (prefixcmp(refname, "refs/remotes/")) {
                /* advise user how to delete local branches */
                if (!prefixcmp(refname, "refs/heads/"))
                        string_list_append(branches->skipped,
@@ -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);
index 517fa1031a86f50c0d41ba567237aa701e9c2c05..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;
@@ -97,7 +97,7 @@ static int replace_object(const char *object_ref, const char *replace_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 1ea525c10e4c00b66006bf46465ebd490823f25f..e6840f23dc9ee6d670bb6254bee074e58e818486 100644 (file)
@@ -1,18 +1,9 @@
 #include "cache.h"
 #include "builtin.h"
-#include "object.h"
-#include "commit.h"
-#include "tag.h"
-#include "run-command.h"
-#include "exec_cmd.h"
-#include "utf8.h"
 #include "parse-options.h"
-#include "cache-tree.h"
 #include "diff.h"
 #include "revision.h"
 #include "rerere.h"
-#include "merge-recursive.h"
-#include "refs.h"
 #include "dir.h"
 #include "sequencer.h"
 
@@ -39,48 +30,14 @@ static const char * const cherry_pick_usage[] = {
        NULL
 };
 
-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;
-
-       /* Boolean options */
-       int edit;
-       int record_origin;
-       int no_commit;
-       int signoff;
-       int allow_ff;
-       int allow_rerere_auto;
-
-       int mainline;
-       int commit_argc;
-       const char **commit_argv;
-
-       /* Merge strategy */
-       const char *strategy;
-       const char **xopts;
-       size_t xopts_nr, xopts_alloc;
-};
-
-#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
-
 static const char *action_name(const struct replay_opts *opts)
 {
-       return opts->action == REVERT ? "revert" : "cherry-pick";
+       return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
 }
 
-static char *get_encoding(const char *message);
-
 static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts)
 {
-       return opts->action == REVERT ? revert_usage : cherry_pick_usage;
+       return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage;
 }
 
 static int option_parse_x(const struct option *opt,
@@ -159,7 +116,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                OPT_END(),
        };
 
-       if (opts->action == CHERRY_PICK) {
+       if (opts->action == REPLAY_PICK) {
                struct option cp_extra[] = {
                        OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"),
                        OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"),
@@ -169,9 +126,9 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                        die(_("program error"));
        }
 
-       opts->commit_argc = parse_options(argc, argv, NULL, options, usage_str,
-                                       PARSE_OPT_KEEP_ARGV0 |
-                                       PARSE_OPT_KEEP_UNKNOWN);
+       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,
@@ -213,9 +170,6 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                                NULL);
        }
 
-       else if (opts->commit_argc < 2)
-               usage_with_options(usage_str, options);
-
        if (opts->allow_ff)
                verify_opt_compatible(me, "--ff",
                                "--signoff", opts->signoff,
@@ -223,868 +177,20 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                                "-x", opts->record_origin,
                                "--edit", opts->edit,
                                NULL);
-       opts->commit_argv = argv;
-}
-
-struct commit_message {
-       char *parent_label;
-       const char *label;
-       const char *subject;
-       char *reencoded_message;
-       const char *message;
-};
-
-static int get_message(struct commit *commit, struct commit_message *out)
-{
-       const char *encoding;
-       const char *abbrev, *subject;
-       int abbrev_len, subject_len;
-       char *q;
-
-       if (!commit->buffer)
-               return -1;
-       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 = commit->buffer;
-       if (strcmp(encoding, git_commit_encoding))
-               out->reencoded_message = reencode_string(commit->buffer,
-                                       git_commit_encoding, encoding);
-       if (out->reencoded_message)
-               out->message = out->reencoded_message;
-
-       abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
-       abbrev_len = strlen(abbrev);
-
-       subject_len = find_commit_subject(out->message, &subject);
-
-       out->parent_label = xmalloc(strlen("parent of ") + abbrev_len +
-                             strlen("... ") + subject_len + 1);
-       q = out->parent_label;
-       q = mempcpy(q, "parent of ", strlen("parent of "));
-       out->label = q;
-       q = mempcpy(q, abbrev, abbrev_len);
-       q = mempcpy(q, "... ", strlen("... "));
-       out->subject = q;
-       q = mempcpy(q, subject, subject_len);
-       *q = '\0';
-       return 0;
-}
-
-static void free_message(struct commit_message *msg)
-{
-       free(msg->parent_label);
-       free(msg->reencoded_message);
-}
-
-static char *get_encoding(const char *message)
-{
-       const char *p = message, *eol;
-
-       while (*p && *p != '\n') {
-               for (eol = p + 1; *eol && *eol != '\n'; eol++)
-                       ; /* do nothing */
-               if (!prefixcmp(p, "encoding ")) {
-                       char *result = xmalloc(eol - 8 - p);
-                       strlcpy(result, p + 9, eol - 8 - p);
-                       return result;
-               }
-               p = eol;
-               if (*p == '\n')
-                       p++;
-       }
-       return NULL;
-}
-
-static void 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));
-
-       filename = git_path("%s", pseudoref);
-       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 || close(fd))
-               die_errno(_("Could not write to '%s'"), filename);
-       strbuf_release(&buf);
-}
-
-static void print_advice(int show_hint)
-{
-       char *msg = getenv("GIT_CHERRY_PICK_HELP");
-
-       if (msg) {
-               fprintf(stderr, "%s\n", msg);
-               /*
-                * A conflict has occured but the porcelain
-                * (typically rebase --interactive) wants to take care
-                * of the commit itself so remove CHERRY_PICK_HEAD
-                */
-               unlink(git_path("CHERRY_PICK_HEAD"));
-               return;
-       }
-
-       if (show_hint) {
-               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'");
-       }
-}
-
-static void write_message(struct strbuf *msgbuf, const char *filename)
-{
-       static struct lock_file msg_file;
-
-       int msg_fd = hold_lock_file_for_update(&msg_file, filename,
-                                              LOCK_DIE_ON_ERROR);
-       if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
-               die_errno(_("Could not write to %s"), filename);
-       strbuf_release(msgbuf);
-       if (commit_lock_file(&msg_file) < 0)
-               die(_("Error wrapping up %s"), filename);
-}
-
-static struct tree *empty_tree(void)
-{
-       return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
-}
-
-static int error_dirty_index(struct replay_opts *opts)
-{
-       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)
-{
-       struct ref_lock *ref_lock;
-
-       read_cache();
-       if (checkout_fast_forward(from, to))
-               exit(1); /* the callee should have complained already */
-       ref_lock = lock_any_ref_for_update("HEAD", from, 0);
-       return write_ref_sha1(ref_lock, to, "cherry-pick");
-}
 
-static int do_recursive_merge(struct commit *base, struct commit *next,
-                             const char *base_label, const char *next_label,
-                             unsigned char *head, struct strbuf *msgbuf,
-                             struct replay_opts *opts)
-{
-       struct merge_options o;
-       struct tree *result, *next_tree, *base_tree, *head_tree;
-       int clean, index_fd;
-       const char **xopt;
-       static struct lock_file index_lock;
-
-       index_fd = hold_locked_index(&index_lock, 1);
-
-       read_cache();
-
-       init_merge_options(&o);
-       o.ancestor = base ? base_label : "(empty tree)";
-       o.branch1 = "HEAD";
-       o.branch2 = next ? next_label : "(empty tree)";
-
-       head_tree = parse_tree_indirect(head);
-       next_tree = next ? next->tree : empty_tree();
-       base_tree = base ? base->tree : empty_tree();
-
-       for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
-               parse_merge_opt(&o, *xopt);
-
-       clean = merge_trees(&o,
-                           head_tree,
-                           next_tree, base_tree, &result);
-
-       if (active_cache_changed &&
-           (write_cache(index_fd, active_cache, active_nr) ||
-            commit_locked_index(&index_lock)))
-               /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
-               die(_("%s: Unable to write new index file"), action_name(opts));
-       rollback_lock_file(&index_lock);
-
-       if (!clean) {
-               int i;
-               strbuf_addstr(msgbuf, "\nConflicts:\n\n");
-               for (i = 0; i < active_nr;) {
-                       struct cache_entry *ce = active_cache[i++];
-                       if (ce_stage(ce)) {
-                               strbuf_addch(msgbuf, '\t');
-                               strbuf_addstr(msgbuf, ce->name);
-                               strbuf_addch(msgbuf, '\n');
-                               while (i < active_nr && !strcmp(ce->name,
-                                               active_cache[i]->name))
-                                       i++;
-                       }
-               }
-       }
-
-       return !clean;
-}
-
-/*
- * If we are cherry-pick, and if the merge did not result in
- * hand-editing, we will hit this commit and inherit the original
- * author date and name.
- * If we are revert, or if our cherry-pick results in a hand merge,
- * we had better say that the current user is responsible for that.
- */
-static int run_git_commit(const char *defmsg, struct replay_opts *opts)
-{
-       /* 6 is max possible length of our args array including NULL */
-       const char *args[6];
-       int i = 0;
-
-       args[i++] = "commit";
-       args[i++] = "-n";
-       if (opts->signoff)
-               args[i++] = "-s";
-       if (!opts->edit) {
-               args[i++] = "-F";
-               args[i++] = defmsg;
-       }
-       args[i] = NULL;
-
-       return run_command_v_opt(args, RUN_GIT_CMD);
-}
-
-static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
-{
-       unsigned char head[20];
-       struct commit *base, *next, *parent;
-       const char *base_label, *next_label;
-       struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
-       char *defmsg = NULL;
-       struct strbuf msgbuf = STRBUF_INIT;
-       int res;
-
-       if (opts->no_commit) {
-               /*
-                * We do not intend to commit immediately.  We just want to
-                * merge the differences in, so let's compute the tree
-                * that represents the "current" state for merge-recursive
-                * to work on.
-                */
-               if (write_cache_as_tree(head, 0, NULL))
-                       die (_("Your index file is unmerged."));
-       } else {
-               if (get_sha1("HEAD", head))
-                       return error(_("You do not have a valid HEAD"));
-               if (index_differs_from("HEAD", 0))
-                       return error_dirty_index(opts);
-       }
-       discard_cache();
-
-       if (!commit->parents) {
-               parent = NULL;
-       }
-       else if (commit->parents->next) {
-               /* Reverting or cherry-picking a merge commit */
-               int cnt;
-               struct commit_list *p;
-
-               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 != opts->mainline && p;
-                    cnt++)
-                       p = p->next;
-               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 < 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 (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 */
-               return error(_("%s: cannot parse parent commit %s"),
-                       action_name(opts), sha1_to_hex(parent->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
-        * the difference it introduces since its first parent "prev"
-        * on top of the current HEAD if we are cherry-pick.  Or the
-        * reverse of it if we are revert.
-        */
-
-       defmsg = git_pathdup("MERGE_MSG");
-
-       if (opts->action == REVERT) {
-               base = commit;
-               base_label = msg.label;
-               next = parent;
-               next_label = msg.parent_label;
-               strbuf_addstr(&msgbuf, "Revert \"");
-               strbuf_addstr(&msgbuf, msg.subject);
-               strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
-               strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
-
-               if (commit->parents && commit->parents->next) {
-                       strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
-                       strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));
-               }
-               strbuf_addstr(&msgbuf, ".\n");
-       } else {
-               const char *p;
-
-               base = parent;
-               base_label = msg.parent_label;
-               next = commit;
-               next_label = msg.label;
-
-               /*
-                * 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 (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REVERT) {
-               res = do_recursive_merge(base, next, base_label, next_label,
-                                        head, &msgbuf, opts);
-               write_message(&msgbuf, defmsg);
-       } else {
-               struct commit_list *common = NULL;
-               struct commit_list *remotes = NULL;
-
-               write_message(&msgbuf, defmsg);
-
-               commit_list_insert(base, &common);
-               commit_list_insert(next, &remotes);
-               res = try_merge_command(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(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(res == 1);
-               rerere(opts->allow_rerere_auto);
+       if (opts->subcommand != REPLAY_NONE) {
+               opts->revs = NULL;
        } else {
-               if (!opts->no_commit)
-                       res = run_git_commit(defmsg, opts);
+               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);
        }
 
-       free_message(&msg);
-       free(defmsg);
-
-       return res;
-}
-
-static void prepare_revs(struct rev_info *revs, struct replay_opts *opts)
-{
-       int argc;
-
-       init_revisions(revs, NULL);
-       revs->no_walk = 1;
-       if (opts->action != REVERT)
-               revs->reverse = 1;
-
-       argc = setup_revisions(opts->commit_argc, opts->commit_argv, revs, NULL);
        if (argc > 1)
-               usage(*revert_or_cherry_pick_usage(opts));
-
-       if (prepare_revision_walk(revs))
-               die(_("revision walk setup failed"));
-
-       if (!revs->commits)
-               die(_("empty commit set passed"));
-}
-
-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"), 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"), action_name(opts));
-       }
-       rollback_lock_file(&index_lock);
-}
-
-/*
- * 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 commit_list *new = xmalloc(sizeof(struct commit_list));
-       new->item = commit;
-       *next = new;
-       new->next = NULL;
-       return &new->next;
-}
-
-static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
-               struct replay_opts *opts)
-{
-       struct commit_list *cur = NULL;
-       struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
-       const char *sha1_abbrev = NULL;
-       const char *action_str = opts->action == REVERT ? "revert" : "pick";
-
-       for (cur = todo_list; cur; cur = cur->next) {
-               sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
-               if (get_message(cur->item, &msg))
-                       return error(_("Cannot get commit message for %s"), sha1_abbrev);
-               strbuf_addf(buf, "%s %s %s\n", action_str, sha1_abbrev, msg.subject);
-       }
-       return 0;
-}
-
-static struct commit *parse_insn_line(char *start, struct replay_opts *opts)
-{
-       unsigned char commit_sha1[20];
-       char sha1_abbrev[40];
-       enum replay_action action;
-       int insn_len = 0;
-       char *p, *q;
-
-       if (!prefixcmp(start, "pick ")) {
-               action = CHERRY_PICK;
-               insn_len = strlen("pick");
-               p = start + insn_len + 1;
-       } else if (!prefixcmp(start, "revert ")) {
-               action = REVERT;
-               insn_len = strlen("revert");
-               p = start + insn_len + 1;
-       } else
-               return NULL;
-
-       q = strchr(p, ' ');
-       if (!q)
-               return NULL;
-       q++;
-
-       strlcpy(sha1_abbrev, p, q - p);
-
-       /*
-        * 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 (get_sha1(sha1_abbrev, commit_sha1) < 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++) {
-               commit = parse_insn_line(p, opts);
-               if (!commit)
-                       return error(_("Could not parse line %d."), i);
-               next = commit_list_append(commit, next);
-               p = strchrnul(p, '\n');
-               if (*p)
-                       p++;
-       }
-       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 rev_info revs;
-       struct commit *commit;
-       struct commit_list **next;
-
-       prepare_revs(&revs, opts);
-
-       next = todo_list;
-       while ((commit = get_revision(&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 (!resolve_ref("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(1);
-       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);
-}
-
-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);
-       }
-}
-
-static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
-{
-       struct commit_list *cur;
-       int res;
-
-       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);
-
-       for (cur = todo_list; cur; cur = cur->next) {
-               save_todo(cur, opts);
-               res = do_pick_commit(cur->item, opts);
-               if (res) {
-                       if (!cur->next)
-                               /*
-                                * An error was encountered while
-                                * picking the last commit; the
-                                * sequencer state is useless now --
-                                * the user simply needs to resolve
-                                * the conflict and commit
-                                */
-                               remove_sequencer_state(0);
-                       return res;
-               }
-       }
-
-       /*
-        * Sequence of picks finished successfully; cleanup by
-        * removing the .git/sequencer directory
-        */
-       remove_sequencer_state(1);
-       return 0;
-}
-
-static int pick_revisions(struct replay_opts *opts)
-{
-       struct commit_list *todo_list = NULL;
-       unsigned char sha1[20];
-
-       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(1);
-               return 0;
-       }
-       if (opts->subcommand == REPLAY_ROLLBACK)
-               return sequencer_rollback(opts);
-       if (opts->subcommand == REPLAY_CONTINUE) {
-               if (!file_exists(git_path(SEQ_TODO_FILE)))
-                       return error(_("No %s in progress"), action_name(opts));
-               read_populate_opts(&opts);
-               read_populate_todo(&todo_list, opts);
-
-               /* Verify that the conflict has been resolved */
-               if (!index_differs_from("HEAD", 0))
-                       todo_list = todo_list->next;
-               return pick_commits(todo_list, 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);
+               usage_with_options(usage_str, options);
 }
 
 int cmd_revert(int argc, const char **argv, const char *prefix)
@@ -1095,10 +201,10 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
        memset(&opts, 0, sizeof(opts));
        if (isatty(0))
                opts.edit = 1;
-       opts.action = REVERT;
+       opts.action = REPLAY_REVERT;
        git_config(git_default_config, NULL);
        parse_args(argc, argv, &opts);
-       res = pick_revisions(&opts);
+       res = sequencer_pick_revisions(&opts);
        if (res < 0)
                die(_("revert failed"));
        return res;
@@ -1110,10 +216,10 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
        int res;
 
        memset(&opts, 0, sizeof(opts));
-       opts.action = CHERRY_PICK;
+       opts.action = REPLAY_PICK;
        git_config(git_default_config, NULL);
        parse_args(argc, argv, &opts);
-       res = pick_revisions(&opts);
+       res = sequencer_pick_revisions(&opts);
        if (res < 0)
                die(_("cherry-pick failed"));
        return res;
index e0b8030f2b31ee337bc2d2e0925788ec6cd92e0f..71f258ef6e620979a9dbeaa87d2501a304b3a0fa 100644 (file)
@@ -263,6 +263,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"))
+               args->quiet = 0;
 
        if (!remote_refs) {
                fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
@@ -301,11 +303,12 @@ int send_pack(struct send_pack_args *args,
                        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",
+                       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" : "");
+                                       use_sideband ? " side-band-64k" : "",
+                                       args->quiet ? " quiet" : "");
                        }
                        else
                                packet_buf_write(&req_buf, "%s %s %s",
@@ -439,6 +442,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 +501,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);
 
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 fafb6dd50081b3af22bdbcde898d170ef47f679d..3911661900f79562133b4ebd6cbb513a06232b5b 100644 (file)
@@ -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 4d3b93fedb5f2eca68edca15ca1e10cb60176d36..f16986c0ae811f6b998d20be39da1af6095fcfe8 100644 (file)
@@ -22,8 +22,6 @@ static size_t cleanup(char *line, size_t len)
  * Remove empty lines from the beginning and end
  * and also trailing spaces from every line.
  *
- * Note that the buffer will not be NUL-terminated.
- *
  * Turn multiple consecutive empty lines between paragraphs
  * into just one empty line.
  *
@@ -77,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 9b6fd95494fd80240f88d49224199b7e621717e4..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);
@@ -422,9 +390,10 @@ 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;
@@ -442,7 +411,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                OPT_CALLBACK('m', "message", &msg, "message",
                             "tag message", parse_msg_arg),
                OPT_FILENAME('F', "file", &msgfile, "read message from file"),
-               OPT_BOOLEAN('s', "sign", &sign, "annotated and GPG-signed tag"),
+               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 08020bc3a258e055a8f5f6974217cf482edc3ee3..b8acf3c18b600f1f413f95744ad281e3879b3f6e 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -31,8 +31,8 @@ static int strbuf_readline_fd(struct strbuf *sb, int fd)
        while (1) {
                char ch;
                ssize_t len = xread(fd, &ch, 1);
-               if (len < 0)
-                       return -1;
+               if (len <= 0)
+                       return len;
                strbuf_addch(sb, ch);
                if (ch == '\n')
                        break;
@@ -320,7 +320,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 f755590da827234830d8b4359720cfbfd87a3dea..28ed6574a2faf3151056aa20e681e06a157e4a2d 100644 (file)
@@ -150,15 +150,18 @@ 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 flags)
 {
        int i, funny;
+       int silent = flags & WRITE_TREE_SILENT;
 
        /* Verify that the tree is merged */
        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;
@@ -239,10 +242,11 @@ static int update_one(struct cache_tree *it,
                      int entries,
                      const char *base,
                      int baselen,
-                     int missing_ok,
-                     int dryrun)
+                     int flags)
 {
        struct strbuf buffer;
+       int missing_ok = flags & WRITE_TREE_MISSING_OK;
+       int dryrun = flags & WRITE_TREE_DRY_RUN;
        int i;
 
        if (0 <= it->entry_count && has_sha1_file(it->sha1))
@@ -286,8 +290,7 @@ static int update_one(struct cache_tree *it,
                                    cache + i, entries - i,
                                    path,
                                    baselen + sublen + 1,
-                                   missing_ok,
-                                   dryrun);
+                                   flags);
                if (subcnt < 0)
                        return subcnt;
                i += subcnt - 1;
@@ -336,8 +339,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');
@@ -369,14 +372,13 @@ static int update_one(struct cache_tree *it,
 int cache_tree_update(struct cache_tree *it,
                      struct cache_entry **cache,
                      int entries,
-                     int missing_ok,
-                     int dryrun)
+                     int flags)
 {
        int i;
-       i = verify_cache(cache, entries);
+       i = verify_cache(cache, entries, flags);
        if (i)
                return i;
-       i = update_one(it, cache, entries, "", 0, missing_ok, dryrun);
+       i = update_one(it, cache, entries, "", 0, flags);
        if (i < 0)
                return i;
        return 0;
@@ -569,11 +571,9 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
 
        was_valid = cache_tree_fully_valid(active_cache_tree);
        if (!was_valid) {
-               int missing_ok = flags & WRITE_TREE_MISSING_OK;
-
                if (cache_tree_update(active_cache_tree,
                                      active_cache, active_nr,
-                                     missing_ok, 0) < 0)
+                                     flags) < 0)
                        return WRITE_TREE_UNMERGED_INDEX;
                if (0 <= newfd) {
                        if (!write_cache(newfd, active_cache, active_nr) &&
@@ -668,3 +668,11 @@ int cache_tree_matches_traversal(struct cache_tree *root,
                return it->entry_count;
        return 0;
 }
+
+int update_main_cache_tree(int flags)
+{
+       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, flags);
+}
index 3df641f59311f43aa951a2cdfa9f110b97b13a45..d8cb2e9e39d2be9c4dad67db44a464c2a1004d2f 100644 (file)
@@ -29,11 +29,15 @@ 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 update_main_cache_tree(int);
 
 /* bitmasks to write_cache_as_tree flags */
 #define WRITE_TREE_MISSING_OK 1
 #define WRITE_TREE_IGNORE_CACHE_TREE 2
+#define WRITE_TREE_DRY_RUN 4
+#define WRITE_TREE_SILENT 8
 
 /* error return codes */
 #define WRITE_TREE_UNREADABLE_INDEX (-1)
diff --git a/cache.h b/cache.h
index 3dfb95d8aaf951763c5b889889c221b446f89a0e..ae0396b84d566108a6b43dfd3ffed3a82f07d2a5 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);
@@ -306,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)
 
@@ -316,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;
@@ -599,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;
@@ -833,7 +834,9 @@ static inline int get_sha1_with_context(const char *str, unsigned char *sha1, st
 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 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.
@@ -865,7 +868,8 @@ extern int read_ref(const char *filename, unsigned char *sha1);
  *
  * errno is sometimes set on errors, but not always.
  */
-extern const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag);
+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);
@@ -874,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);
@@ -1025,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);
 
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 73b7e00292ba2de33fa43b5f028fd807a460af34..4b39c19123c7fa8584a67d5fd91e11f89e5816e4 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;
 
@@ -421,7 +422,8 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
        return ret;
 }
 
-void clear_commit_marks(struct commit *commit, unsigned int mark)
+static void clear_commit_marks_1(struct commit_list **plist,
+                                struct commit *commit, unsigned int mark)
 {
        while (commit) {
                struct commit_list *parents;
@@ -436,12 +438,20 @@ void clear_commit_marks(struct commit *commit, unsigned int mark)
                        return;
 
                while ((parents = parents->next))
-                       clear_commit_marks(parents->item, mark);
+                       commit_list_insert(parents->item, plist);
 
                commit = commit->parents->item;
        }
 }
 
+void clear_commit_marks(struct commit *commit, unsigned int mark)
+{
+       struct commit_list *list = NULL;
+       commit_list_insert(commit, &list);
+       while (list)
+               clear_commit_marks_1(&list, pop_commit(&list), mark);
+}
+
 void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
 {
        struct object *object;
@@ -840,14 +850,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;
@@ -855,6 +1111,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);
 
@@ -868,8 +1127,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;
        }
@@ -881,16 +1142,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 009b113e5bb5d04bdfb116897cc17dc5f5a2fa9c..154c0e34ff7d2dbaddcfb66b74d26697ffba6381 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -181,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 60b5a1d0f8262baca70247923a399fdb4ee3cac1..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
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 efdc703257c9589018e59dc5bc93e9980e0f30b6..a0ac487c0c12ea0e7c81485b5784e28f2115a2ea 100644 (file)
@@ -1712,7 +1712,7 @@ char *getpass(const char *prompt)
        return strbuf_detach(&buf, NULL);
 }
 
-pid_t waitpid(pid_t pid, int *status, unsigned options)
+pid_t waitpid(pid_t pid, int *status, int options)
 {
        HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
            FALSE, pid);
index fecf0d07760d2211fac6db2e19ecbedaac81c57e..0ff1e04812ef2491f15b1d40bb8e1c2977b26d98 100644 (file)
@@ -120,7 +120,7 @@ static inline int mingw_mkdir(const char *path, int mode)
 #define mkdir mingw_mkdir
 
 #define WNOHANG 1
-pid_t waitpid(pid_t pid, int *status, unsigned options);
+pid_t waitpid(pid_t pid, int *status, int options);
 
 #define kill mingw_kill
 int mingw_kill(pid_t pid, int sig);
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 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 */
index b6d789a189713a0259e3f8eb0e29ef657cde66c7..40f9c6d10317ed47f7786e5c328df3ab6f167e7c 100644 (file)
--- a/config.c
+++ b/config.c
@@ -333,7 +333,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;
@@ -356,11 +356,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;
@@ -370,9 +382,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;
        }
@@ -553,7 +575,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;
@@ -564,18 +586,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;
        }
 
@@ -797,6 +818,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;
 }
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..24190de616eae048fbd24839259fc8f160c52d21 100644 (file)
@@ -636,6 +636,12 @@ 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)
+test -n "$LIBC_CONTAINS_LIBINTL" || LIBS="$LIBS -lintl"
+
 ## Checks for header files.
 AC_MSG_NOTICE([CHECKS for header files])
 #
@@ -818,6 +824,29 @@ AC_CHECK_HEADER([paths.h],
 [HAVE_PATHS_H=])
 AC_SUBST(HAVE_PATHS_H)
 #
+# 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)
+#
+# 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 51990fa0cb300a95b125b0727f10133961d0167b..912cddeea8c4f09ec523ce19e677358c84eda9b7 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -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;
-}
index b7c1edf1cc763199d41a490b212a3b65ea581a86..d7367e9faa87482f638ae4e16df38aa1bb853c40 100755 (executable)
@@ -111,7 +111,7 @@ __git_ps1_show_upstream ()
 
        # get some config options from git-config
        local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
-       while read key value; do
+       while read -r key value; do
                case "$key" in
                bash.showupstream)
                        GIT_PS1_SHOWUPSTREAM="$value"
@@ -486,15 +486,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=()
@@ -508,42 +510,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
@@ -551,7 +550,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
@@ -577,7 +576,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
@@ -587,16 +586,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)
@@ -611,30 +621,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*/}"
@@ -661,7 +658,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 ()
@@ -712,15 +710,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
 }
@@ -760,7 +758,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
@@ -785,23 +783,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
@@ -839,7 +837,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 ()
@@ -932,7 +931,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 ()
@@ -1080,7 +1080,7 @@ _git_archive ()
                return
                ;;
        --remote=*)
-               __gitcomp "$(__git_remotes)" "" "${cur##--remote=}"
+               __gitcomp_nl "$(__git_remotes)" "" "${cur##--remote=}"
                return
                ;;
        --*)
@@ -1111,7 +1111,7 @@ _git_bisect ()
 
        case "$subcommand" in
        bad|good|reset|skip|start)
-               __gitcomp "$(__git_refs)"
+               __gitcomp_nl "$(__git_refs)"
                ;;
        *)
                COMPREPLY=()
@@ -1137,14 +1137,14 @@ _git_branch ()
                __gitcomp "
                        --color --no-color --verbose --abbrev= --no-abbrev
                        --track --no-track --contains --merged --no-merged
-                       --set-upstream
+                       --set-upstream --edit-description
                        "
                ;;
        *)
                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
@@ -1191,7 +1191,7 @@ _git_checkout ()
                if [ -n "$(__git_find_on_cmdline "$flags")" ]; then
                        track=''
                fi
-               __gitcomp "$(__git_refs '' $track)"
+               __gitcomp_nl "$(__git_refs '' $track)"
                ;;
        esac
 }
@@ -1208,7 +1208,7 @@ _git_cherry_pick ()
                __gitcomp "--edit --no-commit"
                ;;
        *)
-               __gitcomp "$(__git_refs)"
+               __gitcomp_nl "$(__git_refs)"
                ;;
        esac
 }
@@ -1262,7 +1262,7 @@ _git_commit ()
                ;;
        --reuse-message=*|--reedit-message=*|\
        --fixup=*|--squash=*)
-               __gitcomp "$(__git_refs)" "" "${cur#*=}"
+               __gitcomp_nl "$(__git_refs)" "" "${cur#*=}"
                return
                ;;
        --untracked-files=*)
@@ -1293,7 +1293,7 @@ _git_describe ()
                        "
                return
        esac
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 __git_diff_common_options="--stat --numstat --shortstat --summary
@@ -1459,13 +1459,13 @@ _git_grep ()
        case "$cword,$prev" in
        2,*|*,-*)
                if test -r tags; then
-                       __gitcomp "$(__git_match_ctag "$cur" tags)"
+                       __gitcomp_nl "$(__git_match_ctag "$cur" tags)"
                        return
                fi
                ;;
        esac
 
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_help ()
@@ -1523,7 +1523,7 @@ _git_ls_files ()
 
 _git_ls_remote ()
 {
-       __gitcomp "$(__git_remotes)"
+       __gitcomp_nl "$(__git_remotes)"
 }
 
 _git_ls_tree ()
@@ -1607,7 +1607,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 ()
@@ -1619,7 +1619,7 @@ _git_merge ()
                __gitcomp "$__git_merge_options"
                return
        esac
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_mergetool ()
@@ -1639,7 +1639,7 @@ _git_mergetool ()
 
 _git_merge_base ()
 {
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_mv ()
@@ -1670,7 +1670,7 @@ _git_notes ()
        ,*)
                case "${words[cword-1]}" in
                --ref)
-                       __gitcomp "$(__git_refs)"
+                       __gitcomp_nl "$(__git_refs)"
                        ;;
                *)
                        __gitcomp "$subcommands --ref"
@@ -1679,7 +1679,7 @@ _git_notes ()
                ;;
        add,--reuse-message=*|append,--reuse-message=*|\
        add,--reedit-message=*|append,--reedit-message=*)
-               __gitcomp "$(__git_refs)" "" "${cur#*=}"
+               __gitcomp_nl "$(__git_refs)" "" "${cur#*=}"
                ;;
        add,--*|append,--*)
                __gitcomp '--file= --message= --reedit-message=
@@ -1698,7 +1698,7 @@ _git_notes ()
                -m|-F)
                        ;;
                *)
-                       __gitcomp "$(__git_refs)"
+                       __gitcomp_nl "$(__git_refs)"
                        ;;
                esac
                ;;
@@ -1726,12 +1726,12 @@ _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
                ;;
        --*)
@@ -1769,7 +1769,7 @@ _git_rebase ()
 
                return
        esac
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_reflog ()
@@ -1780,7 +1780,7 @@ _git_reflog ()
        if [ -z "$subcommand" ]; then
                __gitcomp "$subcommands"
        else
-               __gitcomp "$(__git_refs)"
+               __gitcomp_nl "$(__git_refs)"
        fi
 }
 
@@ -1848,7 +1848,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
                *.*=*)
@@ -1862,23 +1862,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
@@ -1925,7 +1929,7 @@ _git_config ()
                return
                ;;
        --get|--get-all|--unset|--unset-all)
-               __gitcomp "$(__git_config_get_set_variables)"
+               __gitcomp_nl "$(__git_config_get_set_variables)"
                return
                ;;
        *.*)
@@ -1951,7 +1955,7 @@ _git_config ()
                ;;
        branch.*)
                local pfx="${cur%.*}." cur_="${cur#*.}"
-               __gitcomp "$(__git_heads)" "$pfx" "$cur_" "."
+               __gitcomp_nl "$(__git_heads)" "$pfx" "$cur_" "."
                return
                ;;
        guitool.*.*)
@@ -1980,7 +1984,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.*.*)
@@ -1993,7 +1997,7 @@ _git_config ()
                ;;
        remote.*)
                local pfx="${cur%.*}." cur_="${cur#*.}"
-               __gitcomp "$(__git_remotes)" "$pfx" "$cur_" "."
+               __gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
                return
                ;;
        url.*.*)
@@ -2294,7 +2298,7 @@ _git_remote ()
 
        case "$subcommand" in
        rename|rm|show|prune)
-               __gitcomp "$(__git_remotes)"
+               __gitcomp_nl "$(__git_remotes)"
                ;;
        update)
                local i c='' IFS=$'\n'
@@ -2312,7 +2316,7 @@ _git_remote ()
 
 _git_replace ()
 {
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_reset ()
@@ -2325,7 +2329,7 @@ _git_reset ()
                return
                ;;
        esac
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_revert ()
@@ -2336,7 +2340,7 @@ _git_revert ()
                return
                ;;
        esac
-       __gitcomp "$(__git_refs)"
+       __gitcomp_nl "$(__git_refs)"
 }
 
 _git_rm ()
@@ -2435,7 +2439,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')"
                        ;;
                *)
@@ -2569,7 +2573,7 @@ _git_tag ()
                i="${words[c]}"
                case "$i" in
                -d|-v)
-                       __gitcomp "$(__git_tags)"
+                       __gitcomp_nl "$(__git_tags)"
                        return
                        ;;
                -f)
@@ -2585,13 +2589,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
 }
@@ -2612,6 +2616,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
@@ -2668,6 +2676,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
@@ -2706,33 +2718,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;
+}
index b975d67fca530a35c590cb677a6f11eabb7900bb..a78d9c54931c7fc1f6a693a7f185f08bcb8f6861 100755 (executable)
@@ -53,9 +53,10 @@ def p4_build_cmd(cmd):
 
 def chdir(dir):
     # P4 uses the PWD environment variable rather than getcwd(). Since we're
-    # not using the shell, we have to set it ourselves.
-    os.environ['PWD']=dir
+    # 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:
@@ -361,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 = ""
 
@@ -557,6 +563,26 @@ class Command:
 class P4UserMap:
     def __init__(self):
         self.userMapFromPerforceServer = False
+        self.myP4UserId = None
+
+    def p4UserId(self):
+        if self.myP4UserId:
+            return self.myP4UserId
+
+        results = p4CmdList("user -o")
+        for r in results:
+            if r.has_key('User'):
+                self.myP4UserId = r['User']
+                return r['User']
+        die("Could not find your p4 user id")
+
+    def p4UserIsMe(self, p4User):
+        # return True if the given p4 user is actually me
+        me = self.p4UserId()
+        if not p4User or p4User != me:
+            return False
+        else:
+            return True
 
     def getUserCacheFilename(self):
         home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
@@ -694,7 +720,6 @@ class P4Submit(Command, P4UserMap):
         self.verbose = False
         self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
         self.isWindows = (platform.system() == "Windows")
-        self.myP4UserId = None
 
     def check(self):
         if len(p4CmdList("opened ...")) > 0:
@@ -793,7 +818,7 @@ class P4Submit(Command, P4UserMap):
     def canChangeChangelists(self):
         # check to see if we have p4 admin or super-user permissions, either of
         # which are required to modify changelists.
-        results = p4CmdList("protects %s" % self.depotPath)
+        results = p4CmdList(["protects", self.depotPath])
         for r in results:
             if r.has_key('perm'):
                 if r['perm'] == 'admin':
@@ -802,25 +827,6 @@ class P4Submit(Command, P4UserMap):
                     return 1
         return 0
 
-    def p4UserId(self):
-        if self.myP4UserId:
-            return self.myP4UserId
-
-        results = p4CmdList("user -o")
-        for r in results:
-            if r.has_key('User'):
-                self.myP4UserId = r['User']
-                return r['User']
-        die("Could not find your p4 user id")
-
-    def p4UserIsMe(self, p4User):
-        # return True if the given p4 user is actually me
-        me = self.p4UserId()
-        if not p4User or p4User != me:
-            return False
-        else:
-            return True
-
     def prepareSubmitTemplate(self):
         # remove lines in the Files section that show changes to files outside the depot path we're committing into
         template = ""
@@ -847,6 +853,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))
 
@@ -1001,7 +1042,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")
@@ -1009,25 +1050,9 @@ 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()
@@ -1039,11 +1064,13 @@ class P4Submit(Command, P4UserMap):
                 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_revert(f)
                 for f in filesToAdd:
@@ -1067,6 +1094,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
 
@@ -1099,6 +1128,10 @@ class P4Submit(Command, P4UserMap):
         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_sync("...")
@@ -1136,6 +1169,219 @@ 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 triple_dot_index != len(self.path) - 3:
+                    die("Can handle only single ... wildcard, at end: %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.path, self.client_side.path)
+
+        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 (i.e.
+                # one later in the list, which we are walking backwards).
+                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" )
 
@@ -1183,7 +1429,9 @@ class P4Sync(Command, P4UserMap):
         self.p4BranchesInGit = []
         self.cloneExclude = []
         self.useClientSpec = False
-        self.clientSpecDirs = []
+        self.clientSpecDirs = None
+        self.tempBranches = []
+        self.tempBranchLocation = "git-p4-tmp"
 
         if gitConfig("git-p4.syncFromOrigin") == "false":
             self.syncWithOrigin = False
@@ -1205,6 +1453,14 @@ class P4Sync(Command, P4UserMap):
                    .replace("%25", "%")
         return path
 
+    # Force a checkpoint in fast-import and wait for it to finish
+    def checkpoint(self):
+        self.gitStream.write("checkpoint\n\n")
+        self.gitStream.write("progress checkpoint\n\n")
+        out = self.gitOutput.readline()
+        if self.verbose:
+            print "checkpoint finished: " + out
+
     def extractFilesFromCommit(self, commit):
         self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
                              for path in self.cloneExclude]
@@ -1234,20 +1490,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])]
@@ -1397,19 +1640,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:
@@ -1434,6 +1675,12 @@ class P4Sync(Command, P4UserMap):
             if self.stream_file.has_key('depotFile'):
                 self.streamOneP4File(self.stream_file, self.stream_contents)
 
+    def make_email(self, userid):
+        if userid in self.users:
+            return self.users[userid]
+        else:
+            return "%s <a@b>" % userid
+
     def commit(self, details, files, branch, branchPrefixes, parent = ""):
         epoch = details["time"]
         author = details["user"]
@@ -1457,10 +1704,7 @@ class P4Sync(Command, P4UserMap):
         committer = ""
         if author not in self.users:
             self.getUserMapFromPerforceServer()
-        if author in self.users:
-            committer = "%s %s %s" % (self.users[author], epoch, self.tz)
-        else:
-            committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
+        committer = "%s %s %s" % (self.make_email(author), epoch, self.tz)
 
         self.gitStream.write("committer %s\n" % committer)
 
@@ -1505,15 +1749,21 @@ class P4Sync(Command, P4UserMap):
                     self.gitStream.write("from %s\n" % branch)
 
                     owner = labelDetails["Owner"]
-                    tagger = ""
-                    if author in self.users:
-                        tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
+
+                    # Try to use the owner of the p4 label, or failing that,
+                    # the current p4 user id.
+                    if owner:
+                        email = self.make_email(owner)
                     else:
-                        tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
+                        email = self.make_email(self.p4UserId())
+                    tagger = "%s %s %s" % (email, epoch, self.tz)
+
                     self.gitStream.write("tagger %s\n" % tagger)
-                    self.gitStream.write("data <<EOT\n")
-                    self.gitStream.write(labelDetails["Description"])
-                    self.gitStream.write("EOT\n\n")
+
+                    description = labelDetails["Description"]
+                    self.gitStream.write("data %d\n" % len(description))
+                    self.gitStream.write(description)
+                    self.gitStream.write("\n")
 
                 else:
                     if not self.silent:
@@ -1528,7 +1778,7 @@ class P4Sync(Command, P4UserMap):
     def getLabels(self):
         self.labels = {}
 
-        l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
+        l = p4CmdList(["labels"] + ["%s..." % p for p in self.depotPaths])
         if len(l) > 0 and not self.silent:
             print "Finding files belonging to labels in %s" % `self.depotPaths`
 
@@ -1570,7 +1820,7 @@ class P4Sync(Command, P4UserMap):
             command = "branches"
 
         for info in p4CmdList(command):
-            details = p4Cmd("branch -o %s" % info["branch"])
+            details = p4Cmd(["branch", "-o", info["branch"]])
             viewIdx = 0
             while details.has_key("View%s" % viewIdx):
                 paths = details["View%s" % viewIdx].split(" ")
@@ -1708,7 +1958,7 @@ class P4Sync(Command, P4UserMap):
         sourceRef = self.gitRefForBranch(sourceBranch)
         #print "source " + sourceBranch
 
-        branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
+        branchParentChange = int(p4Cmd(["changes", "-m", "1", "%s...@1,%s" % (sourceDepotPath, firstChange)])["change"])
         #print "branch parent: %s" % branchParentChange
         gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
         if len(gitParent) > 0:
@@ -1718,10 +1968,24 @@ class P4Sync(Command, P4UserMap):
         self.importChanges(changes)
         return True
 
+    def searchParent(self, parent, branch, target):
+        parentFound = False
+        for blob in read_pipe_lines(["git", "rev-list", "--reverse", "--no-merges", parent]):
+            blob = blob.strip()
+            if len(read_pipe(["git", "diff-tree", blob, target])) == 0:
+                parentFound = True
+                if self.verbose:
+                    print "Found parent of %s in commit %s" % (branch, blob)
+                break
+        if parentFound:
+            return blob
+        else:
+            return None
+
     def importChanges(self, changes):
         cnt = 1
         for change in changes:
-            description = p4Cmd("describe %s" % change)
+            description = p4Cmd(["describe", str(change)])
             self.updateOptionDict(description)
 
             if not self.silent:
@@ -1774,7 +2038,21 @@ class P4Sync(Command, P4UserMap):
                             parent = self.initialParents[branch]
                             del self.initialParents[branch]
 
-                        self.commit(description, filesForCommit, branch, [branchPrefix], parent)
+                        blob = None
+                        if len(parent) > 0:
+                            tempBranch = os.path.join(self.tempBranchLocation, "%d" % (change))
+                            if self.verbose:
+                                print "Creating temporary branch: " + tempBranch
+                            self.commit(description, filesForCommit, tempBranch, [branchPrefix])
+                            self.tempBranches.append(tempBranch)
+                            self.checkpoint()
+                            blob = self.searchParent(parent, branch, tempBranch)
+                        if blob:
+                            self.commit(description, filesForCommit, branch, [branchPrefix], blob)
+                        else:
+                            if self.verbose:
+                                print "Parent of %s not found. Committing into head of %s" % (branch, parent)
+                            self.commit(description, filesForCommit, branch, [branchPrefix], parent)
                 else:
                     files = self.extractFilesFromCommit(description)
                     self.commit(description, files, self.branch, self.depotPaths,
@@ -1848,50 +2126,31 @@ class P4Sync(Command, P4UserMap):
 
 
     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)
+        specList = p4CmdList("client -o")
+        if len(specList) != 1:
+            die('Output from "client -o" is %d lines, expecting 1' %
+                len(specList))
 
-                    temp[v] = (include, cv)
+        # dictionary of all client parameters
+        entry = specList[0]
 
-        self.clientSpecDirs = temp.items()
-        self.clientSpecDirs.sort( lambda x, y: abs( y[1][0] ) - abs( x[1][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])
+
+        self.clientSpecDirs = view
+        if self.verbose:
+            for i, m in enumerate(self.clientSpecDirs.mappings):
+                    print "clientSpecDirs %d: %s" % (i, str(m))
 
     def run(self, args):
         self.depotPaths = []
@@ -1925,7 +2184,10 @@ 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":
+        if not self.useClientSpec:
+            if gitConfig("git-p4.useclientspec", "--bool") == "true":
+                self.useClientSpec = True
+        if self.useClientSpec:
             self.getClientSpec()
 
         # TODO: should always look at previous commits,
@@ -1998,6 +2260,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:
@@ -2014,7 +2287,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("/"):
@@ -2111,6 +2387,12 @@ class P4Sync(Command, P4UserMap):
         self.gitOutput.close()
         self.gitError.close()
 
+        # Cleanup temporary branches created during import
+        if self.tempBranches != []:
+            for branch in self.tempBranches:
+                read_pipe("git update-ref -d %s" % branch)
+            os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
+
         return True
 
 class P4Rebase(Command):
@@ -2309,7 +2591,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,
@@ -2339,6 +2622,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
-
index 72ffea0b3a6a29d69cb8c88989adc0692cd01f34..2dd27ceb0e41c81faa12d44468b46316fa07b32f 100644 (file)
@@ -8,7 +8,10 @@ svn-fe - convert an SVN "dumpfile" to a fast-import stream
 SYNOPSIS
 --------
 [verse]
-svnadmin dump --incremental REPO | svn-fe [url] | git fast-import
+mkfifo backchannel &&
+svnadmin dump --deltas REPO |
+       svn-fe [url] 3<backchannel |
+       git fast-import --cat-blob-fd=3 3>backchannel
 
 DESCRIPTION
 -----------
@@ -29,9 +32,6 @@ Subversion's repository dump format is documented in full in
 Files in this format can be generated using the 'svnadmin dump' or
 'svk admin dump' command.
 
-Dumps produced with 'svnadmin dump --deltas' (dumpfile format v3)
-are not supported.
-
 OUTPUT FORMAT
 -------------
 The fast-import format is documented by the git-fast-import(1)
index 86e9c29ec05139844ff46868e2b52dfbc274cc39..12868ed7bda11648704ffe4e5d3415067764a6e2 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -876,43 +876,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 +1260,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;
 }
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 fa283003ea8809a2defc63f3b36cebe135028ddb..ab21e66b2fb69015c3085761b344b11053c67f4a 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -1086,6 +1086,8 @@ static int serve(struct string_list *listen_addr, int listen_port,
 
        drop_privileges(cred);
 
+       loginfo("Ready to rumble");
+
        return service_loop(&socklist);
 }
 
@@ -1099,6 +1101,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++) {
@@ -1268,10 +1272,8 @@ int main(int argc, char **argv)
        if (inetd_mode || serve_mode)
                return execute();
 
-       if (detach) {
+       if (detach)
                daemonize();
-               loginfo("Ready to rumble");
-       }
        else
                sanitize_stdfds();
 
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;
index 62f4cd94cfbc4d3fe9e46c84f318af6624349a48..fc0dff31b58c8bd6668de5c6396a93b31cc5729d 100644 (file)
@@ -469,6 +469,8 @@ static int diff_cache(struct rev_info *revs,
        opts.src_index = &the_index;
        opts.dst_index = NULL;
        opts.pathspec = &revs->diffopt.pathspec;
+       opts.pathspec->recursive = 1;
+       opts.pathspec->max_depth = -1;
 
        init_tree_desc(&t, tree->buffer, tree->size);
        return unpack_trees(1, &t, &opts);
diff --git a/diff.c b/diff.c
index 374ecf3b486e218c52f94ba14481a715c19b80db..3550c18e390cac7a3cce8fcf93074c7178bf1994 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -177,11 +177,8 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       switch (userdiff_config(var, value)) {
-               case 0: break;
-               case -1: return -1;
-               default: return 0;
-       }
+       if (userdiff_config(var, value) < 0)
+               return -1;
 
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
@@ -1113,6 +1110,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) {
@@ -1313,6 +1319,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;
@@ -1466,9 +1521,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 +1551,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)
diff --git a/diff.h b/diff.h
index 0c51724493f76461b67d6ef8f5556852819ad809..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,
@@ -195,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);
 
@@ -322,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 */
diff --git a/dir.c b/dir.c
index 6c0d7825799f6c35a2c1f1830767ab6203e2da92..0a78d00b545ac4f302ea89b6393773669907599e 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -968,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;
 }
index 0bee6a7a88299f8c89eedeee25cece1d3cdafef0..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;
index 8d8ea3c45c0be5481c7b452c27ee0d163d69fb00..6cd19e580ba710d03682fcaff997fa7e0e555691 100644 (file)
@@ -855,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);
@@ -1143,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)
@@ -1166,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. */
@@ -1175,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)
@@ -1245,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;
@@ -2173,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 */
@@ -2379,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;
@@ -2390,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;
@@ -2450,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--;
 
@@ -2637,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 "))
@@ -3292,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 9042432e23d7e43edafce28b6822a041028b57b7..64d8e2a64ddd2e6162e7a8fd93ab4294de3f161e 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -15,6 +15,7 @@ q,quiet         be quiet
 s,signoff       add a Signed-off-by line to the commit message
 u,utf8          recode into utf8 (default)
 k,keep          pass -k flag to git-mailinfo
+keep-non-patch  pass -b flag to git-mailinfo
 keep-cr         pass --keep-cr flag to git-mailsplit for mbox format
 no-keep-cr      do not pass --keep-cr flag to git-mailsplit independent of am.keepcr
 c,scissors      strip everything before a scissors line
@@ -387,6 +388,8 @@ do
                utf8= ;;
        -k|--keep)
                keep=t ;;
+       --keep-non-patch)
+               keep=b ;;
        -c|--scissors)
                scissors=t ;;
        --no-scissors)
@@ -530,7 +533,6 @@ else
        echo "$sign" >"$dotest/sign"
        echo "$utf8" >"$dotest/utf8"
        echo "$keep" >"$dotest/keep"
-       echo "$keepcr" >"$dotest/keepcr"
        echo "$scissors" >"$dotest/scissors"
        echo "$no_inbody_headers" >"$dotest/no_inbody_headers"
        echo "$GIT_QUIET" >"$dotest/quiet"
@@ -566,21 +568,24 @@ case "$resolved" in
        fi
 esac
 
+# Now, decide what command line options we will give to the git
+# commands we invoke, based on the result of parsing command line
+# options and previous invocation state stored in $dotest/ files.
+
 if test "$(cat "$dotest/utf8")" = t
 then
        utf8=-u
 else
        utf8=-n
 fi
-if test "$(cat "$dotest/keep")" = t
-then
-       keep=-k
-fi
-case "$(cat "$dotest/keepcr")" in
+keep=$(cat "$dotest/keep")
+case "$keep" in
 t)
-       keepcr=--keep-cr ;;
-f)
-       keepcr=--no-keep-cr ;;
+       keep=-k ;;
+b)
+       keep=-b ;;
+*)
+       keep= ;;
 esac
 case "$(cat "$dotest/scissors")" in
 t)
index 8b4dd5c022a160de5c68d83b6237799c23e6ae0a..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
@@ -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 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 f8971603f77a495b253b5470f5546d51dd6efd14..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) {}
@@ -859,6 +896,7 @@ set font_descs {
        {fontui   font_ui   {mc "Main Font"}}
        {fontdiff font_diff {mc "Diff/Console Font"}}
 }
+set default_config(gui.stageuntracked) ask
 
 ######################################################################
 ##
@@ -1060,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) {}
                        }
                }
        }
@@ -1075,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
@@ -2474,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
        }
 }
@@ -3362,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
 
@@ -3878,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 691941e95948e7d332d6984aa6e2cc0956147550..324f7744c495dc2eb9a1d4acde09352d7948efe8 100644 (file)
@@ -219,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 \
@@ -229,12 +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 [expr {[llength $w_columns] - 1}] \
+               -column 0 \
+               -columnspan [expr {[llength $w_columns] + 1}] \
                ]
 
        set w_cviewer $w.file_pane.cm.t
@@ -473,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]
                }
@@ -572,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.
@@ -1062,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 {}
 
@@ -1170,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  \
@@ -1201,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
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 54c7957a66aa5784d47708edc05a1c95178ed7a2..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
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 372bed9948390483d66036231fce2fe8964d7bb6..0d81432af53ba873d89421fa4767243818ab1f0a 100644 (file)
@@ -263,7 +263,9 @@ proc commit_commitmsg {curHEAD msg_p} {
        global is_detached repo_config
        global pch_error
 
-       if {$is_detached && $repo_config(gui.warndetachedcommit)} {
+       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\
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 e38b647b71ea335d6771121cfd079de919ced55b..8efbbdde21123dd65412cd5fe6dbb506966b0af9 100644 (file)
@@ -356,21 +356,33 @@ proc do_add_all {} {
        global file_states
 
        set paths [list]
-       set unknown_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 unknown_paths $path}
+               ?O {lappend untracked_paths $path}
                }
        }
-       if {[llength $unknown_paths]} {
-               set reply [ask_popup [mc "There are unknown files do you also want
-to stage those?"]]
+       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 $unknown_paths]
+                       set paths [concat $paths $untracked_paths]
                }
        }
        add_helper {Adding all changed files} $paths
index c160012de6e4d19c02810d57f0b3268c1037523c..a026de954c3d9cbfd03d4dec9a73a74647bf74ba 100644 (file)
@@ -15,7 +15,7 @@ constructor new {i_w i_text args} {
 
        ${NS}::frame  $w
        ${NS}::label  $w.l       -text [mc "Goto Line:"]
-       entry  $w.ent \
+       tentry  $w.ent \
                -textvariable ${__this}::linenum \
                -background lightgreen \
                -validate key \
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 ef3486f083c74f7f5e5fe9af861818ed0d64e89c..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
@@ -37,6 +66,8 @@ constructor new {i_w i_text args} {
        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
@@ -45,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
 }
@@ -53,6 +88,7 @@ method hide {} {
        if {[visible $this]} {
                focus $ctext
                grid remove $w
+               _save_search $this
        }
 }
 
@@ -98,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
        }
@@ -105,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
 }
 
@@ -126,17 +167,74 @@ 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 {} {
@@ -149,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"
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 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 9868a0bfb478707b361f664a252870b3d1939138..434c139f077ed5b03fef2b70f2eabb66f7147f23 100755 (executable)
@@ -40,10 +40,14 @@ test -f "$GIT_DIR/MERGE_HEAD" && die_merge
 
 strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
 log_arg= verbosity= progress= recurse_submodules=
-merge_args=
+merge_args= edit=
 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
@@ -66,6 +70,10 @@ do
                no_commit=--no-commit ;;
        --c|--co|--com|--comm|--commi|--commit)
                no_commit=--commit ;;
+       -e|--edit)
+               edit=--edit ;;
+       --no-edit)
+               edit=--no-edit ;;
        --sq|--squ|--squa|--squas|--squash)
                squash=--squash ;;
        --no-sq|--no-squ|--no-squa|--no-squas|--no-squash)
@@ -274,7 +282,7 @@ true)
        eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
        ;;
 *)
-       eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only"
+       eval="git-merge $diffstat $no_commit $edit $squash $no_ff $ff_only"
        eval="$eval  $log_arg $strategy_args $merge_args $verbosity $progress"
        eval="$eval \"\$merge_name\" HEAD $merge_head"
        ;;
index 804001bb4e29873ef08d950a40cf1f1745e2aa27..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"
@@ -408,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)
@@ -417,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
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 d491db92c9379cf0eea63182dd704feb6582b6e7..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 = (
@@ -227,7 +228,6 @@ my %config_settings = (
     "bcc" => \@bcclist,
     "suppresscc" => \@suppress_cc,
     "envelopesender" => \$envelope_sender,
-    "multiedit" => \$multiedit,
     "confirm"   => \$confirm,
     "from" => \$sender,
     "assume8bitencoding" => \$auto_8bit_encoding,
index e672366f0c3db3b547233af146cd1bba275c0042..d5fae993b0c093bdd07079101df303c2c74deae3 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.
 
-if test -z "$GIT_GETTEXT_POISON"
+# Export the TEXTDOMAIN* data that we need for Git
+TEXTDOMAIN=git
+export TEXTDOMAIN
+if test -z "$GIT_TEXTDOMAINDIR"
 then
-       gettext () {
-               printf "%s" "$1"
-       }
+       TEXTDOMAINDIR="@@LOCALEDIR@@"
+else
+       TEXTDOMAINDIR="$GIT_TEXTDOMAINDIR"
+fi
+export TEXTDOMAINDIR
 
-       gettextln() {
-               printf "%s\n" "$1"
-       }
+# First decide what scheme to use...
+GIT_INTERNAL_GETTEXT_SH_SCHEME=fallthrough
+if test -n "@@USE_GETTEXT_SCHEME@@"
+then
+       GIT_INTERNAL_GETTEXT_SH_SCHEME="@@USE_GETTEXT_SCHEME@@"
+elif test -n "@@USE_FALLTHROUGH_GETTEXT_SCHEME@@$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
+then
+       : no probing necessary
+elif test -n "$GIT_GETTEXT_POISON"
+then
+       GIT_INTERNAL_GETTEXT_SH_SCHEME=poison
+elif type gettext.sh >/dev/null 2>&1
+then
+       # GNU libintl's gettext.sh
+       GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu
+elif test "$(gettext -h 2>&1)" = "-h"
+then
+       # gettext binary exists but no gettext.sh. likely to be a gettext
+       # binary on a Solaris or something that is not GNU libintl and
+       # lack eval_gettext.
+       GIT_INTERNAL_GETTEXT_SH_SCHEME=gettext_without_eval_gettext
+fi
+export GIT_INTERNAL_GETTEXT_SH_SCHEME
 
+# ... and then follow that decision.
+case "$GIT_INTERNAL_GETTEXT_SH_SCHEME" in
+gnu)
+       # Use libintl's gettext.sh, or fall back to English if we can't.
+       . gettext.sh
+       ;;
+gettext_without_eval_gettext)
+       # Solaris has a gettext(1) but no eval_gettext(1)
        eval_gettext () {
-               printf "%s" "$1" | (
+               gettext "$1" | (
                        export PATH $(git sh-i18n--envsubst --variables "$1");
                        git sh-i18n--envsubst "$1"
                )
        }
-
-       eval_gettextln () {
-               printf "%s\n" "$1" | (
-                       export PATH $(git sh-i18n--envsubst --variables "$1");
-                       git sh-i18n--envsubst "$1"
-               )
-       }
-else
+       ;;
+poison)
+       # Emit garbage so that tests that incorrectly rely on translatable
+       # strings will fail.
        gettext () {
                printf "%s" "# GETTEXT POISON #"
        }
 
-       gettextln () {
-               printf "%s\n" "# GETTEXT POISON #"
-       }
-
        eval_gettext () {
                printf "%s" "# GETTEXT POISON #"
        }
+       ;;
+*)
+       gettext () {
+               printf "%s" "$1"
+       }
 
-       eval_gettextln () {
-               printf "%s\n" "# GETTEXT POISON #"
+       eval_gettext () {
+               printf "%s" "$1" | (
+                       export PATH $(git sh-i18n--envsubst --variables "$1");
+                       git sh-i18n--envsubst "$1"
+               )
        }
-fi
+       ;;
+esac
+
+# Git-specific wrapper functions
+gettextln () {
+       gettext "$1"
+       echo
+}
 
+eval_gettextln () {
+       eval_gettext "$1"
+       echo
+}
index 1fba6c2de0b78ddb5aa1495c9c519c26b48eebc2..5d8e4e6c89f0471567a7aa1f07b054d8ac502e15 100644 (file)
@@ -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 c76669284ce48411b0cd580c5943e3eb0a785884..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")"
 
@@ -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 3adab9363563e80e4ceb2c7fb5c3ab91ac1ef505..9bb2e13e929c824a75c648966aaaa61672b9c445 100755 (executable)
@@ -131,6 +131,7 @@ module_clone()
        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)
index e30df22d89d97642f0b99c3a35ee0b2c5c062bb3..eeb83d375931df81bd87ca285c8d0741c14f33a2 100755 (executable)
@@ -5389,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;
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 c562c622847b9559fd40ae867f0d3cb229fa784b..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
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 4f0c3bd90c7f90dad1674f50999da534c33c0261..3fc7380a5ea510b705eb49542c3e698b0dddb02c 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$,, &&
@@ -759,6 +759,8 @@ our @cgi_param_mapping = (
        extra_options => "opt",
        search_use_regexp => "sr",
        ctag => "by_tag",
+       diff_style => "ds",
+       project_filter => "pf",
        # this must be last entry (for manipulation from JavaScript)
        javascript => "js"
 );
@@ -815,9 +817,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));
                }
        }
 }
@@ -975,7 +977,7 @@ sub evaluate_path_info {
 
 our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_base,
      $hash_parent_base, @extra_options, $page, $searchtype, $search_use_regexp,
-     $searchtext, $search_regexp);
+     $searchtext, $search_regexp, $project_filter);
 sub evaluate_and_validate_params {
        our $action = $input_params{'action'};
        if (defined $action) {
@@ -993,6 +995,13 @@ sub evaluate_and_validate_params {
                }
        }
 
+       our $project_filter = $input_params{'project_filter'};
+       if (defined $project_filter) {
+               if (!validate_pathname($project_filter)) {
+                       die_error(404, "Invalid project_filter parameter");
+               }
+       }
+
        our $file_name = $input_params{'file_name'};
        if (defined $file_name) {
                if (!validate_pathname($file_name)) {
@@ -1122,8 +1131,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 {
@@ -1442,8 +1453,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);
@@ -1695,6 +1706,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 {
@@ -2225,93 +2237,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 .= " ";
+       }
+       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 "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
+       return $diff_class, esc_html($line, -nbsp=>1);
 }
 
 # Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
@@ -2363,7 +2401,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
@@ -2737,7 +2775,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) {
@@ -2799,19 +2837,18 @@ sub git_get_project_url_list {
 
 sub git_get_projects_list {
        my $filter = shift || '';
+       my $paranoid = shift;
        my @list;
 
-       $filter =~ s/\.git$//;
-
        if (-d $projects_list) {
                # search in directory
                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) {
+               if ($filter && !$paranoid) {
                        $dir .= "/$filter";
                        $dir =~ s!/+$!!;
                }
@@ -2836,6 +2873,10 @@ sub git_get_projects_list {
                                }
 
                                my $path = substr($File::Find::name, $pfxlen + 1);
+                               # paranoidly only filter here
+                               if ($paranoid && $filter && $path !~ m!^\Q$filter\E/!) {
+                                       next;
+                               }
                                # we check related file in $projectroot
                                if (check_export_ok("$projectroot/$path")) {
                                        push @list, { path => $path };
@@ -3701,7 +3742,12 @@ sub run_highlighter {
 sub get_page_title {
        my $title = to_utf8($site_name);
 
-       return $title unless (defined $project);
+       unless (defined $project) {
+               if (defined $project_filter) {
+                       $title .= " - projects in '" . esc_path($project_filter) . "'";
+               }
+               return $title;
+       }
        $title .= " - " . to_utf8($project);
 
        return $title unless (defined $action);
@@ -3795,12 +3841,27 @@ sub print_header_links {
        }
 }
 
+sub print_nav_breadcrumbs_path {
+       my $dirprefix = undef;
+       while (my $part = shift) {
+               $dirprefix .= "/" if defined $dirprefix;
+               $dirprefix .= $part;
+               print $cgi->a({-href => href(project => undef,
+                                            project_filter => $dirprefix,
+                                            action => "project_list")},
+                             esc_html($part)) . " / ";
+       }
+}
+
 sub print_nav_breadcrumbs {
        my %opts = @_;
 
        print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
        if (defined $project) {
-               print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
+               my @dirname = split '/', $project;
+               my $projectbasename = pop @dirname;
+               print_nav_breadcrumbs_path(@dirname);
+               print $cgi->a({-href => href(action=>"summary")}, esc_html($projectbasename));
                if (defined $action) {
                        my $action_print = $action ;
                        if (defined $opts{-action_extra}) {
@@ -3813,6 +3874,8 @@ sub print_nav_breadcrumbs {
                        print " / $opts{-action_extra}";
                }
                print "\n";
+       } elsif (defined $project_filter) {
+               print_nav_breadcrumbs_path(split '/', $project_filter);
        }
 }
 
@@ -3843,7 +3906,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) .
@@ -3935,9 +3998,11 @@ sub git_footer_html {
                }
 
        } else {
-               print $cgi->a({-href => href(project=>undef, action=>"opml"),
+               print $cgi->a({-href => href(project=>undef, action=>"opml",
+                                            project_filter => $project_filter),
                              -class => $feed_class}, "OPML") . " ";
-               print $cgi->a({-href => href(project=>undef, action=>"project_index"),
+               print $cgi->a({-href => href(project=>undef, action=>"project_index",
+                                            project_filter => $project_filter),
                              -class => $feed_class}, "TXT") . "\n";
        }
        print "</div>\n"; # class="page_footer"
@@ -4833,8 +4898,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);
@@ -4844,6 +4998,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";
 
@@ -4950,10 +5105,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"
        }
 
@@ -4986,6 +5160,34 @@ sub git_patchset_body {
 
 # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
+sub git_project_search_form {
+       my ($searchtext, $search_use_regexp);
+
+       my $limit = '';
+       if ($project_filter) {
+               $limit = " in '$project_filter/'";
+       }
+
+       print "<div class=\"projsearch\">\n";
+       print $cgi->startform(-method => 'get', -action => $my_uri) .
+             $cgi->hidden(-name => 'a', -value => 'project_list')  . "\n";
+       print $cgi->hidden(-name => 'pf', -value => $project_filter). "\n"
+               if (defined $project_filter);
+       print $cgi->textfield(-name => 's', -value => $searchtext,
+                             -title => "Search project by name and description$limit",
+                             -size => 60) . "\n" .
+             "<span title=\"Extended regular expression\">" .
+             $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
+                            -checked => $search_use_regexp) .
+             "</span>\n" .
+             $cgi->submit(-name => 'btnS', -value => 'Search') .
+             $cgi->end_form() . "\n" .
+             $cgi->a({-href => href(project => undef, searchtext => undef,
+                                    project_filter => $project_filter)},
+                     esc_html("List all projects$limit")) . "<br />\n";
+       print "</div>\n";
+}
+
 # fills project list info (age, description, owner, category, forks)
 # for each project in the list, removing invalid projects from
 # returned list
@@ -5143,7 +5345,7 @@ 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);
 
@@ -5699,7 +5901,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");
@@ -5715,13 +5917,14 @@ sub git_search_files {
        my $lastfile = '';
        while (my $line = <$fd>) {
                chomp $line;
-               my ($file, $lno, $ltext, $binary);
+               my ($file, $file_href, $lno, $ltext, $binary);
                last if ($matches++ > 1000);
                if ($line =~ /^Binary file (.+) matches$/) {
                        $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";
@@ -5730,10 +5933,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;
                }
@@ -5751,10 +5954,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) {
@@ -5842,7 +6044,7 @@ sub git_project_list {
                die_error(400, "Unknown order parameter");
        }
 
-       my @list = git_get_projects_list();
+       my @list = git_get_projects_list($project_filter, $strict_export);
        if (!@list) {
                die_error(404, "No projects found");
        }
@@ -5853,11 +6055,8 @@ sub git_project_list {
                insert_file($home_text);
                print "</div>\n";
        }
-       print $cgi->startform(-method => "get") .
-             "<p class=\"projsearch\">Search:\n" .
-             $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
-             "</p>" .
-             $cgi->end_form() . "\n";
+
+       git_project_search_form($searchtext, $search_use_regexp);
        git_project_list_body(\@list, $order);
        git_footer_html();
 }
@@ -5868,7 +6067,9 @@ sub git_forks {
                die_error(400, "Unknown order parameter");
        }
 
-       my @list = git_get_projects_list($project);
+       my $filter = $project;
+       $filter =~ s/\.git$//;
+       my @list = git_get_projects_list($filter);
        if (!@list) {
                die_error(404, "No forks found");
        }
@@ -5881,7 +6082,7 @@ sub git_forks {
 }
 
 sub git_project_index {
-       my @projects = git_get_projects_list();
+       my @projects = git_get_projects_list($project_filter, $strict_export);
        if (!@projects) {
                die_error(404, "No projects found");
        }
@@ -5927,7 +6128,9 @@ sub git_summary {
 
        if ($check_forks) {
                # find forks of a project
-               @forklist = git_get_projects_list($project);
+               my $filter = $project;
+               $filter =~ s/\.git$//;
+               @forklist = git_get_projects_list($filter);
                # filter out forks of forks
                @forklist = filter_forks_from_projects_list(\@forklist)
                        if (@forklist);
@@ -6058,7 +6261,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
        }
@@ -6107,7 +6310,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";
 
@@ -6949,6 +7154,7 @@ sub git_object {
 
 sub git_blobdiff {
        my $format = shift || 'html';
+       my $diff_style = $input_params{'diff_style'} || 'inline';
 
        my $fd;
        my @difftree;
@@ -7027,6 +7233,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);
@@ -7058,7 +7265,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"
@@ -7083,9 +7291,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') {
@@ -7111,6 +7341,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') {
@@ -7128,8 +7359,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'}) {
@@ -7139,28 +7370,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'}} ) .
                                ')';
@@ -7289,7 +7520,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"
@@ -7689,7 +7921,7 @@ sub git_atom {
 }
 
 sub git_opml {
-       my @list = git_get_projects_list();
+       my @list = git_get_projects_list($project_filter, $strict_export);
        if (!@list) {
                die_error(404, "No projects found");
        }
@@ -7699,11 +7931,18 @@ sub git_opml {
                -charset => 'utf-8',
                -content_disposition => 'inline; filename="opml.xml"');
 
+       my $title = esc_html($site_name);
+       my $filter = " within subdirectory ";
+       if (defined $project_filter) {
+               $filter .= esc_html($project_filter);
+       } else {
+               $filter = "";
+       }
        print <<XML;
 <?xml version="1.0" encoding="utf-8"?>
 <opml version="1.0">
 <head>
-  <title>$site_name OPML Export</title>
+  <title>$title OPML Export$filter</title>
 </head>
 <body>
 <outline text="git RSS feeds">
index 7d88509208417e4b1222629002ea339ecc32526e..c530355a7cefefe694f8cbdcc3a7acc5725bec3a 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;
@@ -490,8 +520,13 @@ div.search {
        right: 12px
 }
 
-p.projsearch {
+div.projsearch {
        text-align: center;
+       margin: 20px 0px;
+}
+
+div.projsearch form {
+       margin-bottom: 2px;
 }
 
 td.linenr {
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..3821400966823ff6bf877abf94f4edea9b6b80c6 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -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
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 69299b7bd2a956266bf581df9c23589a97fca805..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, url);
+       http_init(NULL, url, 0);
        walker = get_http_walker(url);
        walker->get_tree = get_tree;
        walker->get_history = get_history;
index edd553b7f69ed92fde301966e605e7562703718a..f22f7e43caa3e804c5c8275ba0312d58611d7da3 100644 (file)
@@ -1748,6 +1748,8 @@ int main(int argc, char **argv)
        int new_refs;
        struct ref *ref, *local_refs;
 
+       git_setup_gettext();
+
        git_extract_argv0_path(argv[0]);
 
        repo = xcalloc(sizeof(*repo), 1);
@@ -1820,7 +1822,7 @@ int main(int argc, char **argv)
 
        memset(remote_dir_exists, -1, 256);
 
-       http_init(NULL, repo->url);
+       http_init(NULL, repo->url, 1);
 
 #ifdef USE_CURL_MULTI
        is_running_queue = 0;
diff --git a/http.c b/http.c
index e6c75976e8886321732ef2b0b686b08e04a0c200..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, *description;
+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;
 }
 
@@ -139,27 +138,6 @@ static void process_curl_messages(void)
 }
 #endif
 
-static char *git_getpass_with_description(const char *what, const char *desc)
-{
-       struct strbuf prompt = STRBUF_INIT;
-       char *r;
-
-       if (desc)
-               strbuf_addf(&prompt, "%s for '%s': ", what, desc);
-       else
-               strbuf_addf(&prompt, "%s: ", what);
-       /*
-        * NEEDSWORK: for usernames, we should do something less magical that
-        * actually echoes the characters. However, we need to read from
-        * /dev/tty and not stdio, which is not portable (but getpass will do
-        * it for us). http.c uses the same workaround.
-        */
-       r = git_getpass(prompt.buf);
-
-       strbuf_release(&prompt);
-       return xstrdup(r);
-}
-
 static int http_options(const char *var, const char *value, void *cb)
 {
        if (!strcmp("http.sslverify", var)) {
@@ -232,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_with_description("Password", description));
-               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));
        }
@@ -244,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_with_description("Certificate Password", description);
-       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)
@@ -279,10 +253,13 @@ static CURL *get_curl_handle(void)
        curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
 #endif
 
+       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);
@@ -324,42 +301,6 @@ static CURL *get_curl_handle(void)
        return result;
 }
 
-static void http_auth_init(const char *url)
-{
-       const char *at, *colon, *cp, *slash, *host;
-
-       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) {
-               /* No credentials, but we may have to ask for some later */
-               host = cp;
-       }
-       else if (!colon || at <= colon) {
-               /* Only username */
-               user_name = url_decode_mem(cp, at - cp);
-               user_pass = NULL;
-               host = at + 1;
-       } else {
-               user_name = url_decode_mem(cp, colon - cp);
-               user_pass = url_decode_mem(colon + 1, at - (colon + 1));
-               host = at + 1;
-       }
-
-       description = url_decode_mem(host, slash - host);
-}
-
 static void set_from_env(const char **var, const char *envname)
 {
        const char *val = getenv(envname);
@@ -367,7 +308,7 @@ static void set_from_env(const char **var, const char *envname)
                *var = val;
 }
 
-void http_init(struct remote *remote, const char *url)
+void http_init(struct remote *remote, const char *url, int proactive_auth)
 {
        char *low_speed_limit;
        char *low_speed_time;
@@ -378,6 +319,8 @@ void http_init(struct remote *remote, const char *url)
 
        curl_global_init(CURL_GLOBAL_ALL);
 
+       http_proactive_auth = proactive_auth;
+
        if (remote && remote->http_proxy)
                curl_http_proxy = xstrdup(remote->http_proxy);
 
@@ -432,7 +375,7 @@ void http_init(struct remote *remote, const char *url)
                curl_ftp_no_epsv = 1;
 
        if (url) {
-               http_auth_init(url);
+               credential_from_url(&http_auth, url);
                if (!ssl_cert_password_required &&
                    getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") &&
                    !prefixcmp(url, "https://"))
@@ -481,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;
 }
@@ -536,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;
@@ -640,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;
@@ -651,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
@@ -814,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);
@@ -836,17 +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 && user_pass) {
+                       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.
-                                */
-                               if (!user_name)
-                                       user_name = xstrdup(git_getpass_with_description("Username", description));
+                               credential_fill(&http_auth);
                                init_curl_http_auth(slot->curl);
                                ret = HTTP_REAUTH;
                        }
@@ -862,10 +803,12 @@ 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;
 }
 
@@ -1057,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);
@@ -1079,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)
@@ -1148,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);
@@ -1205,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 3c332a98e9358a296b937453351018ac1042120e..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, const char *url);
+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;
index e1ad1a48ce3b8bd8517568a67477d8d0e32dfaa8..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
@@ -41,28 +42,6 @@ struct store_conf {
        unsigned trash_remote_new:1, trash_only_new:1;
 };
 
-struct string_list {
-       struct string_list *next;
-       char string[1];
-};
-
-struct channel_conf {
-       struct channel_conf *next;
-       char *name;
-       struct store_conf *master, *slave;
-       char *master_name, *slave_name;
-       char *sync_state;
-       struct string_list *patterns;
-       int mops, sops;
-       unsigned max_messages; /* for slave only */
-};
-
-struct group_conf {
-       struct group_conf *next;
-       char *name;
-       struct string_list *channels;
-};
-
 /* For message->status */
 #define M_RECENT       (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */
 #define M_DEAD         (1<<1) /* expunged */
@@ -70,7 +49,6 @@ struct group_conf {
 
 struct message {
        struct message *next;
-       /* struct string_list *keywords; */
        size_t size; /* zero implies "not fetched" */
        int uid;
        unsigned char flags, status;
@@ -161,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
@@ -1209,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;
@@ -1539,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);
 
index 39d80c01753527e45716ec762beeb891dfc7d28d..3dd4a960190a1b0016b26dec9187692111b73e3f 100644 (file)
@@ -71,7 +71,8 @@ static void process_tree(struct rev_info *revs,
        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)
@@ -97,12 +98,12 @@ 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;
                }
 
index e7694a3a4ca2e6de7fe8f3b5458eb2b65591373a..cea8756866e5ab86f09f3fadb0fe33e92b04b4bd 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" };
 
@@ -119,9 +120,9 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in
                type = DECORATION_REF_REMOTE;
        else if (!prefixcmp(refname, "refs/tags/"))
                type = DECORATION_REF_TAG;
-       else if (!prefixcmp(refname, "refs/stash"))
+       else if (!strcmp(refname, "refs/stash"))
                type = DECORATION_REF_STASH;
-       else if (!prefixcmp(refname, "HEAD"))
+       else if (!strcmp(refname, "HEAD"))
                type = DECORATION_REF_HEAD;
 
        if (!cb_data || *(int *)cb_data == DECORATE_SHORT_REFS)
@@ -403,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;
@@ -514,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;
 
@@ -599,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 cc664c39b66b0bb499dec13a22880a6096423fa6..6479a60cd112c5b06b354b1a251c60bb4bce972a 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)
                die("error building trees");
 
        result = lookup_tree(active_cache_tree->sha1);
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 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 ce10aacf2978024eda0234482964577e9235f2d2..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,7 +573,7 @@ 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_refname_format(o->local_ref, 0) &&
                is_null_sha1(local_sha1))
@@ -668,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);
        }
 
@@ -695,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",
@@ -733,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..6b06297a5f06cc35cb266d6dd36c92df75a82de7 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;
@@ -189,10 +191,15 @@ struct object *parse_object(const unsigned char *sha1)
        enum object_type type;
        int eaten;
        const unsigned char *repl = lookup_replace_object(sha1);
-       void *buffer = read_sha1_file(sha1, &type, &size);
+       void *buffer;
+       struct object *obj;
+
+       obj = lookup_object(sha1);
+       if (obj && obj->parsed)
+               return obj;
 
+       buffer = read_sha1_file(sha1, &type, &size);
        if (buffer) {
-               struct object *obj;
                if (check_sha1_signature(repl, buffer, size, typename(type)) < 0) {
                        free(buffer);
                        error("sha1 mismatch %s\n", sha1_to_hex(repl));
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 23bbd00e3e542e844fce08156c5c1073e6437d43..f09a05422854c919cd31c542b82b057624275959 100644 (file)
@@ -143,7 +143,6 @@ int pack_refs(unsigned int flags)
        packed.fd = -1;
        if (commit_lock_file(&packed) < 0)
                die_errno("unable to overwrite old ref-pack file");
-       if (cbdata.flags & PACK_REFS_PRUNE)
-               prune_refs(cbdata.ref_to_prune);
+       prune_refs(cbdata.ref_to_prune);
        return 0;
 }
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
index 98b24772c7ebe838d513d8e24f3c8acff7839cb9..d5c6e22d0f74e075a49dd50cde6cbee5049a336c 100644 (file)
@@ -1,5 +1,7 @@
 perl.mak
 perl.mak.old
+MYMETA.json
+MYMETA.yml
 blib
 blibdirs
 pm_to_blib
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 230fe1cc82e3a3bf7c0fef604350868d917acef4..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);
@@ -1116,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..a51bba1b9569d64c348f3affc90ed8640e0e0189 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,14 +1105,21 @@ 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;
 
                ce = istate->cache[i];
                if (ignore_submodules && S_ISGITLINK(ce->ce_mode))
@@ -1122,7 +1132,7 @@ 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);
+                       show_file(unmerged_fmt, ce->name, in_porcelain, &first, header_msg);
                        has_errors = 1;
                        continue;
                }
@@ -1130,10 +1140,12 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
                if (pathspec && !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen))
                        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 +1157,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 +1179,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 +1224,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);
-
-       len = ce->ce_flags & CE_NAMEMASK;
+       flags = ntohs(ondisk->flags);
+       len = 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 +1243,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 +1251,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 +1278,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 +1317,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 +1362,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 +1379,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 e7843eb6b70b7de6c1ed1992db7adee1d99b27bc..c9f68353517bb6dcd4b19114a5191fdf9075a8d1 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -11,14 +11,29 @@ 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;
+
+       /*
+        * Entries with index 0 <= i < sorted are sorted by name.  New
+        * entries are appended to the list unsorted, and are sorted
+        * only when required; thus we avoid the need to sort the list
+        * after the addition of every reference.
+        */
+       int sorted;
+
        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)
 {
        /*
@@ -47,27 +62,30 @@ static const char *parse_ref_line(char *line, unsigned char *sha1)
        return line;
 }
 
-static void add_ref(const char *name, const unsigned char *sha1,
-                   int flag, int check_name, struct ref_array *refs,
-                   struct ref_entry **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_entry *entry;
+       struct ref_entry *ref;
 
-       /* Allocate it and add it in.. */
-       len = strlen(name) + 1;
-       entry = xmalloc(sizeof(struct ref_entry) + len);
-       hashcpy(entry->sha1, sha1);
-       hashclr(entry->peeled);
        if (check_name &&
-           check_refname_format(name, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))
-               die("Reference has invalid format: '%s'", name);
-       memcpy(entry->name, name, len);
-       entry->flag = flag;
-       if (new_entry)
-               *new_entry = entry;
+           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;
+}
+
+/* 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++] = entry;
+       refs->refs[refs->nr++] = ref;
 }
 
 static int ref_entry_cmp(const void *a, const void *b)
@@ -77,48 +95,67 @@ static int ref_entry_cmp(const void *a, const void *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;
+       }
+}
+
+/*
+ * Sort the entries in array (if they are not already sorted).
+ */
 static void sort_ref_array(struct ref_array *array)
 {
-       int i = 0, j = 1;
+       int i, j;
 
-       /* Nothing to sort unless there are at least two entries */
-       if (array->nr < 2)
+       /*
+        * This check also prevents passing a zero-length array to qsort(),
+        * which is a problem on some platforms.
+        */
+       if (array->sorted == array->nr)
                return;
 
        qsort(array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
 
        /* Remove any duplicates from the ref_array */
-       for (; j < array->nr; j++) {
-               struct ref_entry *a = array->refs[i];
-               struct ref_entry *b = array->refs[j];
-               if (!strcmp(a->name, b->name)) {
-                       if (hashcmp(a->sha1, b->sha1))
-                               die("Duplicated ref, and SHA1s don't match: %s",
-                                   a->name);
-                       warning("Duplicated ref: %s", a->name);
-                       free(b);
+       i = 0;
+       for (j = 1; j < array->nr; j++) {
+               if (is_dup_ref(array->refs[i], array->refs[j])) {
+                       free(array->refs[j]);
                        continue;
                }
-               i++;
-               array->refs[i] = array->refs[j];
+               array->refs[++i] = array->refs[j];
        }
-       array->nr = i + 1;
+       array->sorted = array->nr = i + 1;
 }
 
-static struct ref_entry *search_ref_array(struct ref_array *array, const char *name)
+static struct ref_entry *search_ref_array(struct ref_array *array, const char *refname)
 {
        struct ref_entry *e, **r;
        int len;
 
-       if (name == NULL)
+       if (refname == NULL)
                return NULL;
 
        if (!array->nr)
                return NULL;
-
-       len = strlen(name) + 1;
+       sort_ref_array(array);
+       len = strlen(refname) + 1;
        e = xmalloc(sizeof(struct ref_entry) + len);
-       memcpy(e->name, name, len);
+       memcpy(e->name, refname, len);
 
        r = bsearch(&e, array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
 
@@ -146,29 +183,27 @@ static struct ref_cache {
 
 static struct ref_entry *current_ref;
 
-static struct ref_array extra_refs;
-
-static void free_ref_array(struct ref_array *array)
+static void clear_ref_array(struct ref_array *array)
 {
        int i;
        for (i = 0; i < array->nr; i++)
                free(array->refs[i]);
        free(array->refs);
-       array->nr = array->alloc = 0;
+       array->sorted = array->nr = array->alloc = 0;
        array->refs = NULL;
 }
 
 static void clear_packed_ref_cache(struct ref_cache *refs)
 {
        if (refs->did_packed)
-               free_ref_array(&refs->packed);
+               clear_ref_array(&refs->packed);
        refs->did_packed = 0;
 }
 
 static void clear_loose_ref_cache(struct ref_cache *refs)
 {
        if (refs->did_loose)
-               free_ref_array(&refs->loose);
+               clear_ref_array(&refs->loose);
        refs->did_loose = 0;
 }
 
@@ -222,7 +257,7 @@ static void read_packed_refs(FILE *f, struct ref_array *array)
 
        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)) {
@@ -233,9 +268,10 @@ static void read_packed_refs(FILE *f, struct ref_array *array)
                        continue;
                }
 
-               name = parse_ref_line(refline, sha1);
-               if (name) {
-                       add_ref(name, sha1, flag, 1, array, &last);
+               refname = parse_ref_line(refline, sha1);
+               if (refname) {
+                       last = create_ref_entry(refname, sha1, flag, 1);
+                       add_ref(array, last);
                        continue;
                }
                if (last &&
@@ -245,29 +281,16 @@ static void read_packed_refs(FILE *f, struct ref_array *array)
                    !get_sha1_hex(refline + 1, sha1))
                        hashcpy(last->peeled, sha1);
        }
-       sort_ref_array(array);
 }
 
-void add_extra_ref(const char *name, const unsigned char *sha1, int flag)
+static struct ref_array *get_packed_refs(struct ref_cache *refs)
 {
-       add_ref(name, sha1, flag, 0, &extra_refs, NULL);
-}
-
-void clear_extra_refs(void)
-{
-       free_ref_array(&extra_refs);
-}
-
-static struct ref_array *get_packed_refs(const char *submodule)
-{
-       struct ref_cache *refs = get_ref_cache(submodule);
-
        if (!refs->did_packed) {
                const char *packed_refs_file;
                FILE *f;
 
-               if (submodule)
-                       packed_refs_file = git_path_submodule(submodule, "packed-refs");
+               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");
@@ -280,14 +303,20 @@ static struct ref_array *get_packed_refs(const char *submodule)
        return &refs->packed;
 }
 
-static void get_ref_dir(const char *submodule, const char *base,
+void add_packed_ref(const char *refname, const unsigned char *sha1)
+{
+       add_ref(get_packed_refs(get_ref_cache(NULL)),
+                       create_ref_entry(refname, sha1, REF_ISPACKED, 1));
+}
+
+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);
 
@@ -297,11 +326,11 @@ static void 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];
@@ -317,30 +346,30 @@ static void 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)) {
-                               get_ref_dir(submodule, ref, array);
+                               get_ref_dir(refs, refname, array);
                                continue;
                        }
-                       if (submodule) {
+                       if (*refs->name) {
                                hashclr(sha1);
                                flag = 0;
-                               if (resolve_gitlink_ref(submodule, ref, sha1) < 0) {
+                               if (resolve_gitlink_ref(refs->name, refname, sha1) < 0) {
                                        hashclr(sha1);
                                        flag |= REF_ISBROKEN;
                                }
-                       } else if (!resolve_ref(ref, sha1, 1, &flag)) {
+                       } else if (read_ref_full(refname, sha1, 1, &flag)) {
                                hashclr(sha1);
                                flag |= REF_ISBROKEN;
                        }
-                       add_ref(ref, sha1, flag, 1, array, NULL);
+                       add_ref(array, create_ref_entry(refname, sha1, flag, 1));
                }
-               free(ref);
+               free(refname);
                closedir(dir);
        }
 }
@@ -361,7 +390,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;
 
@@ -379,13 +408,10 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
        for_each_rawref(warn_if_dangling_symref, &data);
 }
 
-static struct ref_array *get_loose_refs(const char *submodule)
+static struct ref_array *get_loose_refs(struct ref_cache *refs)
 {
-       struct ref_cache *refs = get_ref_cache(submodule);
-
        if (!refs->did_loose) {
-               get_ref_dir(submodule, "refs", &refs->loose);
-               sort_ref_array(&refs->loose);
+               get_ref_dir(refs, "refs", &refs->loose);
                refs->did_loose = 1;
        }
        return &refs->loose;
@@ -397,39 +423,39 @@ static struct ref_array *get_loose_refs(const char *submodule)
 
 /*
  * Called by resolve_gitlink_ref_recursive() after it failed to read
- * from "name", which is "module/.git/<refname>". Find <refname> in
- * the packed-refs file for the submodule.
+ * from the loose refs in ref_cache refs. Find <refname> in the
+ * packed-refs file for the submodule.
  */
-static int resolve_gitlink_packed_ref(char *name, int pathlen, const char *refname, unsigned char *result)
+static int resolve_gitlink_packed_ref(struct ref_cache *refs,
+                                     const char *refname, unsigned char *sha1)
 {
-       int retval = -1;
        struct ref_entry *ref;
-       struct ref_array *array;
+       struct ref_array *array = get_packed_refs(refs);
 
-       /* being defensive: resolve_gitlink_ref() did this for us */
-       if (pathlen < 6 || memcmp(name + pathlen - 6, "/.git/", 6))
-               die("Oops");
-       name[pathlen - 6] = '\0'; /* make it path to the submodule */
-       array = get_packed_refs(name);
        ref = search_ref_array(array, refname);
-       if (ref != NULL) {
-               memcpy(result, ref->sha1, 20);
-               retval = 0;
-       }
-       return retval;
+       if (ref == NULL)
+               return -1;
+
+       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);
@@ -440,7 +466,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? */
@@ -450,35 +476,24 @@ 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;
 }
 
@@ -486,10 +501,10 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *re
  * Try to read ref from the packed references.  On success, set sha1
  * and return 0; otherwise, return -1.
  */
-static int get_packed_ref(const char *ref, unsigned char *sha1)
+static int get_packed_ref(const char *refname, unsigned char *sha1)
 {
-       struct ref_array *packed = get_packed_refs(NULL);
-       struct ref_entry *entry = search_ref_array(packed, ref);
+       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;
@@ -497,17 +512,17 @@ static int get_packed_ref(const char *ref, unsigned char *sha1)
        return -1;
 }
 
-const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
+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(ref, REFNAME_ALLOW_ONELEVEL))
+       if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
                return NULL;
 
        for (;;) {
@@ -519,7 +534,7 @@ 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);
+               git_snpath(path, sizeof(path), "%s", refname);
 
                if (lstat(path, &st) < 0) {
                        if (errno != ENOENT)
@@ -528,17 +543,17 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                         * The loose reference file does not exist;
                         * check for a packed reference.
                         */
-                       if (!get_packed_ref(ref, sha1)) {
+                       if (!get_packed_ref(refname, sha1)) {
                                if (flag)
                                        *flag |= REF_ISPACKED;
-                               return ref;
+                               return refname;
                        }
                        /* The reference is not a packed reference, either. */
                        if (reading) {
                                return NULL;
                        } else {
                                hashclr(sha1);
-                               return ref;
+                               return refname;
                        }
                }
 
@@ -550,8 +565,8 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                        buffer[len] = 0;
                        if (!prefixcmp(buffer, "refs/") &&
                                        !check_refname_format(buffer, 0)) {
-                               strcpy(ref_buffer, buffer);
-                               ref = ref_buffer;
+                               strcpy(refname_buffer, buffer);
+                               refname = refname_buffer;
                                if (flag)
                                        *flag |= REF_ISSYMREF;
                                continue;
@@ -594,7 +609,7 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                                *flag |= REF_ISBROKEN;
                        return NULL;
                }
-               ref = strcpy(ref_buffer, buf);
+               refname = strcpy(refname_buffer, buf);
        }
        /* Please note that FETCH_HEAD has a second line containing other data. */
        if (get_sha1_hex(buffer, sha1) || (buffer[40] != '\0' && !isspace(buffer[40]))) {
@@ -602,7 +617,13 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                        *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 */
@@ -612,13 +633,18 @@ 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_entry *entry)
@@ -638,23 +664,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;
@@ -663,12 +689,12 @@ 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_array *array = get_packed_refs(NULL);
-               struct ref_entry *r = search_ref_array(array, ref);
+               struct ref_array *array = get_packed_refs(get_ref_cache(NULL));
+               struct ref_entry *r = search_ref_array(array, refname);
 
                if (r != NULL && r->flag & REF_KNOWS_PEELED) {
                        hashcpy(sha1, r->peeled);
@@ -679,7 +705,7 @@ int peel_ref(const char *ref, unsigned char *sha1)
 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;
@@ -691,15 +717,13 @@ 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, i, p = 0, l = 0;
-       struct ref_array *packed = get_packed_refs(submodule);
-       struct ref_array *loose = get_loose_refs(submodule);
-
-       struct ref_array *extra = &extra_refs;
-
-       for (i = 0; i < extra->nr; i++)
-               retval = do_one_ref(base, fn, trim, flags, cb_data, extra->refs[i]);
+       int retval = 0, 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);
 
+       sort_ref_array(packed);
+       sort_ref_array(loose);
        while (p < packed->nr && l < loose->nr) {
                struct ref_entry *entry;
                int cmp = strcmp(packed->refs[p]->name, loose->refs[l]->name);
@@ -746,7 +770,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;
@@ -826,7 +850,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);
 
@@ -909,16 +933,16 @@ static inline int bad_ref_char(int ch)
 }
 
 /*
- * Try to read one refname component from the front of ref.  Return
+ * 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 *ref, int flags)
+static int check_refname_component(const char *refname, int flags)
 {
        const char *cp;
        char last = '\0';
 
-       for (cp = ref; ; cp++) {
+       for (cp = refname; ; cp++) {
                char ch = *cp;
                if (ch == '\0' || ch == '/')
                        break;
@@ -930,34 +954,34 @@ static int check_refname_component(const char *ref, int flags)
                        return -1; /* Refname contains "@{". */
                last = ch;
        }
-       if (cp == ref)
+       if (cp == refname)
                return -1; /* Component has zero length. */
-       if (ref[0] == '.') {
+       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 (ref[1] == '\0')
+               if (refname[1] == '\0')
                        return -1; /* Component equals ".". */
        }
-       if (cp - ref >= 5 && !memcmp(cp - 5, ".lock", 5))
+       if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5))
                return -1; /* Refname ends with ".lock". */
-       return cp - ref;
+       return cp - refname;
 }
 
-int check_refname_format(const char *ref, int flags)
+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(ref, flags);
+               component_len = check_refname_component(refname, flags);
                if (component_len < 0) {
                        if ((flags & REFNAME_REFSPEC_PATTERN) &&
-                                       ref[0] == '*' &&
-                                       (ref[1] == '\0' || ref[1] == '/')) {
+                                       refname[0] == '*' &&
+                                       (refname[1] == '\0' || refname[1] == '/')) {
                                /* Accept one wildcard as a full refname component. */
                                flags &= ~REFNAME_REFSPEC_PATTERN;
                                component_len = 1;
@@ -966,13 +990,13 @@ int check_refname_format(const char *ref, int flags)
                        }
                }
                component_count++;
-               if (ref[component_len] == '\0')
+               if (refname[component_len] == '\0')
                        break;
                /* Skip to next component. */
-               ref += component_len + 1;
+               refname += component_len + 1;
        }
 
-       if (ref[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. */
@@ -998,13 +1022,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;
@@ -1022,7 +1039,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;
@@ -1055,22 +1072,28 @@ static int remove_empty_directories(const char *file)
        return result;
 }
 
-static int is_refname_available(const char *ref, const char *oldref,
-                               struct ref_array *array, int quiet)
+/*
+ * 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(ref); /* e.g. 'foo/bar' */
+       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 (!oldref || strcmp(oldref, entry->name)) {
+               if (!oldrefname || strcmp(oldrefname, entry->name)) {
                        int len = strlen(entry->name);
                        int cmplen = (namlen < len) ? namlen : len;
-                       const char *lead = (namlen < len) ? entry->name : ref;
-                       if (!strncmp(ref, entry->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'",
-                                             entry->name, ref);
+                               error("'%s' exists; cannot create '%s'",
+                                     entry->name, refname);
                                return 0;
                        }
                }
@@ -1113,7 +1136,7 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
 
                this_result = refs_found ? sha1_from_ref : sha1;
                mksnpath(fullref, sizeof(fullref), *p, len, str);
-               r = resolve_ref(fullref, this_result, 1, &flag);
+               r = resolve_ref_unsafe(fullref, this_result, 1, &flag);
                if (r) {
                        if (!refs_found++)
                                *ref = xstrdup(r);
@@ -1143,7 +1166,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
                const char *ref, *it;
 
                mksnpath(path, sizeof(path), *p, len, str);
-               ref = resolve_ref(path, hash, 1, NULL);
+               ref = resolve_ref_unsafe(path, hash, 1, NULL);
                if (!ref)
                        continue;
                if (!stat(git_path("logs/%s", path), &st) &&
@@ -1166,10 +1189,12 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
        return logs_found;
 }
 
-static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int flags, int *type_p)
+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;
@@ -1179,27 +1204,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);
@@ -1209,7 +1234,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;
        }
@@ -1218,12 +1243,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))
@@ -1244,20 +1269,21 @@ 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_refname_format(ref, 0))
+       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)
 {
-       if (check_refname_format(ref, REFNAME_ALLOW_ONELEVEL))
+       if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
                return NULL;
-       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;
@@ -1265,12 +1291,10 @@ static struct lock_file packlock;
 static int repack_without_ref(const char *refname)
 {
        struct ref_array *packed;
-       struct ref_entry *ref;
        int fd, i;
 
-       packed = get_packed_refs(NULL);
-       ref = search_ref_array(packed, refname);
-       if (ref == NULL)
+       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) {
@@ -1281,8 +1305,7 @@ static int repack_without_ref(const char *refname)
        for (i = 0; i < packed->nr; i++) {
                char line[PATH_MAX + 100];
                int len;
-
-               ref = packed->refs[i];
+               struct ref_entry *ref = packed->refs[i];
 
                if (!strcmp(refname, ref->name))
                        continue;
@@ -1343,96 +1366,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;
        }
 
@@ -1440,17 +1465,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;
 }
@@ -1507,16 +1532,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);
@@ -1546,7 +1571,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;
@@ -1559,7 +1584,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;
 
@@ -1643,7 +1668,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);
@@ -1730,7 +1755,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;
@@ -1741,7 +1768,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);
@@ -1834,14 +1861,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;
@@ -1892,9 +1919,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)
@@ -1929,7 +1956,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);
@@ -1980,7 +2007,7 @@ int update_ref(const char *action, const 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)
@@ -2014,7 +2041,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;
@@ -2043,10 +2070,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) {
@@ -2054,7 +2081,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);
@@ -2072,7 +2099,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 */
@@ -2086,7 +2112,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;
                }
 
@@ -2099,5 +2125,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 3fd55369f90829706c3f4b11329fadc38380da32..33202b0d4c85cafdaf60b568a6f728dd83462c46 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -51,23 +51,23 @@ extern int for_each_rawref(each_ref_fn, void *);
 extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
 
 /*
- * Extra refs will be listed by for_each_ref() before any actual refs
- * for the duration of this process or until clear_extra_refs() is
- * called. Only extra refs added before for_each_ref() is called will
- * be listed on a given call of for_each_ref().
+ * Add a reference to the in-memory packed reference cache.  To actually
+ * write the reference to the packed-refs file, call pack_refs().
  */
-extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags);
-extern void clear_extra_refs(void);
+extern void add_packed_ref(const char *refname, const unsigned char *sha1);
+
 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);
@@ -93,12 +93,14 @@ extern void invalidate_ref_cache(const char *submodule);
 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,
@@ -111,9 +113,9 @@ extern int for_each_reflog(each_ref_fn, void *);
 #define REFNAME_DOT_COMPONENT 4
 
 /*
- * Return 0 iff ref 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
+ * 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
@@ -121,16 +123,20 @@ extern int for_each_reflog(each_ref_fn, void *);
  * components to start with "." (but not a whole component equal to
  * "." or "..").
  */
-extern int check_refname_format(const char *ref, int flags);
+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 0e720ee8bbf4cbc6a50336a1f1c93bfc63842fe3..d159fe7f3433ccf6e8c8908961736951e42b9c35 100644 (file)
@@ -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, url);
+       http_init(remote, url, 0);
 
        do {
                if (strbuf_getline(&buf, stdin, '\n') == EOF) {
index e2ef99114478c49863809ea5cef46392a5a8c0e9..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 =
@@ -1007,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;
 
@@ -1058,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/")))
@@ -1507,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)
@@ -1572,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;
 }
 
index 8764dde381111cfc9c8ea7eb3856223de9786ec9..819ff012ff046fee0da1fee16f965b3059ebc31b 100644 (file)
@@ -139,11 +139,32 @@ void mark_tree_uninteresting(struct tree *tree)
 
 void mark_parents_uninteresting(struct commit *commit)
 {
-       struct commit_list *parents = commit->parents;
+       struct commit_list *parents = NULL, *l;
+
+       for (l = commit->parents; l; l = l->next)
+               commit_list_insert(l->item, &parents);
 
        while (parents) {
                struct commit *commit = parents->item;
-               if (!(commit->object.flags & UNINTERESTING)) {
+               l = parents;
+               parents = parents->next;
+               free(l);
+
+               while (commit) {
+                       /*
+                        * A missing commit is ok iff its parent is marked
+                        * uninteresting.
+                        *
+                        * We just mark such a thing parsed, so that when
+                        * it is popped next time around, we won't be trying
+                        * to parse it and get an error.
+                        */
+                       if (!has_sha1_file(commit->object.sha1))
+                               commit->object.parsed = 1;
+
+                       if (commit->object.flags & UNINTERESTING)
+                               break;
+
                        commit->object.flags |= UNINTERESTING;
 
                        /*
@@ -154,21 +175,13 @@ void mark_parents_uninteresting(struct commit *commit)
                         * wasn't uninteresting), in which case we need
                         * to mark its parents recursively too..
                         */
-                       if (commit->parents)
-                               mark_parents_uninteresting(commit);
-               }
+                       if (!commit->parents)
+                               break;
 
-               /*
-                * A missing commit is ok iff its parent is marked
-                * uninteresting.
-                *
-                * We just mark such a thing parsed, so that when
-                * it is popped next time around, we won't be trying
-                * to parse it and get an error.
-                */
-               if (!has_sha1_file(commit->object.sha1))
-                       commit->object.parsed = 1;
-               parents = parents->next;
+                       for (l = commit->parents->next; l; l = l->next)
+                               commit_list_insert(l->item, &parents);
+                       commit = commit->parents->item;
+               }
        }
 }
 
@@ -416,7 +429,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
@@ -444,6 +457,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),
@@ -1469,6 +1490,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;
@@ -2126,7 +2149,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 6aa53d1aa708918e4bbbebb042e0bf75c1629b05..b8e9223954a5d66e01bd1eb342a936495aa67ad1 100644 (file)
@@ -110,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,
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);
 
 /*
index bc2c046aab23c5b3de8caa02c23703856ee86fb4..5fcbcb8875454574f4d3a52b5d1ac84b99452ba7 100644 (file)
 #include "cache.h"
 #include "sequencer.h"
-#include "strbuf.h"
 #include "dir.h"
+#include "object.h"
+#include "commit.h"
+#include "tag.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+#include "utf8.h"
+#include "cache-tree.h"
+#include "diff.h"
+#include "revision.h"
+#include "rerere.h"
+#include "merge-recursive.h"
+#include "refs.h"
 
-void remove_sequencer_state(int aggressive)
+#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
+
+void remove_sequencer_state(void)
 {
        struct strbuf seq_dir = STRBUF_INIT;
-       struct strbuf seq_old_dir = STRBUF_INIT;
 
        strbuf_addf(&seq_dir, "%s", git_path(SEQ_DIR));
-       strbuf_addf(&seq_old_dir, "%s", git_path(SEQ_OLD_DIR));
-       remove_dir_recursively(&seq_old_dir, 0);
-       rename(git_path(SEQ_DIR), git_path(SEQ_OLD_DIR));
-       if (aggressive)
-               remove_dir_recursively(&seq_old_dir, 0);
+       remove_dir_recursively(&seq_dir, 0);
        strbuf_release(&seq_dir);
-       strbuf_release(&seq_old_dir);
+}
+
+static const char *action_name(const struct replay_opts *opts)
+{
+       return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
+}
+
+static char *get_encoding(const char *message);
+
+struct commit_message {
+       char *parent_label;
+       const char *label;
+       const char *subject;
+       char *reencoded_message;
+       const char *message;
+};
+
+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 (!commit->buffer)
+               return -1;
+       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 = commit->buffer;
+       if (strcmp(encoding, git_commit_encoding))
+               out->reencoded_message = reencode_string(commit->buffer,
+                                       git_commit_encoding, encoding);
+       if (out->reencoded_message)
+               out->message = out->reencoded_message;
+
+       abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
+       abbrev_len = strlen(abbrev);
+
+       subject_len = find_commit_subject(out->message, &subject);
+
+       out->parent_label = xmalloc(strlen("parent of ") + abbrev_len +
+                             strlen("... ") + subject_len + 1);
+       q = out->parent_label;
+       q = mempcpy(q, "parent of ", strlen("parent of "));
+       out->label = q;
+       q = mempcpy(q, abbrev, abbrev_len);
+       q = mempcpy(q, "... ", strlen("... "));
+       out->subject = q;
+       q = mempcpy(q, subject, subject_len);
+       *q = '\0';
+       return 0;
+}
+
+static void free_message(struct commit_message *msg)
+{
+       free(msg->parent_label);
+       free(msg->reencoded_message);
+}
+
+static char *get_encoding(const char *message)
+{
+       const char *p = message, *eol;
+
+       while (*p && *p != '\n') {
+               for (eol = p + 1; *eol && *eol != '\n'; eol++)
+                       ; /* do nothing */
+               if (!prefixcmp(p, "encoding ")) {
+                       char *result = xmalloc(eol - 8 - p);
+                       strlcpy(result, p + 9, eol - 8 - p);
+                       return result;
+               }
+               p = eol;
+               if (*p == '\n')
+                       p++;
+       }
+       return NULL;
+}
+
+static void 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));
+
+       filename = git_path("%s", pseudoref);
+       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 || close(fd))
+               die_errno(_("Could not write to '%s'"), filename);
+       strbuf_release(&buf);
+}
+
+static void print_advice(int show_hint)
+{
+       char *msg = getenv("GIT_CHERRY_PICK_HELP");
+
+       if (msg) {
+               fprintf(stderr, "%s\n", msg);
+               /*
+                * A conflict has occured but the porcelain
+                * (typically rebase --interactive) wants to take care
+                * of the commit itself so remove CHERRY_PICK_HEAD
+                */
+               unlink(git_path("CHERRY_PICK_HEAD"));
+               return;
+       }
+
+       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)
+{
+       static struct lock_file msg_file;
+
+       int msg_fd = hold_lock_file_for_update(&msg_file, filename,
+                                              LOCK_DIE_ON_ERROR);
+       if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
+               die_errno(_("Could not write to %s"), filename);
+       strbuf_release(msgbuf);
+       if (commit_lock_file(&msg_file) < 0)
+               die(_("Error wrapping up %s"), filename);
+}
+
+static struct tree *empty_tree(void)
+{
+       return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
+}
+
+static int error_dirty_index(struct replay_opts *opts)
+{
+       if (read_cache_unmerged())
+               return error_resolve_conflict(action_name(opts));
+
+       /* Different translation strings for cherry-pick and revert */
+       if (opts->action == REPLAY_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)
+{
+       struct ref_lock *ref_lock;
+
+       read_cache();
+       if (checkout_fast_forward(from, to))
+               exit(1); /* the callee should have complained already */
+       ref_lock = lock_any_ref_for_update("HEAD", from, 0);
+       return write_ref_sha1(ref_lock, to, "cherry-pick");
+}
+
+static int do_recursive_merge(struct commit *base, struct commit *next,
+                             const char *base_label, const char *next_label,
+                             unsigned char *head, struct strbuf *msgbuf,
+                             struct replay_opts *opts)
+{
+       struct merge_options o;
+       struct tree *result, *next_tree, *base_tree, *head_tree;
+       int clean, index_fd;
+       const char **xopt;
+       static struct lock_file index_lock;
+
+       index_fd = hold_locked_index(&index_lock, 1);
+
+       read_cache();
+
+       init_merge_options(&o);
+       o.ancestor = base ? base_label : "(empty tree)";
+       o.branch1 = "HEAD";
+       o.branch2 = next ? next_label : "(empty tree)";
+
+       head_tree = parse_tree_indirect(head);
+       next_tree = next ? next->tree : empty_tree();
+       base_tree = base ? base->tree : empty_tree();
+
+       for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
+               parse_merge_opt(&o, *xopt);
+
+       clean = merge_trees(&o,
+                           head_tree,
+                           next_tree, base_tree, &result);
+
+       if (active_cache_changed &&
+           (write_cache(index_fd, active_cache, active_nr) ||
+            commit_locked_index(&index_lock)))
+               /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
+               die(_("%s: Unable to write new index file"), action_name(opts));
+       rollback_lock_file(&index_lock);
+
+       if (!clean) {
+               int i;
+               strbuf_addstr(msgbuf, "\nConflicts:\n\n");
+               for (i = 0; i < active_nr;) {
+                       struct cache_entry *ce = active_cache[i++];
+                       if (ce_stage(ce)) {
+                               strbuf_addch(msgbuf, '\t');
+                               strbuf_addstr(msgbuf, ce->name);
+                               strbuf_addch(msgbuf, '\n');
+                               while (i < active_nr && !strcmp(ce->name,
+                                               active_cache[i]->name))
+                                       i++;
+                       }
+               }
+       }
+
+       return !clean;
+}
+
+/*
+ * If we are cherry-pick, and if the merge did not result in
+ * hand-editing, we will hit this commit and inherit the original
+ * author date and name.
+ * If we are revert, or if our cherry-pick results in a hand merge,
+ * we had better say that the current user is responsible for that.
+ */
+static int run_git_commit(const char *defmsg, struct replay_opts *opts)
+{
+       /* 6 is max possible length of our args array including NULL */
+       const char *args[6];
+       int i = 0;
+
+       args[i++] = "commit";
+       args[i++] = "-n";
+       if (opts->signoff)
+               args[i++] = "-s";
+       if (!opts->edit) {
+               args[i++] = "-F";
+               args[i++] = defmsg;
+       }
+       args[i] = NULL;
+
+       return run_command_v_opt(args, RUN_GIT_CMD);
+}
+
+static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
+{
+       unsigned char head[20];
+       struct commit *base, *next, *parent;
+       const char *base_label, *next_label;
+       struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
+       char *defmsg = NULL;
+       struct strbuf msgbuf = STRBUF_INIT;
+       int res;
+
+       if (opts->no_commit) {
+               /*
+                * We do not intend to commit immediately.  We just want to
+                * merge the differences in, so let's compute the tree
+                * that represents the "current" state for merge-recursive
+                * to work on.
+                */
+               if (write_cache_as_tree(head, 0, NULL))
+                       die (_("Your index file is unmerged."));
+       } else {
+               if (get_sha1("HEAD", head))
+                       return error(_("You do not have a valid HEAD"));
+               if (index_differs_from("HEAD", 0))
+                       return error_dirty_index(opts);
+       }
+       discard_cache();
+
+       if (!commit->parents) {
+               parent = NULL;
+       }
+       else if (commit->parents->next) {
+               /* Reverting or cherry-picking a merge commit */
+               int cnt;
+               struct commit_list *p;
+
+               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 != opts->mainline && p;
+                    cnt++)
+                       p = p->next;
+               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 < 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 (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 */
+               return error(_("%s: cannot parse parent commit %s"),
+                       action_name(opts), sha1_to_hex(parent->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
+        * the difference it introduces since its first parent "prev"
+        * on top of the current HEAD if we are cherry-pick.  Or the
+        * reverse of it if we are revert.
+        */
+
+       defmsg = git_pathdup("MERGE_MSG");
+
+       if (opts->action == REPLAY_REVERT) {
+               base = commit;
+               base_label = msg.label;
+               next = parent;
+               next_label = msg.parent_label;
+               strbuf_addstr(&msgbuf, "Revert \"");
+               strbuf_addstr(&msgbuf, msg.subject);
+               strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
+               strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
+
+               if (commit->parents && commit->parents->next) {
+                       strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
+                       strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));
+               }
+               strbuf_addstr(&msgbuf, ".\n");
+       } else {
+               const char *p;
+
+               base = parent;
+               base_label = msg.parent_label;
+               next = commit;
+               next_label = msg.label;
+
+               /*
+                * 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 (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
+               res = do_recursive_merge(base, next, base_label, next_label,
+                                        head, &msgbuf, opts);
+               write_message(&msgbuf, defmsg);
+       } else {
+               struct commit_list *common = NULL;
+               struct commit_list *remotes = NULL;
+
+               write_message(&msgbuf, defmsg);
+
+               commit_list_insert(base, &common);
+               commit_list_insert(next, &remotes);
+               res = try_merge_command(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 == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1))
+               write_cherry_pick_head(commit, "CHERRY_PICK_HEAD");
+       if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1))
+               write_cherry_pick_head(commit, "REVERT_HEAD");
+
+       if (res) {
+               error(opts->action == REPLAY_REVERT
+                     ? _("could not revert %s... %s")
+                     : _("could not apply %s... %s"),
+                     find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
+                     msg.subject);
+               print_advice(res == 1);
+               rerere(opts->allow_rerere_auto);
+       } else {
+               if (!opts->no_commit)
+                       res = run_git_commit(defmsg, opts);
+       }
+
+       free_message(&msg);
+       free(defmsg);
+
+       return res;
+}
+
+static void prepare_revs(struct replay_opts *opts)
+{
+       if (opts->action != REPLAY_REVERT)
+               opts->revs->reverse ^= 1;
+
+       if (prepare_revision_walk(opts->revs))
+               die(_("revision walk setup failed"));
+
+       if (!opts->revs->commits)
+               die(_("empty commit set passed"));
+}
+
+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"), 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"), action_name(opts));
+       }
+       rollback_lock_file(&index_lock);
+}
+
+/*
+ * 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 commit_list *new = xmalloc(sizeof(struct commit_list));
+       new->item = commit;
+       *next = new;
+       new->next = NULL;
+       return &new->next;
+}
+
+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 == REPLAY_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 = REPLAY_PICK;
+               bol += strlen("pick");
+       } else if (!prefixcmp(bol, "revert")) {
+               action = REPLAY_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 == REPLAY_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);
+}
+
+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);
+       }
+}
+
+static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
+{
+       struct commit_list *cur;
+       int res;
+
+       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);
+
+       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);
+}
+
+int sequencer_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 == REPLAY_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);
 }
index f435fdb4b147d2e2ce5480ddb4832228af1cb73b..bb4b13830e157dafb468f8256bab25110058ba9b 100644 (file)
@@ -2,19 +2,48 @@
 #define SEQUENCER_H
 
 #define SEQ_DIR                "sequencer"
-#define SEQ_OLD_DIR    "sequencer-old"
 #define SEQ_HEAD_FILE  "sequencer/head"
 #define SEQ_TODO_FILE  "sequencer/todo"
 #define SEQ_OPTS_FILE  "sequencer/opts"
 
-/*
- * Removes SEQ_OLD_DIR and renames SEQ_DIR to SEQ_OLD_DIR, ignoring
- * any errors.  Intended to be used by 'git reset'.
- *
- * With the aggressive flag, it additionally removes SEQ_OLD_DIR,
- * ignoring any errors.  Inteded to be used by the sequencer's
- * '--quit' subcommand.
- */
-void remove_sequencer_state(int aggressive);
+enum replay_action {
+       REPLAY_REVERT,
+       REPLAY_PICK
+};
+
+enum replay_subcommand {
+       REPLAY_NONE,
+       REPLAY_REMOVE_STATE,
+       REPLAY_CONTINUE,
+       REPLAY_ROLLBACK
+};
+
+struct replay_opts {
+       enum replay_action action;
+       enum replay_subcommand subcommand;
+
+       /* Boolean options */
+       int edit;
+       int record_origin;
+       int no_commit;
+       int signoff;
+       int allow_ff;
+       int allow_rerere_auto;
+
+       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;
+};
+
+/* Removes SEQ_DIR. */
+extern void remove_sequencer_state(void);
+
+int sequencer_pick_revisions(struct replay_opts *opts);
 
 #endif
index 6dcae3882bf969cce9ae0a973a80abf76e3bb814..f9f8d5e91c278000e5869f49ba6a79ddfaf13145 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;
@@ -719,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;
                }
@@ -1201,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);
@@ -1267,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;
@@ -2008,54 +2019,58 @@ 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)) {
-                               warning("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;
 }
 
@@ -2450,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 */
@@ -2503,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)
@@ -2679,10 +2694,8 @@ 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
@@ -2696,65 +2709,7 @@ 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,
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..ff0b96b4162bd92162a7eb05eee5be7a5ec2b6ba 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -397,3 +397,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..fbf059f4d371441b58fcad748e74e106a436241f 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);
@@ -115,4 +123,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 0fd10a0fdbf5d1af10819dea808b43f6b13b98a8..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(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;
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..3534f43d016ae56fdd20a824cf76e2b63339d66d 100644 (file)
--- a/t/README
+++ b/t/README
@@ -548,6 +548,19 @@ library for your script to use.
                ...
        '
 
+ - test_pause
+
+       This command is useful for writing and debugging tests and must be
+       removed before submitting. It halts the execution of the test and
+       spawns a shell in the trash directory. Exit the shell to continue
+       the test. Example:
+
+       test_expect_success 'test' '
+               git do-something >actual &&
+               test_pause &&
+               test_cmp expected actual
+       '
+
 Prerequisites
 -------------
 
@@ -658,76 +671,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.
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-daemon.sh b/t/lib-git-daemon.sh
new file mode 100644 (file)
index 0000000..ef2d01f
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+if test -z "$GIT_TEST_GIT_DAEMON"
+then
+       skip_all="git-daemon testing disabled (define GIT_TEST_GIT_DAEMON to enable)"
+       test_done
+fi
+
+LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-'8121'}
+
+GIT_DAEMON_PID=
+GIT_DAEMON_DOCUMENT_ROOT_PATH="$PWD"/repo
+GIT_DAEMON_URL=git://127.0.0.1:$LIB_GIT_DAEMON_PORT
+
+start_git_daemon() {
+       if test -n "$GIT_DAEMON_PID"
+       then
+               error "start_git_daemon already called"
+       fi
+
+       mkdir -p "$GIT_DAEMON_DOCUMENT_ROOT_PATH"
+
+       trap 'code=$?; stop_git_daemon; (exit $code); die' EXIT
+
+       say >&3 "Starting git daemon ..."
+       mkfifo git_daemon_output
+       git daemon --listen=127.0.0.1 --port="$LIB_GIT_DAEMON_PORT" \
+               --reuseaddr --verbose \
+               --base-path="$GIT_DAEMON_DOCUMENT_ROOT_PATH" \
+               "$@" "$GIT_DAEMON_DOCUMENT_ROOT_PATH" \
+               >&3 2>git_daemon_output &
+       GIT_DAEMON_PID=$!
+       {
+               read line
+               echo >&4 "$line"
+               cat >&4 &
+
+               # Check expected output
+               if test x"$(expr "$line" : "\[[0-9]*\] \(.*\)")" != x"Ready to rumble"
+               then
+                       kill "$GIT_DAEMON_PID"
+                       wait "$GIT_DAEMON_PID"
+                       trap 'die' EXIT
+                       error "git daemon failed to start"
+               fi
+       } <git_daemon_output
+}
+
+stop_git_daemon() {
+       if test -z "$GIT_DAEMON_PID"
+       then
+               return
+       fi
+
+       trap 'die' EXIT
+
+       # kill git-daemon child of git
+       say >&3 "Stopping git daemon ..."
+       kill "$GIT_DAEMON_PID"
+       wait "$GIT_DAEMON_PID" >&3 2>&4
+       ret=$?
+       # expect exit with status 143 = 128+15 for signal TERM=15
+       if test $ret -ne 143
+       then
+               error "git daemon exited with status: $ret"
+       fi
+       GIT_DAEMON_PID=
+       rm -f git_daemon_output
+}
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 dbb2623d930e433111f7e125749f5f1071e9ab3c..51f3045ba4bde549b06ea669b1877f0e2db7c401 100755 (executable)
@@ -159,6 +159,16 @@ test_expect_success 'relative paths' '
        (cd b && attr_check ../a/b/g a/b/g)
 '
 
+test_expect_success 'prefixes are not confused with leading directories' '
+       attr_check a_plus/g unspecified &&
+       cat >expect <<-\EOF &&
+       a/g: test: a/g
+       a_plus/g: test: unspecified
+       EOF
+       git check-attr test a/g a_plus/g >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'core.attributesfile' '
        attr_check global unspecified &&
        git config core.attributesfile "$HOME/global-gitattributes" &&
diff --git a/t/t0080-vcs-svn.sh b/t/t0080-vcs-svn.sh
deleted file mode 100755 (executable)
index 99a314b..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-#!/bin/sh
-
-test_description='check infrastructure for svn importer'
-
-. ./test-lib.sh
-uint32_max=4294967295
-
-test_expect_success 'obj pool: store data' '
-       cat <<-\EOF >expected &&
-       0
-       1
-       EOF
-
-       test-obj-pool <<-\EOF >actual &&
-       alloc one 16
-       set one 13
-       test one 13
-       reset one
-       EOF
-       test_cmp expected actual
-'
-
-test_expect_success 'obj pool: NULL is offset ~0' '
-       echo "$uint32_max" >expected &&
-       echo null one | test-obj-pool >actual &&
-       test_cmp expected actual
-'
-
-test_expect_success 'obj pool: out-of-bounds access' '
-       cat <<-EOF >expected &&
-       0
-       0
-       $uint32_max
-       $uint32_max
-       16
-       20
-       $uint32_max
-       EOF
-
-       test-obj-pool <<-\EOF >actual &&
-       alloc one 16
-       alloc two 16
-       offset one 20
-       offset two 20
-       alloc one 5
-       offset one 20
-       free one 1
-       offset one 20
-       reset one
-       reset two
-       EOF
-       test_cmp expected actual
-'
-
-test_expect_success 'obj pool: high-water mark' '
-       cat <<-\EOF >expected &&
-       0
-       0
-       10
-       20
-       20
-       20
-       EOF
-
-       test-obj-pool <<-\EOF >actual &&
-       alloc one 10
-       committed one
-       alloc one 10
-       commit one
-       committed one
-       alloc one 10
-       free one 20
-       committed one
-       reset one
-       EOF
-       test_cmp expected actual
-'
-
-test_expect_success 'string pool' '
-       echo a does not equal b >expected.differ &&
-       echo a equals a >expected.match &&
-       echo equals equals equals >expected.matchmore &&
-
-       test-string-pool "a,--b" >actual.differ &&
-       test-string-pool "a,a" >actual.match &&
-       test-string-pool "equals-equals" >actual.matchmore &&
-       test_must_fail test-string-pool a,a,a &&
-       test_must_fail test-string-pool a &&
-
-       test_cmp expected.differ actual.differ &&
-       test_cmp expected.match actual.match &&
-       test_cmp expected.matchmore actual.matchmore
-'
-
-test_expect_success 'treap sort' '
-       cat <<-\EOF >unsorted &&
-       68
-       12
-       13
-       13
-       68
-       13
-       13
-       21
-       10
-       11
-       12
-       13
-       13
-       EOF
-       sort unsorted >expected &&
-
-       test-treap <unsorted >actual &&
-       test_cmp expected actual
-'
-
-test_done
diff --git a/t/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..8621ab0
--- /dev/null
@@ -0,0 +1,275 @@
+#!/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: $*"
+       while IFS== read key value; do
+               echo >&2 "$whoami: $key=$value"
+               eval "$key=$value"
+       done
+       EOF
+
+       write_script git-credential-useless <<-\EOF &&
+       . ./dump
+       exit 0
+       EOF
+
+       write_script git-credential-verbatim <<-\EOF &&
+       user=$1; shift
+       pass=$1; shift
+       . ./dump
+       test -z "$user" || echo username=$user
+       test -z "$pass" || echo password=$pass
+       EOF
+
+       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 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 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
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 51caff047b0da1a6d1df7fa651b3a8c31e8ae3e2..0690e0edf4e758200d4febb1c7837b5c7059add6 100755 (executable)
@@ -38,7 +38,7 @@ cat > expect << EOF
        WhatEver = Second
 EOF
 test_expect_success 'similar section' '
-       git config Cores.WhatEver Second
+       git config Cores.WhatEver Second &&
        test_cmp expect .git/config
 '
 
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 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
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 58a329961e5c269e1b035558db890c9e30375147..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
+       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 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 bc73c2099b5069ab136a1d93aef5a8ab4a0dad1f..dd1acebd88070b75bbbfd07048b5a255aa00e0f3 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,7 +19,7 @@ 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_might_fail git branch --help </dev/null >/dev/null 2>/dev/null &&
      test_path_is_missing .git/refs/heads/--help
 '
 
@@ -88,7 +85,7 @@ test_expect_success \
 test_expect_success \
     'git branch -m n/n n should work' \
        'git branch -l n/n &&
-        git branch -m n/n 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' '
@@ -115,6 +112,22 @@ 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 &&
@@ -604,4 +617,40 @@ 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_done
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 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 &&
index 2c4c1c851dcbb1cd38edc2a2620e2b04e36f7192..97f371070011e2023552d9fe941dc0a406f6d09f 100755 (executable)
@@ -2,6 +2,7 @@
 
 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
@@ -13,6 +14,9 @@ test_description='Test cherry-pick continuation features
 
 . ./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" &&
@@ -27,6 +31,7 @@ test_cmp_rev () {
 }
 
 test_expect_success setup '
+       git config advice.detachedhead false
        echo unrelated >unrelated &&
        git add unrelated &&
        test_commit initial foo a &&
@@ -35,22 +40,34 @@ test_expect_success setup '
        test_commit picked foo c &&
        test_commit anotherpick foo d &&
        test_commit yetanotherpick foo e &&
-       git config advice.detachedhead false
-
+       pristine_detach initial &&
+       test_commit conflicting unrelated
 '
 
 test_expect_success 'cherry-pick persists data on failure' '
        pristine_detach initial &&
-       test_must_fail git cherry-pick -s base..anotherpick &&
+       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_must_fail git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours base..anotherpick &&
+       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 &&
@@ -90,7 +107,7 @@ test_expect_success '--abort requires cherry-pick in progress' '
 
 test_expect_success '--quit cleans up sequencer state' '
        pristine_detach initial &&
-       test_must_fail git cherry-pick base..picked &&
+       test_expect_code 1 git cherry-pick base..picked &&
        git cherry-pick --quit &&
        test_path_is_missing .git/sequencer
 '
@@ -104,7 +121,7 @@ test_expect_success '--quit keeps HEAD and conflicted index intact' '
        :000000 100644 OBJID OBJID A    foo
        :000000 100644 OBJID OBJID A    unrelated
        EOF
-       test_must_fail git cherry-pick base..picked &&
+       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 &&
@@ -118,7 +135,7 @@ test_expect_success '--quit keeps HEAD and conflicted index intact' '
 
 test_expect_success '--abort to cancel multiple cherry-pick' '
        pristine_detach initial &&
-       test_must_fail git cherry-pick base..anotherpick &&
+       test_expect_code 1 git cherry-pick base..anotherpick &&
        git cherry-pick --abort &&
        test_path_is_missing .git/sequencer &&
        test_cmp_rev initial HEAD &&
@@ -128,7 +145,7 @@ test_expect_success '--abort to cancel multiple cherry-pick' '
 
 test_expect_success '--abort to cancel single cherry-pick' '
        pristine_detach initial &&
-       test_must_fail git cherry-pick picked &&
+       test_expect_code 1 git cherry-pick picked &&
        git cherry-pick --abort &&
        test_path_is_missing .git/sequencer &&
        test_cmp_rev initial HEAD &&
@@ -138,7 +155,7 @@ test_expect_success '--abort to cancel single cherry-pick' '
 
 test_expect_success 'cherry-pick --abort to cancel multiple revert' '
        pristine_detach anotherpick &&
-       test_must_fail git revert base..picked &&
+       test_expect_code 1 git revert base..picked &&
        git cherry-pick --abort &&
        test_path_is_missing .git/sequencer &&
        test_cmp_rev anotherpick HEAD &&
@@ -148,7 +165,7 @@ test_expect_success 'cherry-pick --abort to cancel multiple revert' '
 
 test_expect_success 'revert --abort works, too' '
        pristine_detach anotherpick &&
-       test_must_fail git revert base..picked &&
+       test_expect_code 1 git revert base..picked &&
        git revert --abort &&
        test_path_is_missing .git/sequencer &&
        test_cmp_rev anotherpick HEAD
@@ -156,7 +173,7 @@ test_expect_success 'revert --abort works, too' '
 
 test_expect_success '--abort to cancel single revert' '
        pristine_detach anotherpick &&
-       test_must_fail git revert picked &&
+       test_expect_code 1 git revert picked &&
        git revert --abort &&
        test_path_is_missing .git/sequencer &&
        test_cmp_rev anotherpick HEAD &&
@@ -167,7 +184,7 @@ test_expect_success '--abort to cancel single revert' '
 test_expect_success '--abort keeps unrelated change, easy case' '
        pristine_detach unrelatedpick &&
        echo changed >expect &&
-       test_must_fail git cherry-pick picked..yetanotherpick &&
+       test_expect_code 1 git cherry-pick picked..yetanotherpick &&
        echo changed >unrelated &&
        git cherry-pick --abort &&
        test_cmp expect unrelated
@@ -176,7 +193,7 @@ test_expect_success '--abort keeps unrelated change, easy case' '
 test_expect_success '--abort refuses to clobber unrelated change, harder case' '
        pristine_detach initial &&
        echo changed >expect &&
-       test_must_fail git cherry-pick base..anotherpick &&
+       test_expect_code 1 git cherry-pick base..anotherpick &&
        echo changed >unrelated &&
        test_must_fail git cherry-pick --abort &&
        test_cmp expect unrelated &&
@@ -189,10 +206,10 @@ test_expect_success '--abort refuses to clobber unrelated change, harder case' '
        test_cmp_rev initial HEAD
 '
 
-test_expect_success 'cherry-pick cleans up sequencer state when one commit is left' '
+test_expect_success 'cherry-pick still writes sequencer state when one commit is left' '
        pristine_detach initial &&
-       test_must_fail git cherry-pick base..picked &&
-       test_path_is_missing .git/sequencer &&
+       test_expect_code 1 git cherry-pick base..picked &&
+       test_path_is_dir .git/sequencer &&
        echo "resolved" >foo &&
        git add foo &&
        git commit &&
@@ -213,9 +230,9 @@ test_expect_success 'cherry-pick cleans up sequencer state when one commit is le
        test_cmp expect actual
 '
 
-test_expect_failure '--abort after last commit in sequence' '
+test_expect_success '--abort after last commit in sequence' '
        pristine_detach initial &&
-       test_must_fail git cherry-pick base..picked &&
+       test_expect_code 1 git cherry-pick base..picked &&
        git cherry-pick --abort &&
        test_path_is_missing .git/sequencer &&
        test_cmp_rev initial HEAD &&
@@ -225,27 +242,86 @@ test_expect_failure '--abort after last commit in sequence' '
 
 test_expect_success 'cherry-pick does not implicitly stomp an existing operation' '
        pristine_detach initial &&
-       test_must_fail git cherry-pick base..anotherpick &&
+       test_expect_code 1 git cherry-pick base..anotherpick &&
        test-chmtime -v +0 .git/sequencer >expect &&
-       test_must_fail git cherry-pick unrelatedpick &&
+       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_must_fail git cherry-pick --continue
+       test_expect_code 128 git cherry-pick --continue
 '
 
 test_expect_success '--continue complains when there are unresolved conflicts' '
        pristine_detach initial &&
-       test_must_fail git cherry-pick base..anotherpick &&
-       test_must_fail git cherry-pick --continue
+       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 continues after conflicts are resolved' '
+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 &&
@@ -270,9 +346,32 @@ test_expect_success '--continue continues after conflicts are resolved' '
        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_must_fail git cherry-pick -x base..anotherpick &&
+       test_expect_code 1 git cherry-pick -x base..anotherpick &&
        echo "c" >foo &&
        git add foo &&
        git commit &&
@@ -288,9 +387,32 @@ test_expect_success '--continue respects opts' '
        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_must_fail git cherry-pick --signoff base..anotherpick &&
+       test_expect_code 1 git cherry-pick --signoff base..anotherpick &&
        echo "c" >foo &&
        git add foo &&
        git commit &&
@@ -306,26 +428,93 @@ test_expect_success '--signoff is not automatically propagated to resolved confl
        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_must_fail git cherry-pick base..anotherpick &&
+       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_must_fail git cherry-pick --continue
+       test_expect_code 128 git cherry-pick --continue
 '
 
 test_expect_success 'malformed instruction sheet 2' '
        pristine_detach initial &&
-       test_must_fail git cherry-pick base..anotherpick &&
+       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_must_fail git cherry-pick --continue
+       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 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 ef44fb22601b03be78d8d5f5eaa858ca240ec4da..a5e7e6b2ba3138d06c400e965decf1389da3274a 100755 (executable)
@@ -17,6 +17,7 @@ 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 &&
@@ -35,6 +36,13 @@ test_expect_success 'stash save --include-untracked cleaned the untracked files'
 '
 
 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
@@ -51,14 +59,16 @@ index 0000000..5a72eb2
 +untracked
 EOF
 cat > expect.lstree <<EOF
+HEAD
 file2
 untracked
 EOF
 
 test_expect_success 'stash save --include-untracked stashed the untracked files' '
-       test "!" -f file2 &&
-       test ! -e untracked &&
-       git diff HEAD stash^3 -- file2 untracked >actual &&
+       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
@@ -75,6 +85,7 @@ git clean --force --quiet
 
 cat > expect <<EOF
  M file
+?? HEAD
 ?? actual
 ?? expect
 ?? file2
@@ -116,10 +127,12 @@ test_expect_success 'stash save --include-untracked dirty index got stashed' '
 
 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' '
@@ -133,7 +146,7 @@ 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
@@ -155,14 +168,14 @@ test_expect_success 'stash save --include-untracked respects .gitignore' '
 test_expect_success 'stash save -u can stash with only untracked files different' '
        echo 4 > file4 &&
        git stash -u &&
-       test "!" -f file4
+       test_path_is_missing file4
 '
 
 test_expect_success 'stash save --all does not respect .gitignore' '
        git stash -a &&
-       test "!" -f ignored &&
-       test "!" -e ignored.d &&
-       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' '
index fbc8cd8f05f4debeb30b935c1b1db86e94e49f0e..af5134b70c6b4898aa652907d58191d45aa30ac4 100755 (executable)
@@ -47,6 +47,14 @@ test_expect_success \
     'git diff-index --cached $tree -- path1/ >current &&
      compare_diff_raw current expected'
 
+cat >expected <<\EOF
+:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M     path1/file1
+EOF
+test_expect_success \
+    '"*file1" should show path1/file1' \
+    'git diff-index --cached $tree -- "*file1" >current &&
+     compare_diff_raw current expected'
+
 cat >expected <<\EOF
 :100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M     file0
 EOF
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 9059bcd69eec7a718b6017356c82ec163875c042..cc3db1304ef202a376e84360986568bb7408c701 100755 (executable)
@@ -103,7 +103,7 @@ test_expect_success 'another test, with -w --ignore-space-at-eol' 'test_cmp expe
 git diff -w -b --ignore-space-at-eol > out
 test_expect_success 'another test, with -w -b --ignore-space-at-eol' 'test_cmp expect out'
 
-tr 'Q' '\015' << EOF > expect
+tr 'Q_' '\015 ' << EOF > expect
 diff --git a/x b/x
 index d99af23..8b32fb5 100644
 --- a/x
@@ -111,19 +111,19 @@ index d99af23..8b32fb5 100644
 @@ -1,6 +1,6 @@
 -whitespace at beginning
 +      whitespace at beginning
- whitespace change
+ whitespace     change
 -whitespace in the middle
 +white space in the middle
- whitespace at end
+ whitespace at end__
  unchanged line
- CR at endQ
+ CR at end
 EOF
 git diff -b > out
 test_expect_success 'another test, with -b' 'test_cmp expect out'
 git diff -b --ignore-space-at-eol > out
 test_expect_success 'another test, with -b --ignore-space-at-eol' 'test_cmp expect out'
 
-tr 'Q' '\015' << EOF > expect
+tr 'Q_' '\015 ' << EOF > expect
 diff --git a/x b/x
 index d99af23..8b32fb5 100644
 --- a/x
@@ -135,9 +135,9 @@ index d99af23..8b32fb5 100644
 +      whitespace at beginning
 +whitespace     change
 +white space in the middle
- whitespace at end
+ whitespace at end__
  unchanged line
- CR at endQ
+ CR at end
 EOF
 git diff --ignore-space-at-eol > out
 test_expect_success 'another test, with --ignore-space-at-eol' 'test_cmp expect out'
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
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..f1b60b8560ce1b27497d9e0e08af007159e04d1d 100755 (executable)
@@ -136,7 +136,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
 '
 
@@ -237,7 +237,7 @@ test_expect_success 'am stays in branch' '
 
 test_expect_success 'am --signoff does not add Signed-off-by: line if already there' '
        git format-patch --stdout HEAD^ >patch3 &&
-       sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2," patch3 >patch4 &&
+       sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," patch3 >patch4 &&
        rm -fr .git/rebase-apply &&
        git reset --hard &&
        git checkout HEAD^ &&
@@ -259,7 +259,17 @@ test_expect_success 'am --keep really keeps the subject' '
        git am --keep patch4 &&
        ! test -d .git/rebase-apply &&
        git cat-file commit HEAD >actual &&
-       grep "Re: Re: Re: \[PATCH 1/5 v2\] third" actual
+       grep "Re: Re: Re: \[PATCH 1/5 v2\] \[foo\] third" actual
+'
+
+test_expect_success 'am --keep-non-patch really keeps the non-patch part' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout HEAD^ &&
+       git am --keep-non-patch patch4 &&
+       ! test -d .git/rebase-apply &&
+       git cat-file commit HEAD >actual &&
+       grep "^\[foo\] third" actual
 '
 
 test_expect_success 'am -3 falls back to 3-way merge' '
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 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 602806d09cda72c7bf0f407da35b2fb859404bd7..d9d856b87b2a896d4f80a3e62e6d1925b680a680 100755 (executable)
@@ -38,6 +38,10 @@ test_expect_success \
     'pack without delta' \
     'packname_1=$(git pack-objects --window=0 test-1 <obj-list)'
 
+test_expect_success \
+    'pack-objects with bogus arguments' \
+    'test_must_fail git pack-objects --window=0 test-1 blah blah <obj-list'
+
 rm -fr .git2
 mkdir .git2
 
index f8fa92446cfc46309468b4ecf142b74b1a812985..fe82025d4a0dbae0cee3d0c3943617ede5b5c7a5 100755 (executable)
@@ -73,6 +73,10 @@ test_expect_success 'index-pack --verify on index version 2' '
        git index-pack --verify "test-2-${pack2}.pack"
 '
 
+test_expect_success \
+    'pack-objects --index-version=2, is not accepted' \
+    'test_must_fail git pack-objects --index-version=2, test-3 <obj-list'
+
 test_expect_success \
     'index v2: force some 64-bit offsets with pack-objects' \
     'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)'
index bafcca765e4fea92f430e7127506a2370e062ec7..ce51692bb2b9ae221d11458a01ab8ef669f24659 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,12 +110,23 @@ 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 'single branch clone' '
+       git clone --single-branch "file://$(pwd)/." singlebranch
+'
+
+test_expect_success 'single branch object count' '
+       GIT_DIR=singlebranch/.git git count-objects -v |
+               grep "^in-pack:" > count.singlebranch &&
+       echo "in-pack: 198" >expected &&
+       test_cmp expected count.singlebranch
+'
 
 test_expect_success 'clone shallow' '
-       git clone --depth 2 "file://$(pwd)/." shallow
+       git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow
 '
 
 test_expect_success 'clone shallow object count' '
@@ -248,4 +259,71 @@ test_expect_success 'clone shallow object count' '
        grep "^count: 52" count.shallow
 '
 
+test_expect_success 'clone shallow without --no-single-branch' '
+       git clone --depth 1 "file://$(pwd)/." shallow2
+'
+
+test_expect_success 'clone shallow object count' '
+       (
+               cd shallow2 &&
+               git count-objects -v
+       ) > count.shallow2 &&
+       grep "^in-pack: 6" count.shallow2
+'
+
+test_expect_success 'clone shallow with --branch' '
+       git clone --depth 1 --branch A "file://$(pwd)/." shallow3
+'
+
+test_expect_success 'clone shallow object count' '
+       echo "in-pack: 12" > count3.expected &&
+       GIT_DIR=shallow3/.git git count-objects -v |
+               grep "^in-pack" > count3.actual &&
+       test_cmp count3.expected count3.actual
+'
+
+test_expect_success 'clone shallow with detached HEAD' '
+       git checkout HEAD^ &&
+       git clone --depth 1 "file://$(pwd)/." shallow5 &&
+       git checkout - &&
+       GIT_DIR=shallow5/.git git rev-parse HEAD >actual &&
+       git rev-parse HEAD^ >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'shallow clone pulling tags' '
+       git tag -a -m A TAGA1 A &&
+       git tag -a -m B TAGB1 B &&
+       git tag TAGA2 A &&
+       git tag TAGB2 B &&
+       git clone --depth 1 "file://$(pwd)/." shallow6 &&
+
+       cat >taglist.expected <<\EOF &&
+TAGB1
+TAGB2
+EOF
+       GIT_DIR=shallow6/.git git tag -l >taglist.actual &&
+       test_cmp taglist.expected taglist.actual &&
+
+       echo "in-pack: 7" > count6.expected &&
+       GIT_DIR=shallow6/.git git count-objects -v |
+               grep "^in-pack" > count6.actual &&
+       test_cmp count6.expected count6.actual
+'
+
+test_expect_success 'shallow cloning single tag' '
+       git clone --depth 1 --branch=TAGB1 "file://$(pwd)/." shallow7 &&
+       cat >taglist.expected <<\EOF &&
+TAGB1
+TAGB2
+EOF
+       GIT_DIR=shallow7/.git git tag -l >taglist.actual &&
+       test_cmp taglist.expected taglist.actual &&
+
+       echo "in-pack: 7" > count7.expected &&
+       GIT_DIR=shallow7/.git git count-objects -v |
+               grep "^in-pack" > count7.actual &&
+       test_cmp count7.expected count7.actual
+'
+
 test_done
index e0af4c4e62c40a563f692361944cc58e2c8e38e2..79ee91313059bf2e6efc7fe3c15c6fd56e8fc671 100755 (executable)
@@ -70,8 +70,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 +166,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 +175,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' '
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 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..9ee52cfc458b2914ee82da6ac1117a2b1ae45a64 100755 (executable)
@@ -108,4 +108,11 @@ test_expect_failure TTY 'push --no-progress suppresses progress' '
        ! 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..d66ed2450854c3091105a43bb2aa0f831140ed1f 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:5541/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 311a33ca84f693b9410bab9e282ad0ebcae87441..e5e6b8f643206c2d4fd01e3ad71ca50a43f3da19 100755 (executable)
@@ -49,40 +49,84 @@ test_expect_success 'setup askpass helpers' '
        EOF
        chmod +x askpass &&
        GIT_ASKPASS="$PWD/askpass" &&
-       export GIT_ASKPASS &&
-       >askpass-expect-none &&
-       echo "askpass: Password for '\''$HTTPD_DEST'\'': " >askpass-expect-pass &&
-       { echo "askpass: Username for '\''$HTTPD_DEST'\'': " &&
-         cat askpass-expect-pass
-       } >askpass-expect-both
-'
+       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 &&
-       test_cmp askpass-expect-both askpass-query
+       expect_askpass both wrong
 '
 
 test_expect_success 'http auth can use user/pass in URL' '
        >askpass-query &&
-       echo wrong >askpass-reponse &&
+       echo wrong >askpass-response &&
        git clone "$HTTPD_URL_USER_PASS/auth/repo.git" clone-auth-none &&
-       test_cmp askpass-expect-none askpass-query
+       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 &&
-       test_cmp askpass-expect-pass askpass-query
+       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 &&
-       test_cmp askpass-expect-both askpass-query
+       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' '
@@ -118,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
diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh
new file mode 100755 (executable)
index 0000000..7cbc999
--- /dev/null
@@ -0,0 +1,148 @@
+#!/bin/sh
+
+test_description='test fetching over git protocol'
+. ./test-lib.sh
+
+LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-5570}
+. "$TEST_DIRECTORY"/lib-git-daemon.sh
+start_git_daemon
+
+test_expect_success 'setup repository' '
+       echo content >file &&
+       git add file &&
+       git commit -m one
+'
+
+test_expect_success 'create git-accessible bare repository' '
+       mkdir "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git" &&
+       (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git" &&
+        git --bare init &&
+        : >git-daemon-export-ok
+       ) &&
+       git remote add public "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git" &&
+       git push public master:master
+'
+
+test_expect_success 'clone git repository' '
+       git clone "$GIT_DAEMON_URL/repo.git" clone &&
+       test_cmp file clone/file
+'
+
+test_expect_success 'fetch changes via git protocol' '
+       echo content >>file &&
+       git commit -a -m two &&
+       git push public &&
+       (cd clone && git pull) &&
+       test_cmp file clone/file
+'
+
+test_expect_failure 'remote detects correct HEAD' '
+       git push public master:other &&
+       (cd clone &&
+        git remote set-head -d origin &&
+        git remote set-head -a origin &&
+        git symbolic-ref refs/remotes/origin/HEAD > output &&
+        echo refs/remotes/origin/master > expect &&
+        test_cmp expect output
+       )
+'
+
+test_expect_success 'prepare pack objects' '
+       cp -R "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo.git "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git &&
+       (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git &&
+        git --bare repack -a -d
+       )
+'
+
+test_expect_success 'fetch notices corrupt pack' '
+       cp -R "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
+       (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
+        p=`ls objects/pack/pack-*.pack` &&
+        chmod u+w $p &&
+        printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc
+       ) &&
+       mkdir repo_bad1.git &&
+       (cd repo_bad1.git &&
+        git --bare init &&
+        test_must_fail git --bare fetch "$GIT_DAEMON_URL/repo_bad1.git" &&
+        test 0 = `ls objects/pack/pack-*.pack | wc -l`
+       )
+'
+
+test_expect_success 'fetch notices corrupt idx' '
+       cp -R "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
+       (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
+        p=`ls objects/pack/pack-*.idx` &&
+        chmod u+w $p &&
+        printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc
+       ) &&
+       mkdir repo_bad2.git &&
+       (cd repo_bad2.git &&
+        git --bare init &&
+        test_must_fail git --bare fetch "$GIT_DAEMON_URL/repo_bad2.git" &&
+        test 0 = `ls objects/pack | wc -l`
+       )
+'
+
+test_remote_error()
+{
+       do_export=YesPlease
+       while test $# -gt 0
+       do
+               case $1 in
+               -x)
+                       shift
+                       chmod -x "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git"
+                       ;;
+               -n)
+                       shift
+                       do_export=
+                       ;;
+               *)
+                       break
+               esac
+       done
+
+       if test $# -ne 3
+       then
+               error "invalid number of arguments"
+       fi
+
+       cmd=$1
+       repo=$2
+       msg=$3
+
+       if test -x "$GIT_DAEMON_DOCUMENT_ROOT_PATH/$repo"
+       then
+               if test -n "$do_export"
+               then
+                       : >"$GIT_DAEMON_DOCUMENT_ROOT_PATH/$repo/git-daemon-export-ok"
+               else
+                       rm -f "$GIT_DAEMON_DOCUMENT_ROOT_PATH/$repo/git-daemon-export-ok"
+               fi
+       fi
+
+       test_must_fail git "$cmd" "$GIT_DAEMON_URL/$repo" 2>output &&
+       echo "fatal: remote error: $msg: /$repo" >expect &&
+       test_cmp expect output
+       ret=$?
+       chmod +x "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git"
+       (exit $ret)
+}
+
+msg="access denied or repository not exported"
+test_expect_success 'clone non-existent' "test_remote_error    clone nowhere.git '$msg'"
+test_expect_success 'push disabled'      "test_remote_error    push  repo.git    '$msg'"
+test_expect_success 'read access denied' "test_remote_error -x fetch repo.git    '$msg'"
+test_expect_success 'not exported'       "test_remote_error -n fetch repo.git    '$msg'"
+
+stop_git_daemon
+start_git_daemon --informative-errors
+
+test_expect_success 'clone non-existent' "test_remote_error    clone nowhere.git 'no such repository'"
+test_expect_success 'push disabled'      "test_remote_error    push  repo.git    'service not enabled'"
+test_expect_success 'read access denied' "test_remote_error -x fetch repo.git    'no such repository'"
+test_expect_success 'not exported'       "test_remote_error -n fetch repo.git    'repository not exported'"
+
+stop_git_daemon
+test_done
index 87ee01662c2fcfa58d8f9896fd43881e2e7ed7d6..67869b4813dd354f4376ead1470ecb0e58929302 100755 (executable)
@@ -9,10 +9,13 @@ test_expect_success setup '
        rm -fr .git &&
        test_create_repo src &&
        (
-               cd src
-               >file
-               git add file
-               git commit -m initial
+               cd src &&
+               >file &&
+               git add file &&
+               git commit -m initial &&
+               echo 1 >file &&
+               git add file &&
+               git commit -m updated
        )
 
 '
@@ -88,6 +91,26 @@ test_expect_success 'clone --mirror' '
 
 '
 
+test_expect_success 'clone --mirror with detached HEAD' '
+
+       ( cd src && git checkout HEAD^ && git rev-parse HEAD >../expected ) &&
+       git clone --mirror src mirror.detached &&
+       ( cd src && git checkout - ) &&
+       GIT_DIR=mirror.detached git rev-parse HEAD >actual &&
+       test_cmp expected actual
+
+'
+
+test_expect_success 'clone --bare with detached HEAD' '
+
+       ( cd src && git checkout HEAD^ && git rev-parse HEAD >../expected ) &&
+       git clone --bare src bare.detached &&
+       ( cd src && git checkout - ) &&
+       GIT_DIR=bare.detached git rev-parse HEAD >actual &&
+       test_cmp expected actual
+
+'
+
 test_expect_success 'clone --bare names the local repository <name>.git' '
 
        git clone --bare src &&
@@ -248,4 +271,13 @@ test_expect_success 'clone from original with relative alternate' '
        grep /src/\\.git/objects target-10/objects/info/alternates
 '
 
+test_expect_success 'clone checking out a tag' '
+       git clone --branch=some-tag src dst.tag &&
+       GIT_DIR=src/.git git rev-parse some-tag >expected &&
+       test_cmp expected dst.tag/.git/HEAD &&
+       GIT_DIR=dst.tag/.git git config remote.origin.fetch >fetch.actual &&
+       echo "+refs/heads/*:refs/remotes/origin/*" >fetch.expected &&
+       test_cmp fetch.expected fetch.actual
+'
+
 test_done
index c4c375ac042bbb7f58998c87d8c9277d2f5004a6..bbc4691bd7ef1e3633d4a66440211179fae42a84 100755 (executable)
@@ -52,13 +52,13 @@ test_cmp expected current'
 
 cd "$base_dir"
 
-rm -f "$U"
+rm -f "$U.D"
 
 test_expect_success 'cloning with reference (no -l -s)' \
-'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>"$U"'
+'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>"$U.D"'
 
 test_expect_success 'fetched no objects' \
-'! grep "^want" "$U"'
+'! grep "^want" "$U.D"'
 
 cd "$base_dir"
 
@@ -153,4 +153,32 @@ test_expect_success 'clone with reference from a tagged repository' '
        git clone --reference=A A I
 '
 
+test_expect_success 'prepare branched repository' '
+       git clone A J &&
+       (
+               cd J &&
+               git checkout -b other master^ &&
+               echo other >otherfile &&
+               git add otherfile &&
+               git commit -m other &&
+               git checkout master
+       )
+'
+
+rm -f "$U.K"
+
+test_expect_success 'fetch with incomplete alternates' '
+       git init K &&
+       echo "$base_dir/A/.git/objects" >K/.git/objects/info/alternates &&
+       (
+               cd K &&
+               git remote add J "file://$base_dir/J" &&
+               GIT_DEBUG_SEND_PACK=3 git fetch J 3>"$U.K"
+       ) &&
+       master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) &&
+       ! grep "^want $master_object" "$U.K" &&
+       tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) &&
+       ! grep "^want $tag_object" "$U.K"
+'
+
 test_done
index 728ccd88c3d69f06bff2c3593ddc325f9186402c..4ae127d106c4a8ada0cea928affeff933bf0dbaa 100755 (executable)
@@ -53,4 +53,10 @@ test_expect_failure 'bundle --stdin <rev-list options>' '
 
 '
 
+test_expect_success 'empty bundle file is rejected' '
+
+    >empty-bundle && test_must_fail git fetch empty-bundle
+
+'
+
 test_done
index f3f9a76056d25fa7d5e4823b1d05ebc1edff46cc..56be67e07e3c20f029a0bdeb2179e219ebc0bda1 100755 (executable)
@@ -57,12 +57,8 @@ test_expect_success 'clone -b does not munge remotes/origin/HEAD' '
        )
 '
 
-test_expect_success 'clone -b with bogus branch chooses HEAD' '
-       git clone -b bogus parent clone-bogus &&
-       (cd clone-bogus &&
-        check_HEAD master &&
-        check_file one
-       )
+test_expect_success 'clone -b with bogus branch' '
+       test_must_fail git clone -b bogus parent clone-bogus
 '
 
 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 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 e93ac73829f332cdbf53b05fcc611d4ea38c4c55..4ef79aabc47a4ef2e9def65253edf6bcd8ce91a3 100755 (executable)
@@ -586,6 +586,19 @@ test_expect_success \
        test_cmp expect actual
 '
 
+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:
 
 test_expect_success GPG \
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
diff --git a/t/t7106-reset-sequence.sh b/t/t7106-reset-sequence.sh
deleted file mode 100755 (executable)
index 83f7ea5..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/bin/sh
-
-test_description='Test interaction of reset --hard with sequencer
-
-  + 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
-
-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_expect_success setup '
-       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 &&
-       git config advice.detachedhead false
-
-'
-
-test_expect_success 'reset --hard cleans up sequencer state, providing one-level undo' '
-       pristine_detach initial &&
-       test_must_fail git cherry-pick base..anotherpick &&
-       test_path_is_dir .git/sequencer &&
-       git reset --hard &&
-       test_path_is_missing .git/sequencer &&
-       test_path_is_dir .git/sequencer-old &&
-       git reset --hard &&
-       test_path_is_missing .git/sequencer-old
-'
-
-test_expect_success 'cherry-pick --abort does not leave sequencer-old dir' '
-       pristine_detach initial &&
-       test_must_fail git cherry-pick base..anotherpick &&
-       git cherry-pick --abort &&
-       test_path_is_missing .git/sequencer &&
-       test_path_is_missing .git/sequencer-old
-'
-
-test_done
index 33b292b8a8e992c98774ad6e51270a9babc7e6de..5b97222c48a15dff018cb814ca14c59ed0ee91a2 100755 (executable)
@@ -611,4 +611,12 @@ test_expect_success 'submodule update places git-dir in superprojects git-dir re
        )
 '
 
+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 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
 
 '
 
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 3008e4e1210900be3bdc2e28dddd681ce8ec3551..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 () {
@@ -666,4 +671,28 @@ test_expect_success 'merge --no-ff --edit' '
        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 81263b78516cbe2949ad7e426a5e44af188cccdd..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 &&
        (
index 4ee42f12f0af6bc7e4b072350f88988b85e40cbb..c3c22f7764adc1542579dfddf1b47a687a93607a 100755 (executable)
@@ -10,7 +10,7 @@ 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
 
index 6f6175a8f7d9b0c6e6334f89ff21b74f067d6532..b7eed2489fa169aa1c6e5c98f59a0349ba8118fa 100755 (executable)
@@ -5,8 +5,27 @@ test_description='check svn dumpfile importer'
 . ./test-lib.sh
 
 reinit_git () {
+       if ! test_declared_prereq PIPE
+       then
+               echo >&4 "reinit_git: need to declare PIPE prerequisite"
+               return 127
+       fi
        rm -fr .git &&
-       git init
+       rm -f stream backflow &&
+       git init &&
+       mkfifo stream backflow
+}
+
+try_dump () {
+       input=$1 &&
+       maybe_fail_svnfe=${2:+test_$2} &&
+       maybe_fail_fi=${3:+test_$3} &&
+
+       {
+               $maybe_fail_svnfe test-svn-fe "$input" >stream 3<backflow &
+       } &&
+       $maybe_fail_fi git fast-import --cat-blob-fd=3 <stream 3>backflow &&
+       wait $!
 }
 
 properties () {
@@ -35,21 +54,27 @@ text_no_props () {
 
 >empty
 
-test_expect_success 'empty dump' '
+test_expect_success 'setup: have pipes?' '
+       rm -f frob &&
+       if mkfifo frob
+       then
+               test_set_prereq PIPE
+       fi
+'
+
+test_expect_success PIPE 'empty dump' '
        reinit_git &&
        echo "SVN-fs-dump-format-version: 2" >input &&
-       test-svn-fe input >stream &&
-       git fast-import <stream
+       try_dump input
 '
 
-test_expect_success 'v4 dumps not supported' '
+test_expect_success PIPE 'v4 dumps not supported' '
        reinit_git &&
        echo "SVN-fs-dump-format-version: 4" >v4.dump &&
-       test_must_fail test-svn-fe v4.dump >stream &&
-       test_cmp empty stream
+       try_dump v4.dump must_fail
 '
 
-test_expect_failure 'empty revision' '
+test_expect_failure PIPE 'empty revision' '
        reinit_git &&
        printf "rev <nobody, nobody@local>: %s\n" "" "" >expect &&
        cat >emptyrev.dump <<-\EOF &&
@@ -64,13 +89,12 @@ test_expect_failure 'empty revision' '
        Content-length: 0
 
        EOF
-       test-svn-fe emptyrev.dump >stream &&
-       git fast-import <stream &&
+       try_dump emptyrev.dump &&
        git log -p --format="rev <%an, %ae>: %s" HEAD >actual &&
        test_cmp expect actual
 '
 
-test_expect_success 'empty properties' '
+test_expect_success PIPE 'empty properties' '
        reinit_git &&
        printf "rev <nobody, nobody@local>: %s\n" "" "" >expect &&
        cat >emptyprop.dump <<-\EOF &&
@@ -88,13 +112,12 @@ test_expect_success 'empty properties' '
 
        PROPS-END
        EOF
-       test-svn-fe emptyprop.dump >stream &&
-       git fast-import <stream &&
+       try_dump emptyprop.dump &&
        git log -p --format="rev <%an, %ae>: %s" HEAD >actual &&
        test_cmp expect actual
 '
 
-test_expect_success 'author name and commit message' '
+test_expect_success PIPE 'author name and commit message' '
        reinit_git &&
        echo "<author@example.com, author@example.com@local>" >expect.author &&
        cat >message <<-\EOF &&
@@ -121,15 +144,14 @@ test_expect_success 'author name and commit message' '
                echo &&
                cat props
        } >log.dump &&
-       test-svn-fe log.dump >stream &&
-       git fast-import <stream &&
+       try_dump log.dump &&
        git log -p --format="%B" HEAD >actual.log &&
        git log --format="<%an, %ae>" >actual.author &&
        test_cmp message actual.log &&
        test_cmp expect.author actual.author
 '
 
-test_expect_success 'unsupported properties are ignored' '
+test_expect_success PIPE 'unsupported properties are ignored' '
        reinit_git &&
        echo author >expect &&
        cat >extraprop.dump <<-\EOF &&
@@ -149,13 +171,12 @@ test_expect_success 'unsupported properties are ignored' '
        author
        PROPS-END
        EOF
-       test-svn-fe extraprop.dump >stream &&
-       git fast-import <stream &&
+       try_dump extraprop.dump &&
        git log -p --format=%an HEAD >actual &&
        test_cmp expect actual
 '
 
-test_expect_failure 'timestamp and empty file' '
+test_expect_failure PIPE 'timestamp and empty file' '
        echo author@example.com >expect.author &&
        echo 1999-01-01 >expect.date &&
        echo file >expect.files &&
@@ -186,8 +207,7 @@ test_expect_failure 'timestamp and empty file' '
 
                EOF
        } >emptyfile.dump &&
-       test-svn-fe emptyfile.dump >stream &&
-       git fast-import <stream &&
+       try_dump emptyfile.dump &&
        git log --format=%an HEAD >actual.author &&
        git log --date=short --format=%ad HEAD >actual.date &&
        git ls-tree -r --name-only HEAD >actual.files &&
@@ -198,7 +218,7 @@ test_expect_failure 'timestamp and empty file' '
        test_cmp empty file
 '
 
-test_expect_success 'directory with files' '
+test_expect_success PIPE 'directory with files' '
        reinit_git &&
        printf "%s\n" directory/file1 directory/file2 >expect.files &&
        echo hi >hi &&
@@ -242,8 +262,7 @@ test_expect_success 'directory with files' '
                EOF
                text_no_props hi
        } >directory.dump &&
-       test-svn-fe directory.dump >stream &&
-       git fast-import <stream &&
+       try_dump directory.dump &&
 
        git ls-tree -r --name-only HEAD >actual.files &&
        git checkout HEAD directory &&
@@ -252,7 +271,107 @@ test_expect_success 'directory with files' '
        test_cmp hi directory/file2
 '
 
-test_expect_success 'node without action' '
+test_expect_success PIPE 'branch name with backslash' '
+       reinit_git &&
+       sort <<-\EOF >expect.branch-files &&
+       trunk/file1
+       trunk/file2
+       "branches/UpdateFOPto094\\/file1"
+       "branches/UpdateFOPto094\\/file2"
+       EOF
+
+       echo hi >hi &&
+       echo hello >hello &&
+       {
+               properties \
+                       svn:author author@example.com \
+                       svn:date "1999-02-02T00:01:02.000000Z" \
+                       svn:log "add directory with some files in it" &&
+               echo PROPS-END
+       } >props.setup &&
+       {
+               properties \
+                       svn:author brancher@example.com \
+                       svn:date "2007-12-06T21:38:34.000000Z" \
+                       svn:log "Updating fop to .94 and adjust fo-stylesheets" &&
+               echo PROPS-END
+       } >props.branch &&
+       {
+               cat <<-EOF &&
+               SVN-fs-dump-format-version: 3
+
+               Revision-number: 1
+               EOF
+               echo Prop-content-length: $(wc -c <props.setup) &&
+               echo Content-length: $(wc -c <props.setup) &&
+               echo &&
+               cat props.setup &&
+               cat <<-\EOF &&
+
+               Node-path: trunk
+               Node-kind: dir
+               Node-action: add
+               Prop-content-length: 10
+               Content-length: 10
+
+               PROPS-END
+
+               Node-path: branches
+               Node-kind: dir
+               Node-action: add
+               Prop-content-length: 10
+               Content-length: 10
+
+               PROPS-END
+
+               Node-path: trunk/file1
+               Node-kind: file
+               Node-action: add
+               EOF
+               text_no_props hello &&
+               cat <<-\EOF &&
+               Node-path: trunk/file2
+               Node-kind: file
+               Node-action: add
+               EOF
+               text_no_props hi &&
+               cat <<-\EOF &&
+
+               Revision-number: 2
+               EOF
+               echo Prop-content-length: $(wc -c <props.branch) &&
+               echo Content-length: $(wc -c <props.branch) &&
+               echo &&
+               cat props.branch &&
+               cat <<-\EOF
+
+               Node-path: branches/UpdateFOPto094\
+               Node-kind: dir
+               Node-action: add
+               Node-copyfrom-rev: 1
+               Node-copyfrom-path: trunk
+
+               Node-kind: dir
+               Node-action: add
+               Prop-content-length: 34
+               Content-length: 34
+
+               K 13
+               svn:mergeinfo
+               V 0
+
+               PROPS-END
+               EOF
+       } >branch.dump &&
+       try_dump branch.dump &&
+
+       git ls-tree -r --name-only HEAD |
+       sort >actual.branch-files &&
+       test_cmp expect.branch-files actual.branch-files
+'
+
+test_expect_success PIPE 'node without action' '
+       reinit_git &&
        cat >inaction.dump <<-\EOF &&
        SVN-fs-dump-format-version: 3
 
@@ -269,10 +388,11 @@ test_expect_success 'node without action' '
 
        PROPS-END
        EOF
-       test_must_fail test-svn-fe inaction.dump
+       try_dump inaction.dump must_fail
 '
 
-test_expect_success 'action: add node without text' '
+test_expect_success PIPE 'action: add node without text' '
+       reinit_git &&
        cat >textless.dump <<-\EOF &&
        SVN-fs-dump-format-version: 3
 
@@ -290,10 +410,10 @@ test_expect_success 'action: add node without text' '
 
        PROPS-END
        EOF
-       test_must_fail test-svn-fe textless.dump
+       try_dump textless.dump must_fail
 '
 
-test_expect_failure 'change file mode but keep old content' '
+test_expect_failure PIPE 'change file mode but keep old content' '
        reinit_git &&
        cat >expect <<-\EOF &&
        OBJID
@@ -356,8 +476,7 @@ test_expect_failure 'change file mode but keep old content' '
 
        PROPS-END
        EOF
-       test-svn-fe filemode.dump >stream &&
-       git fast-import <stream &&
+       try_dump filemode.dump &&
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
@@ -370,7 +489,7 @@ test_expect_failure 'change file mode but keep old content' '
        test_cmp hello actual.target
 '
 
-test_expect_success 'NUL in property value' '
+test_expect_success PIPE 'NUL in property value' '
        reinit_git &&
        echo "commit message" >expect.message &&
        {
@@ -391,13 +510,12 @@ test_expect_success 'NUL in property value' '
                echo &&
                cat props
        } >nulprop.dump &&
-       test-svn-fe nulprop.dump >stream &&
-       git fast-import <stream &&
+       try_dump nulprop.dump &&
        git diff-tree --always -s --format=%s HEAD >actual.message &&
        test_cmp expect.message actual.message
 '
 
-test_expect_success 'NUL in log message, file content, and property name' '
+test_expect_success PIPE 'NUL in log message, file content, and property name' '
        # Caveat: svnadmin 1.6.16 (r1073529) truncates at \0 in the
        # svn:specialQnotreally example.
        reinit_git &&
@@ -458,8 +576,7 @@ test_expect_success 'NUL in log message, file content, and property name' '
                link hello
                EOF
        } >8bitclean.dump &&
-       test-svn-fe 8bitclean.dump >stream &&
-       git fast-import <stream &&
+       try_dump 8bitclean.dump &&
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
@@ -478,7 +595,7 @@ test_expect_success 'NUL in log message, file content, and property name' '
        test_cmp expect.hello2 actual.hello2
 '
 
-test_expect_success 'change file mode and reiterate content' '
+test_expect_success PIPE 'change file mode and reiterate content' '
        reinit_git &&
        cat >expect <<-\EOF &&
        OBJID
@@ -490,7 +607,7 @@ test_expect_success 'change file mode and reiterate content' '
        EOF
        echo "link hello" >expect.blob &&
        echo hello >hello &&
-       cat >filemode.dump <<-\EOF &&
+       cat >filemode2.dump <<-\EOF &&
        SVN-fs-dump-format-version: 3
 
        Revision-number: 1
@@ -545,8 +662,7 @@ test_expect_success 'change file mode and reiterate content' '
        PROPS-END
        link hello
        EOF
-       test-svn-fe filemode.dump >stream &&
-       git fast-import <stream &&
+       try_dump filemode2.dump &&
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
@@ -559,7 +675,8 @@ test_expect_success 'change file mode and reiterate content' '
        test_cmp hello actual.target
 '
 
-test_expect_success 'deltas not supported' '
+test_expect_success PIPE 'deltas supported' '
+       reinit_git &&
        {
                # (old) h + (inline) ello + (old) \n
                printf "SVNQ%b%b%s" "Q\003\006\005\004" "\001Q\0204\001\002" "ello" |
@@ -619,10 +736,10 @@ test_expect_success 'deltas not supported' '
                echo PROPS-END &&
                cat delta
        } >delta.dump &&
-       test_must_fail test-svn-fe delta.dump
+       try_dump delta.dump
 '
 
-test_expect_success 'property deltas supported' '
+test_expect_success PIPE 'property deltas supported' '
        reinit_git &&
        cat >expect <<-\EOF &&
        OBJID
@@ -678,8 +795,7 @@ test_expect_success 'property deltas supported' '
                PROPS-END
                EOF
        } >propdelta.dump &&
-       test-svn-fe propdelta.dump >stream &&
-       git fast-import <stream &&
+       try_dump propdelta.dump &&
        {
                git rev-list HEAD |
                git diff-tree --stdin |
@@ -688,7 +804,7 @@ test_expect_success 'property deltas supported' '
        test_cmp expect actual
 '
 
-test_expect_success 'properties on /' '
+test_expect_success PIPE 'properties on /' '
        reinit_git &&
        cat <<-\EOF >expect &&
        OBJID
@@ -733,8 +849,7 @@ test_expect_success 'properties on /' '
 
        PROPS-END
        EOF
-       test-svn-fe changeroot.dump >stream &&
-       git fast-import <stream &&
+       try_dump changeroot.dump &&
        {
                git rev-list HEAD |
                git diff-tree --root --always --stdin |
@@ -743,7 +858,7 @@ test_expect_success 'properties on /' '
        test_cmp expect actual
 '
 
-test_expect_success 'deltas for typechange' '
+test_expect_success PIPE 'deltas for typechange' '
        reinit_git &&
        cat >expect <<-\EOF &&
        OBJID
@@ -819,8 +934,7 @@ test_expect_success 'deltas for typechange' '
        PROPS-END
        link testing 321
        EOF
-       test-svn-fe deleteprop.dump >stream &&
-       git fast-import <stream &&
+       try_dump deleteprop.dump &&
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
@@ -829,6 +943,143 @@ test_expect_success 'deltas for typechange' '
        test_cmp expect actual
 '
 
+test_expect_success PIPE 'deltas need not consume the whole preimage' '
+       reinit_git &&
+       cat >expect <<-\EOF &&
+       OBJID
+       :120000 100644 OBJID OBJID T    postimage
+       OBJID
+       :100644 120000 OBJID OBJID T    postimage
+       OBJID
+       :000000 100644 OBJID OBJID A    postimage
+       EOF
+       echo "first preimage" >expect.1 &&
+       printf target >expect.2 &&
+       printf lnk >expect.3 &&
+       {
+               printf "SVNQ%b%b%b" "QQ\017\001\017" "\0217" "first preimage\n" |
+               q_to_nul
+       } >delta.1 &&
+       {
+               properties svn:special "*" &&
+               echo PROPS-END
+       } >symlink.props &&
+       {
+               printf "SVNQ%b%b%b" "Q\002\013\004\012" "\0201\001\001\0211" "lnk target" |
+               q_to_nul
+       } >delta.2 &&
+       {
+               printf "SVNQ%b%b" "Q\004\003\004Q" "\001Q\002\002" |
+               q_to_nul
+       } >delta.3 &&
+       {
+               cat <<-\EOF &&
+               SVN-fs-dump-format-version: 3
+
+               Revision-number: 1
+               Prop-content-length: 10
+               Content-length: 10
+
+               PROPS-END
+
+               Node-path: postimage
+               Node-kind: file
+               Node-action: add
+               Text-delta: true
+               Prop-content-length: 10
+               EOF
+               echo Text-content-length: $(wc -c <delta.1) &&
+               echo Content-length: $((10 + $(wc -c <delta.1))) &&
+               echo &&
+               echo PROPS-END &&
+               cat delta.1 &&
+               cat <<-\EOF &&
+
+               Revision-number: 2
+               Prop-content-length: 10
+               Content-length: 10
+
+               PROPS-END
+
+               Node-path: postimage
+               Node-kind: file
+               Node-action: change
+               Text-delta: true
+               EOF
+               echo Prop-content-length: $(wc -c <symlink.props) &&
+               echo Text-content-length: $(wc -c <delta.2) &&
+               echo Content-length: $(($(wc -c <symlink.props) + $(wc -c <delta.2))) &&
+               echo &&
+               cat symlink.props &&
+               cat delta.2 &&
+               cat <<-\EOF &&
+
+               Revision-number: 3
+               Prop-content-length: 10
+               Content-length: 10
+
+               PROPS-END
+
+               Node-path: postimage
+               Node-kind: file
+               Node-action: change
+               Text-delta: true
+               Prop-content-length: 10
+               EOF
+               echo Text-content-length: $(wc -c <delta.3) &&
+               echo Content-length: $((10 + $(wc -c <delta.3))) &&
+               echo &&
+               echo PROPS-END &&
+               cat delta.3 &&
+               echo
+       } >deltapartial.dump &&
+       try_dump deltapartial.dump &&
+       {
+               git rev-list HEAD |
+               git diff-tree --root --stdin |
+               sed "s/$_x40/OBJID/g"
+       } >actual &&
+       test_cmp expect actual &&
+       git show HEAD:postimage >actual.3 &&
+       git show HEAD^:postimage >actual.2 &&
+       git show HEAD^^:postimage >actual.1 &&
+       test_cmp expect.1 actual.1 &&
+       test_cmp expect.2 actual.2 &&
+       test_cmp expect.3 actual.3
+'
+
+test_expect_success PIPE 'no hang for delta trying to read past end of preimage' '
+       reinit_git &&
+       {
+               # COPY 1
+               printf "SVNQ%b%b" "Q\001\001\002Q" "\001Q" |
+               q_to_nul
+       } >greedy.delta &&
+       {
+               cat <<-\EOF &&
+               SVN-fs-dump-format-version: 3
+
+               Revision-number: 1
+               Prop-content-length: 10
+               Content-length: 10
+
+               PROPS-END
+
+               Node-path: bootstrap
+               Node-kind: file
+               Node-action: add
+               Text-delta: true
+               Prop-content-length: 10
+               EOF
+               echo Text-content-length: $(wc -c <greedy.delta) &&
+               echo Content-length: $((10 + $(wc -c <greedy.delta))) &&
+               echo &&
+               echo PROPS-END &&
+               cat greedy.delta &&
+               echo
+       } >greedydelta.dump &&
+       try_dump greedydelta.dump must_fail might_fail
+'
 
 test_expect_success 'set up svn repo' '
        svnconf=$PWD/svnconf &&
@@ -844,12 +1095,12 @@ test_expect_success 'set up svn repo' '
        fi
 '
 
-test_expect_success SVNREPO 't9135/svn.dump' '
-       git init simple-git &&
-       test-svn-fe "$TEST_DIRECTORY/t9135/svn.dump" >simple.fe &&
+test_expect_success SVNREPO,PIPE 't9135/svn.dump' '
+       mkdir -p simple-git &&
        (
                cd simple-git &&
-               git fast-import <../simple.fe
+               reinit_git &&
+               try_dump "$TEST_DIRECTORY/t9135/svn.dump"
        ) &&
        (
                cd simple-svnco &&
diff --git a/t/t9011-svn-da.sh b/t/t9011-svn-da.sh
new file mode 100755 (executable)
index 0000000..b38d16f
--- /dev/null
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+test_description='test parsing of svndiff0 files
+
+Using the "test-svn-fe -d" helper, check that svn-fe correctly
+interprets deltas using various facilities (some from the spec,
+some only learned from practice).
+'
+. ./test-lib.sh
+
+>empty
+printf foo >preimage
+
+test_expect_success 'reject empty delta' '
+       test_must_fail test-svn-fe -d preimage empty 0
+'
+
+test_expect_success 'delta can empty file' '
+       printf "SVNQ" | q_to_nul >clear.delta &&
+       test-svn-fe -d preimage clear.delta 4 >actual &&
+       test_cmp empty actual
+'
+
+test_expect_success 'reject svndiff2' '
+       printf "SVN\002" >bad.filetype &&
+       test_must_fail test-svn-fe -d preimage bad.filetype 4
+'
+
+test_expect_success 'one-window empty delta' '
+       printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow &&
+       test-svn-fe -d preimage clear.onewindow 9 >actual &&
+       test_cmp empty actual
+'
+
+test_expect_success 'reject incomplete window header' '
+       printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow &&
+       printf "SVNQ%s" "QQ" | q_to_nul >clear.partialwindow &&
+       test_must_fail test-svn-fe -d preimage clear.onewindow 6 &&
+       test_must_fail test-svn-fe -d preimage clear.partialwindow 6
+'
+
+test_expect_success 'reject declared delta longer than actual delta' '
+       printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow &&
+       printf "SVNQ%s" "QQ" | q_to_nul >clear.partialwindow &&
+       test_must_fail test-svn-fe -d preimage clear.onewindow 14 &&
+       test_must_fail test-svn-fe -d preimage clear.partialwindow 9
+'
+
+test_expect_success 'two-window empty delta' '
+       printf "SVNQ%s%s" "QQQQQ" "QQQQQ" | q_to_nul >clear.twowindow &&
+       test-svn-fe -d preimage clear.twowindow 14 >actual &&
+       test_must_fail test-svn-fe -d preimage clear.twowindow 13 &&
+       test_cmp empty actual
+'
+
+test_expect_success 'noisy zeroes' '
+       printf "SVNQ%s" \
+               "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQ" |
+               tr R "\200" |
+               q_to_nul >clear.noisy &&
+       len=$(wc -c <clear.noisy) &&
+       test-svn-fe -d preimage clear.noisy $len &&
+       test_cmp empty actual
+'
+
+test_expect_success 'reject variable-length int in magic' '
+       printf "SVNRQ" | tr R "\200" | q_to_nul >clear.badmagic &&
+       test_must_fail test-svn-fe -d preimage clear.badmagic 5
+'
+
+test_expect_success 'reject truncated integer' '
+       printf "SVNQ%s%s" "QQQQQ" "QQQQRRQ" |
+               tr R "\200" |
+               q_to_nul >clear.fullint &&
+       printf "SVNQ%s%s" "QQQQQ" "QQQQRR" |
+               tr RT "\201" |
+               q_to_nul >clear.partialint &&
+       test_must_fail test-svn-fe -d preimage clear.fullint 15 &&
+       test-svn-fe -d preimage clear.fullint 16 &&
+       test_must_fail test-svn-fe -d preimage clear.partialint 15
+'
+
+test_expect_success 'nonempty (but unused) preimage view' '
+       printf "SVNQ%b" "Q\003QQQ" | q_to_nul >clear.readpreimage &&
+       test-svn-fe -d preimage clear.readpreimage 9 >actual &&
+       test_cmp empty actual
+'
+
+test_expect_success 'preimage view: right endpoint cannot backtrack' '
+       printf "SVNQ%b%b" "Q\003QQQ" "Q\002QQQ" |
+               q_to_nul >clear.backtrack &&
+       test_must_fail test-svn-fe -d preimage clear.backtrack 14
+'
+
+test_expect_success 'preimage view: left endpoint can advance' '
+       printf "SVNQ%b%b" "Q\003QQQ" "\001\002QQQ" |
+               q_to_nul >clear.preshrink &&
+       printf "SVNQ%b%b" "Q\003QQQ" "\001\001QQQ" |
+               q_to_nul >clear.shrinkbacktrack &&
+       test-svn-fe -d preimage clear.preshrink 14 >actual &&
+       test_must_fail test-svn-fe -d preimage clear.shrinkbacktrack 14 &&
+       test_cmp empty actual
+'
+
+test_expect_success 'preimage view: offsets compared by value' '
+       printf "SVNQ%b%b" "\001\001QQQ" "\0200Q\003QQQ" |
+               q_to_nul >clear.noisybacktrack &&
+       printf "SVNQ%b%b" "\001\001QQQ" "\0200\001\002QQQ" |
+               q_to_nul >clear.noisyadvance &&
+       test_must_fail test-svn-fe -d preimage clear.noisybacktrack 15 &&
+       test-svn-fe -d preimage clear.noisyadvance 15 &&
+       test_cmp empty actual
+'
+
+test_expect_success 'preimage view: reject truncated preimage' '
+       printf "SVNQ%b" "\010QQQQ" | q_to_nul >clear.lateemptyread &&
+       printf "SVNQ%b" "\010\001QQQ" | q_to_nul >clear.latenonemptyread &&
+       printf "SVNQ%b" "\001\010QQQ" | q_to_nul >clear.longread &&
+       test_must_fail test-svn-fe -d preimage clear.lateemptyread 9 &&
+       test_must_fail test-svn-fe -d preimage clear.latenonemptyread 9 &&
+       test_must_fail test-svn-fe -d preimage clear.longread 9
+'
+
+test_expect_success 'forbid unconsumed inline data' '
+       printf "SVNQ%b%s%b%s" "QQQQ\003" "bar" "QQQQ\001" "x" |
+               q_to_nul >inline.clear &&
+       test_must_fail test-svn-fe -d preimage inline.clear 18 >actual
+'
+
+test_expect_success 'reject truncated inline data' '
+       printf "SVNQ%b%s" "QQQQ\003" "b" | q_to_nul >inline.trunc &&
+       test_must_fail test-svn-fe -d preimage inline.trunc 10
+'
+
+test_expect_success 'reject truncated inline data (after instruction section)' '
+       printf "SVNQ%b%b%s" "QQ\001\001\003" "\0201" "b" | q_to_nul >insn.trunc &&
+       test_must_fail test-svn-fe -d preimage insn.trunc 11
+'
+
+test_expect_success 'copyfrom_data' '
+       echo hi >expect &&
+       printf "SVNQ%b%b%b" "QQ\003\001\003" "\0203" "hi\n" | q_to_nul >copydat &&
+       test-svn-fe -d preimage copydat 13 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'multiple copyfrom_data' '
+       echo hi >expect &&
+       printf "SVNQ%b%b%b%b%b" "QQ\003\002\003" "\0201\0202" "hi\n" \
+               "QQQ\002Q" "\0200Q" | q_to_nul >copy.multi &&
+       len=$(wc -c <copy.multi) &&
+       test-svn-fe -d preimage copy.multi $len >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'incomplete multiple insn' '
+       printf "SVNQ%b%b%b" "QQ\003\002\003" "\0203\0200" "hi\n" |
+               q_to_nul >copy.partial &&
+       len=$(wc -c <copy.partial) &&
+       test_must_fail test-svn-fe -d preimage copy.partial $len
+'
+
+test_expect_success 'catch attempt to copy missing data' '
+       printf "SVNQ%b%b%s%b%s" "QQ\002\002\001" "\0201\0201" "X" \
+                       "QQQQ\002" "YZ" |
+               q_to_nul >copy.incomplete &&
+       len=$(wc -c <copy.incomplete) &&
+       test_must_fail test-svn-fe -d preimage copy.incomplete $len
+'
+
+test_expect_success 'copyfrom target to repeat data' '
+       printf foofoo >expect &&
+       printf "SVNQ%b%b%s" "QQ\006\004\003" "\0203\0100\003Q" "foo" |
+               q_to_nul >copytarget.repeat &&
+       len=$(wc -c <copytarget.repeat) &&
+       test-svn-fe -d preimage copytarget.repeat $len >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'copyfrom target out of order' '
+       printf foooof >expect &&
+       printf "SVNQ%b%b%s" \
+               "QQ\006\007\003" "\0203\0101\002\0101\001\0101Q" "foo" |
+               q_to_nul >copytarget.reverse &&
+       len=$(wc -c <copytarget.reverse) &&
+       test-svn-fe -d preimage copytarget.reverse $len >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'catch copyfrom future' '
+       printf "SVNQ%b%b%s" "QQ\004\004\003" "\0202\0101\002\0201" "XYZ" |
+               q_to_nul >copytarget.infuture &&
+       len=$(wc -c <copytarget.infuture) &&
+       test_must_fail test-svn-fe -d preimage copytarget.infuture $len
+'
+
+test_expect_success 'copy to sustain' '
+       printf XYXYXYXYXYXZ >expect &&
+       printf "SVNQ%b%b%s" "QQ\014\004\003" "\0202\0111Q\0201" "XYZ" |
+               q_to_nul >copytarget.sustain &&
+       len=$(wc -c <copytarget.sustain) &&
+       test-svn-fe -d preimage copytarget.sustain $len >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'catch copy that overflows' '
+       printf "SVNQ%b%b%s" "QQ\003\003\001" "\0201\0177Q" X |
+               q_to_nul >copytarget.overflow &&
+       len=$(wc -c <copytarget.overflow) &&
+       test_must_fail test-svn-fe -d preimage copytarget.overflow $len
+'
+
+test_expect_success 'copyfrom source' '
+       printf foo >expect &&
+       printf "SVNQ%b%b" "Q\003\003\002Q" "\003Q" | q_to_nul >copysource.all &&
+       test-svn-fe -d preimage copysource.all 11 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'copy backwards' '
+       printf oof >expect &&
+       printf "SVNQ%b%b" "Q\003\003\006Q" "\001\002\001\001\001Q" |
+               q_to_nul >copysource.rev &&
+       test-svn-fe -d preimage copysource.rev 15 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'offsets are relative to window' '
+       printf fo >expect &&
+       printf "SVNQ%b%b%b%b" "Q\003\001\002Q" "\001Q" \
+               "\002\001\001\002Q" "\001Q" |
+               q_to_nul >copysource.two &&
+       test-svn-fe -d preimage copysource.two 18 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'example from notes/svndiff' '
+       printf aaaaccccdddddddd >expect &&
+       printf aaaabbbbcccc >source &&
+       printf "SVNQ%b%b%s" "Q\014\020\007\001" \
+               "\004Q\004\010\0201\0107\010" d |
+               q_to_nul >delta.example &&
+       len=$(wc -c <delta.example) &&
+       test-svn-fe -d source delta.example $len >actual &&
+       test_cmp expect actual
+'
+
+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)" &&
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 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..0f771c673d58009e2bcde45254d4b4e9fa68efb9 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"'
diff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh
new file mode 100755 (executable)
index 0000000..04ee20e
--- /dev/null
@@ -0,0 +1,464 @@
+#!/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 test_must_fail "$GITP4" commit --preserve-user &&
+               test_must_fail 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 test_must_fail "$GITP4" commit --preserve-user &&
+               test_must_fail 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 &&
+               P4EDITOR=touch P4USER=alice P4PASSWD=secret "$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 "$GITP4" commit |\
+               grep "git author derek@localhost does not match" &&
+
+               make_change_by_user usernamefile3 Charlie charlie@localhost &&
+               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit |\
+               grep "git author charlie@localhost does not match" &&
+
+               make_change_by_user usernamefile3 alice alice@localhost &&
+               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" |\
+               test_must_fail grep "git author.*does not match" &&
+
+               git config git-p4.skipUserNameCheck true &&
+               make_change_by_user usernamefile3 Charlie charlie@localhost &&
+               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$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 272de3f..0000000
+++ /dev/null
@@ -1,404 +0,0 @@
-#!/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 '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 test_must_fail "$GITP4" commit --preserve-user &&
-               test_must_fail 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 test_must_fail "$GITP4" commit --preserve-user &&
-               test_must_fail 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 &&
-               P4EDITOR=touch P4USER=alice P4PASSWD=secret "$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 "$GITP4" commit |\
-               grep "git author derek@localhost does not match" &&
-
-               make_change_by_user usernamefile3 Charlie charlie@localhost &&
-               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit |\
-               grep "git author charlie@localhost does not match" &&
-
-               make_change_by_user usernamefile3 alice alice@localhost &&
-               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" |\
-               test_must_fail grep "git author.*does not match" &&
-
-               git config git-p4.skipUserNameCheck true &&
-               make_change_by_user usernamefile3 Charlie charlie@localhost &&
-               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$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
index a25f18d36a196a4b85f6cac15a6a081744fe8fa1..d41470541650590355bf0de1a1b556b3502492b5 100755 (executable)
@@ -172,9 +172,9 @@ test_expect_success 'add simple p4 branches' '
                echo file1 >file1 &&
                echo file2 >file2 &&
                p4 add file1 file2 &&
-               p4 submit -d "branch1" &&
+               p4 submit -d "Create branch1" &&
                p4 integrate //depot/branch1/... //depot/branch2/... &&
-               p4 submit -d "branch2" &&
+               p4 submit -d "Integrate branch2 from branch1" &&
                echo file3 >file3 &&
                p4 add file3 &&
                p4 submit -d "add file3 in branch1" &&
@@ -182,7 +182,7 @@ test_expect_success 'add simple p4 branches' '
                echo update >>file2 &&
                p4 submit -d "update file2 in branch1" &&
                p4 integrate //depot/branch1/... //depot/branch3/... &&
-               p4 submit -d "branch3"
+               p4 submit -d "Integrate branch3 from branch1"
        )
 '
 
@@ -203,17 +203,17 @@ test_expect_success 'git-p4 clone simple branches' '
                test -f file1 &&
                test -f file2 &&
                test -f file3 &&
-               grep -q update file2 &&
+               grep update file2 &&
                git reset --hard p4/depot/branch2 &&
                test -f file1 &&
                test -f file2 &&
                test ! -f file3 &&
-               test_must_fail grep -q update file2 &&
+               ! grep update file2 &&
                git reset --hard p4/depot/branch3 &&
                test -f file1 &&
                test -f file2 &&
                test -f file3 &&
-               grep -q update file2 &&
+               grep update file2 &&
                cd "$cli" &&
                cd branch1 &&
                p4 edit file2 &&
@@ -222,7 +222,87 @@ test_expect_success 'git-p4 clone simple branches' '
                cd "$git" &&
                git reset --hard p4/depot/branch1 &&
                "$GITP4" rebase &&
-               grep -q file2_ file2
+               grep file2_ file2
+       )
+'
+
+# Create a complex branch structure in P4 depot to check if they are correctly
+# cloned. The branches are created from older changelists to check if git-p4 is
+# able to correctly detect them.
+# The final expected structure is:
+# `branch1
+# | `- file1
+# | `- file2 (updated)
+# | `- file3
+# `branch2
+# | `- file1
+# | `- file2
+# `branch3
+# | `- file1
+# | `- file2 (updated)
+# | `- file3
+# `branch4
+# | `- file1
+# | `- file2
+# `branch5
+#   `- file1
+#   `- file2
+#   `- file3
+test_expect_success 'git-p4 add complex branches' '
+       test_when_finished cleanup_git &&
+       test_create_repo "$git" &&
+       (
+               cd "$cli" &&
+               changelist=$(p4 changes -m1 //depot/... | cut -d" " -f2) &&
+               changelist=$(($changelist - 5)) &&
+               p4 integrate //depot/branch1/...@$changelist //depot/branch4/... &&
+               p4 submit -d "Integrate branch4 from branch1@${changelist}" &&
+               changelist=$(($changelist + 2)) &&
+               p4 integrate //depot/branch1/...@$changelist //depot/branch5/... &&
+               p4 submit -d "Integrate branch5 from branch1@${changelist}"
+       )
+'
+
+# Configure branches through git-config and clone them. git-p4 will only be able
+# to clone the original structure if it is able to detect the origin changelist
+# of each branch.
+test_expect_success 'git-p4 clone complex 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 &&
+               git config --add git-p4.branchList branch1:branch4 &&
+               git config --add git-p4.branchList branch1:branch5 &&
+               "$GITP4" clone --dest=. --detect-branches //depot@all &&
+               git log --all --graph --decorate --stat &&
+               git reset --hard p4/depot/branch1 &&
+               test_path_is_file file1 &&
+               test_path_is_file file2 &&
+               test_path_is_file file3 &&
+               grep update file2 &&
+               git reset --hard p4/depot/branch2 &&
+               test_path_is_file file1 &&
+               test_path_is_file file2 &&
+               test_path_is_missing file3 &&
+               ! grep update file2 &&
+               git reset --hard p4/depot/branch3 &&
+               test_path_is_file file1 &&
+               test_path_is_file file2 &&
+               test_path_is_file file3 &&
+               grep update file2 &&
+               git reset --hard p4/depot/branch4 &&
+               test_path_is_file file1 &&
+               test_path_is_file file2 &&
+               test_path_is_missing file3 &&
+               ! grep update file2 &&
+               git reset --hard p4/depot/branch5 &&
+               test_path_is_file file1 &&
+               test_path_is_file file2 &&
+               test_path_is_file file3 &&
+               ! grep update file2 &&
+               test_path_is_missing .git/git-p4-tmp
        )
 '
 
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..db67020
--- /dev/null
@@ -0,0 +1,112 @@
+#!/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
+       )
+'
+
+# Create a branch with a shell metachar in its name
+#
+# 1. //depot/main
+# 2. //depot/branch$3
+
+test_expect_success 'branch with shell char' '
+       test_when_finished cleanup_git &&
+       test_create_repo "$git" &&
+       (
+               cd "$cli" &&
+
+               mkdir -p main &&
+
+               echo f1 >main/f1 &&
+               p4 add main/f1 &&
+               p4 submit -d "main/f1" &&
+
+               p4 integrate //depot/main/... //depot/branch\$3/... &&
+               p4 submit -d "integrate main to branch\$3" &&
+
+               echo f1 >branch\$3/shell_char_branch_file &&
+               p4 add branch\$3/shell_char_branch_file &&
+               p4 submit -d "branch\$3/shell_char_branch_file" &&
+
+               p4 branch -i <<-EOF &&
+               Branch: branch\$3
+               View: //depot/main/... //depot/branch\$3/...
+               EOF
+
+               p4 edit main/f1 &&
+               echo "a change" >> main/f1 &&
+               p4 submit -d "a change" main/f1 &&
+
+               p4 integrate -b branch\$3 &&
+               p4 resolve -am branch\$3/... &&
+               p4 submit -d "integrate main to branch\$3" &&
+
+               cd "$git" &&
+
+               git config git-p4.branchList main:branch\$3 &&
+               "$GITP4" clone --dest=. --detect-branches //depot@all &&
+               git log --all --graph --decorate --stat &&
+               git reset --hard p4/depot/branch\$3 &&
+               test -f shell_char_branch_file &&
+               test -f f1
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9803-git-shell-metachars.sh b/t/t9803-git-shell-metachars.sh
deleted file mode 100755 (executable)
index db04375..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/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/t9804-git-p4-label.sh b/t/t9804-git-p4-label.sh
new file mode 100755 (executable)
index 0000000..80d01ea
--- /dev/null
@@ -0,0 +1,113 @@
+test_description='git-p4 p4 label tests'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+# Basic p4 label tests.
+#
+# Note: can't have more than one label per commit - others
+# are silently discarded.
+#
+test_expect_success 'basic p4 labels' '
+       test_when_finished cleanup_git &&
+       (
+               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" &&
+
+               echo f3 >main/file_with_\$metachar &&
+               p4 add main/file_with_\$metachar &&
+               p4 submit -d "file with metachar" &&
+
+               p4 tag -l tag_f1_only main/f1 &&
+               p4 tag -l tag_with\$_shell_char main/... &&
+
+               echo f4 >main/f4 &&
+               p4 add main/f4 &&
+               p4 submit -d "main/f4" &&
+
+               p4 label -i <<-EOF &&
+               Label: long_label
+               Description:
+                  A Label first line
+                  A Label second line
+               View:   //depot/...
+               EOF
+
+               p4 tag -l long_label ... &&
+
+               p4 labels ... &&
+
+               "$GITP4" clone --dest="$git" --detect-labels //depot@all &&
+               cd "$git" &&
+
+               git tag &&
+               git tag >taglist &&
+               test_line_count = 3 taglist &&
+
+               cd main &&
+               git checkout tag_tag_f1_only &&
+               ! test -f f2 &&
+               git checkout tag_tag_with\$_shell_char &&
+               test -f f1 && test -f f2 && test -f file_with_\$metachar &&
+
+               git show tag_long_label | grep -q "A Label second line"
+       )
+'
+
+# Test some label corner cases:
+#
+# - two tags on the same file; both should be available
+# - a tag that is only on one file; this kind of tag
+#   cannot be imported (at least not easily).
+
+test_expect_failure 'two labels on the same changelist' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$cli" &&
+               mkdir -p main &&
+
+               p4 edit main/f1 main/f2 &&
+               echo "hello world" >main/f1 &&
+               echo "not in the tag" >main/f2 &&
+               p4 submit -d "main/f[12]: testing two labels" &&
+
+               p4 tag -l tag_f1_1 main/... &&
+               p4 tag -l tag_f1_2 main/... &&
+
+               p4 labels ... &&
+
+               "$GITP4" clone --dest="$git" --detect-labels //depot@all &&
+               cd "$git" &&
+
+               git tag | grep tag_f1 &&
+               git tag | grep -q tag_f1_1 &&
+               git tag | grep -q tag_f1_2 &&
+
+               cd main &&
+
+               git checkout tag_tag_f1_1 &&
+               ls &&
+               test -f f1 &&
+
+               git checkout tag_tag_f1_2 &&
+               ls &&
+               test -f f1
+       )
+'
+
+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..0571602
--- /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_missing 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_missing 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..ae9145e
--- /dev/null
@@ -0,0 +1,683 @@
+#!/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
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               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
+       )
+'
+
+# 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 1' '
+       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 'wildcard ... only supported at end of spec 2' '
+       client_view "//depot/.../a/... //client/.../a/..." &&
+       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"
+'
+
+#
+# What happens when two files of the same name are overlayed together?
+# The last-listed file should take preference.
+#
+# //depot
+#   - dir1
+#     - file11
+#     - file12
+#     - filecollide
+#   - dir2
+#     - file21
+#     - file22
+#     - filecollide
+#
+test_expect_success 'overlay collision setup' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir1/filecollide >dir1/filecollide &&
+               p4 add dir1/filecollide &&
+               p4 submit -d dir1/filecollide &&
+               echo dir2/filecollide >dir2/filecollide &&
+               p4 add dir2/filecollide &&
+               p4 submit -d dir2/filecollide
+       )
+'
+
+test_expect_success 'overlay collision 1 to 2' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22 filecollide" &&
+       echo dir2/filecollide >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/filecollide &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files &&
+       test_cmp actual "$git"/filecollide
+'
+
+test_expect_failure 'overlay collision 2 to 1' '
+       client_view "//depot/dir2/... //client/..." \
+                   "+//depot/dir1/... //client/..." &&
+       files="file11 file12 file21 file22 filecollide" &&
+       echo dir1/filecollide >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/filecollide &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files &&
+       test_cmp actual "$git"/filecollide
+'
+
+test_expect_success 'overlay collision delete 2' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               p4 delete dir2/filecollide &&
+               p4 submit -d "remove dir2/filecollide"
+       )
+'
+
+# no filecollide, got deleted with dir2
+test_expect_failure 'overlay collision 1 to 2, but 2 deleted' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'overlay collision update 1' '
+       client_view "//depot/dir1/... //client/dir1/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               p4 open dir1/filecollide &&
+               echo dir1/filecollide update >dir1/filecollide &&
+               p4 submit -d "update dir1/filecollide"
+       )
+'
+
+# still no filecollide, dir2 still wins with the deletion even though the
+# change to dir1 is more recent
+test_expect_failure 'overlay collision 1 to 2, but 2 deleted, then 1 updated' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'overlay collision delete filecollides' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               p4 delete dir1/filecollide dir2/filecollide &&
+               p4 submit -d "remove filecollides"
+       )
+'
+
+#
+# Overlays as part of sync, rather than initial checkout:
+#   1.  add a file in dir1
+#   2.  sync to include it
+#   3.  add same file in dir2
+#   4.  sync, make sure content switches as dir2 has priority
+#   5.  add another file in dir1
+#   6.  sync
+#   7.  add/delete same file in dir2
+#   8.  sync, make sure it disappears, again dir2 wins
+#   9.  cleanup
+#
+# //depot
+#   - dir1
+#     - file11
+#     - file12
+#     - colA
+#     - colB
+#   - dir2
+#     - file21
+#     - file22
+#     - colA
+#     - colB
+#
+test_expect_success 'overlay sync: add colA in dir1' '
+       client_view "//depot/dir1/... //client/dir1/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir1/colA >dir1/colA &&
+               p4 add dir1/colA &&
+               p4 submit -d dir1/colA
+       )
+'
+
+test_expect_success 'overlay sync: initial git checkout' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22 colA" &&
+       echo dir1/colA >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colA &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files &&
+       test_cmp actual "$git"/colA
+'
+
+test_expect_success 'overlay sync: add colA in dir2' '
+       client_view "//depot/dir2/... //client/dir2/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir2/colA >dir2/colA &&
+               p4 add dir2/colA &&
+               p4 submit -d dir2/colA
+       )
+'
+
+test_expect_success 'overlay sync: colA content switch' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22 colA" &&
+       echo dir2/colA >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colA &&
+       (
+               cd "$git" &&
+               "$GITP4" sync --use-client-spec &&
+               git merge --ff-only p4/master
+       ) &&
+       git_verify $files &&
+       test_cmp actual "$git"/colA
+'
+
+test_expect_success 'overlay sync: add colB in dir1' '
+       client_view "//depot/dir1/... //client/dir1/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir1/colB >dir1/colB &&
+               p4 add dir1/colB &&
+               p4 submit -d dir1/colB
+       )
+'
+
+test_expect_success 'overlay sync: colB appears' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22 colA colB" &&
+       echo dir1/colB >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colB &&
+       (
+               cd "$git" &&
+               "$GITP4" sync --use-client-spec &&
+               git merge --ff-only p4/master
+       ) &&
+       git_verify $files &&
+       test_cmp actual "$git"/colB
+'
+
+test_expect_success 'overlay sync: add/delete colB in dir2' '
+       client_view "//depot/dir2/... //client/dir2/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir2/colB >dir2/colB &&
+               p4 add dir2/colB &&
+               p4 submit -d dir2/colB &&
+               p4 delete dir2/colB &&
+               p4 submit -d "delete dir2/colB"
+       )
+'
+
+test_expect_success 'overlay sync: colB disappears' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22 colA" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               "$GITP4" sync --use-client-spec &&
+               git merge --ff-only p4/master
+       ) &&
+       git_verify $files
+'
+
+test_expect_success 'overlay sync: cleanup' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               p4 delete dir1/colA dir2/colA dir1/colB &&
+               p4 submit -d "remove overlay sync files"
+       )
+'
+
+#
+# Overlay tests again, but swapped so dir1 has priority.
+#   1.  add a file in dir1
+#   2.  sync to include it
+#   3.  add same file in dir2
+#   4.  sync, make sure content does not switch
+#   5.  add another file in dir1
+#   6.  sync
+#   7.  add/delete same file in dir2
+#   8.  sync, make sure it is still there
+#   9.  cleanup
+#
+# //depot
+#   - dir1
+#     - file11
+#     - file12
+#     - colA
+#     - colB
+#   - dir2
+#     - file21
+#     - file22
+#     - colA
+#     - colB
+#
+test_expect_success 'overlay sync swap: add colA in dir1' '
+       client_view "//depot/dir1/... //client/dir1/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir1/colA >dir1/colA &&
+               p4 add dir1/colA &&
+               p4 submit -d dir1/colA
+       )
+'
+
+test_expect_success 'overlay sync swap: initial git checkout' '
+       client_view "//depot/dir2/... //client/..." \
+                   "+//depot/dir1/... //client/..." &&
+       files="file11 file12 file21 file22 colA" &&
+       echo dir1/colA >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colA &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files &&
+       test_cmp actual "$git"/colA
+'
+
+test_expect_success 'overlay sync swap: add colA in dir2' '
+       client_view "//depot/dir2/... //client/dir2/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir2/colA >dir2/colA &&
+               p4 add dir2/colA &&
+               p4 submit -d dir2/colA
+       )
+'
+
+test_expect_failure 'overlay sync swap: colA no content switch' '
+       client_view "//depot/dir2/... //client/..." \
+                   "+//depot/dir1/... //client/..." &&
+       files="file11 file12 file21 file22 colA" &&
+       echo dir1/colA >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colA &&
+       (
+               cd "$git" &&
+               "$GITP4" sync --use-client-spec &&
+               git merge --ff-only p4/master
+       ) &&
+       git_verify $files &&
+       test_cmp actual "$git"/colA
+'
+
+test_expect_success 'overlay sync swap: add colB in dir1' '
+       client_view "//depot/dir1/... //client/dir1/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir1/colB >dir1/colB &&
+               p4 add dir1/colB &&
+               p4 submit -d dir1/colB
+       )
+'
+
+test_expect_success 'overlay sync swap: colB appears' '
+       client_view "//depot/dir2/... //client/..." \
+                   "+//depot/dir1/... //client/..." &&
+       files="file11 file12 file21 file22 colA colB" &&
+       echo dir1/colB >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colB &&
+       (
+               cd "$git" &&
+               "$GITP4" sync --use-client-spec &&
+               git merge --ff-only p4/master
+       ) &&
+       git_verify $files &&
+       test_cmp actual "$git"/colB
+'
+
+test_expect_success 'overlay sync swap: add/delete colB in dir2' '
+       client_view "//depot/dir2/... //client/dir2/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir2/colB >dir2/colB &&
+               p4 add dir2/colB &&
+               p4 submit -d dir2/colB &&
+               p4 delete dir2/colB &&
+               p4 submit -d "delete dir2/colB"
+       )
+'
+
+test_expect_failure 'overlay sync swap: colB no change' '
+       client_view "//depot/dir2/... //client/..." \
+                   "+//depot/dir1/... //client/..." &&
+       files="file11 file12 file21 file22 colA colB" &&
+       echo dir1/colB >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colB &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               "$GITP4" sync --use-client-spec &&
+               git merge --ff-only p4/master
+       ) &&
+       git_verify $files &&
+       test_cmp actual "$cli"/colB
+'
+
+test_expect_success 'overlay sync swap: cleanup' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               p4 delete dir1/colA dir2/colA dir1/colB &&
+               p4 submit -d "remove overlay sync files"
+       )
+'
+
+#
+# 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
index bdd9513b84301275330d3dd7e49af05081ef9cd7..e28d5fdebe21f33b91942ce00125cc215f6427fd 100644 (file)
@@ -44,6 +44,7 @@ export LANG LC_ALL PAGER TERM TZ
 EDITOR=:
 unset VISUAL
 unset EMAIL
+unset LANGUAGE
 unset $(perl -e '
        my @env = keys %ENV;
        my $ok = join("|", qw(
@@ -63,7 +64,8 @@ GIT_AUTHOR_NAME='A U Thor'
 GIT_COMMITTER_EMAIL=committer@example.com
 GIT_COMMITTER_NAME='C O Mitter'
 GIT_MERGE_VERBOSITY=5
-export GIT_MERGE_VERBOSITY
+GIT_MERGE_AUTOEDIT=no
+export GIT_MERGE_VERBOSITY GIT_MERGE_AUTOEDIT
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
 export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
 export EDITOR
@@ -191,6 +193,7 @@ then
 fi
 
 exec 5>&1
+exec 6<&0
 if test "$verbose" = "t"
 then
        exec 4>&2 3>&1
@@ -327,6 +330,19 @@ test_tick () {
        export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
 }
 
+# Stop execution and start a shell. This is useful for debugging tests and
+# only makes sense together with "-v".
+#
+# Be sure to remove all invocations of this command before submitting.
+
+test_pause () {
+       if test "$verbose" = t; then
+               "$SHELL_PATH" <&6 >&3 2>&4
+       else
+               error >&5 "test_pause requires --verbose"
+       fi
+}
+
 # Call test_commit with the arguments "<message> [<file> [<contents>]]"
 #
 # This will commit a file with the given contents and the given commit
@@ -379,6 +395,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 +499,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 +1143,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..78d272b863f22285048cf54b9dcb03f80cb36f00 100644 (file)
--- a/tag.c
+++ b/tag.c
@@ -24,6 +24,18 @@ struct object *deref_tag(struct object *o, const char *warn, int warnlen)
        return o;
 }
 
+struct object *deref_tag_noverify(struct object *o)
+{
+       while (o && o->type == OBJ_TAG) {
+               o = parse_object(o->sha1);
+               if (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged)
+                       o = ((struct tag *)o)->tagged;
+               else
+                       o = NULL;
+       }
+       return o;
+}
+
 struct tag *lookup_tag(const unsigned char *sha1)
 {
        struct object *obj = lookup_object(sha1);
@@ -139,6 +151,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/tag.h b/tag.h
index 5ee88e6550cafa78b7e4acaa1285d5805974a037..bc8a1e40f04e87a6d502ab9d96022f734c57f4eb 100644 (file)
--- a/tag.h
+++ b/tag.h
@@ -16,6 +16,7 @@ extern struct tag *lookup_tag(const unsigned char *sha1);
 extern int parse_tag_buffer(struct tag *item, const void *data, unsigned long size);
 extern int parse_tag(struct tag *item);
 extern struct object *deref_tag(struct object *, const char *, int);
+extern struct object *deref_tag_noverify(struct object *);
 extern size_t parse_signature(const char *buf, unsigned long size);
 
 #endif /* TAG_H */
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 1f73f1ea7dfa6a14dedf384c99751e86c8121ff4..a6ffdf39d58bcb2b4469080e828dc8a5b0fbc675 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, WRITE_TREE_DRY_RUN);
        return dump_cache_tree(active_cache_tree, another, "");
 }
diff --git a/test-obj-pool.c b/test-obj-pool.c
deleted file mode 100644 (file)
index 5018863..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * test-obj-pool.c: code to exercise the svn importer's object pool
- */
-
-#include "cache.h"
-#include "vcs-svn/obj_pool.h"
-
-enum pool { POOL_ONE, POOL_TWO };
-obj_pool_gen(one, int, 1)
-obj_pool_gen(two, int, 4096)
-
-static uint32_t strtouint32(const char *s)
-{
-       char *end;
-       uintmax_t n = strtoumax(s, &end, 10);
-       if (*s == '\0' || (*end != '\n' && *end != '\0'))
-               die("invalid offset: %s", s);
-       return (uint32_t) n;
-}
-
-static void handle_command(const char *command, enum pool pool, const char *arg)
-{
-       switch (*command) {
-       case 'a':
-               if (!prefixcmp(command, "alloc ")) {
-                       uint32_t n = strtouint32(arg);
-                       printf("%"PRIu32"\n",
-                               pool == POOL_ONE ?
-                               one_alloc(n) : two_alloc(n));
-                       return;
-               }
-       case 'c':
-               if (!prefixcmp(command, "commit ")) {
-                       pool == POOL_ONE ? one_commit() : two_commit();
-                       return;
-               }
-               if (!prefixcmp(command, "committed ")) {
-                       printf("%"PRIu32"\n",
-                               pool == POOL_ONE ?
-                               one_pool.committed : two_pool.committed);
-                       return;
-               }
-       case 'f':
-               if (!prefixcmp(command, "free ")) {
-                       uint32_t n = strtouint32(arg);
-                       pool == POOL_ONE ? one_free(n) : two_free(n);
-                       return;
-               }
-       case 'n':
-               if (!prefixcmp(command, "null ")) {
-                       printf("%"PRIu32"\n",
-                               pool == POOL_ONE ?
-                               one_offset(NULL) : two_offset(NULL));
-                       return;
-               }
-       case 'o':
-               if (!prefixcmp(command, "offset ")) {
-                       uint32_t n = strtouint32(arg);
-                       printf("%"PRIu32"\n",
-                               pool == POOL_ONE ?
-                               one_offset(one_pointer(n)) :
-                               two_offset(two_pointer(n)));
-                       return;
-               }
-       case 'r':
-               if (!prefixcmp(command, "reset ")) {
-                       pool == POOL_ONE ? one_reset() : two_reset();
-                       return;
-               }
-       case 's':
-               if (!prefixcmp(command, "set ")) {
-                       uint32_t n = strtouint32(arg);
-                       if (pool == POOL_ONE)
-                               *one_pointer(n) = 1;
-                       else
-                               *two_pointer(n) = 1;
-                       return;
-               }
-       case 't':
-               if (!prefixcmp(command, "test ")) {
-                       uint32_t n = strtouint32(arg);
-                       printf("%d\n", pool == POOL_ONE ?
-                               *one_pointer(n) : *two_pointer(n));
-                       return;
-               }
-       default:
-               die("unrecognized command: %s", command);
-       }
-}
-
-static void handle_line(const char *line)
-{
-       const char *arg = strchr(line, ' ');
-       enum pool pool;
-
-       if (arg && !prefixcmp(arg + 1, "one"))
-               pool = POOL_ONE;
-       else if (arg && !prefixcmp(arg + 1, "two"))
-               pool = POOL_TWO;
-       else
-               die("no pool specified: %s", line);
-
-       handle_command(line, pool, arg + strlen("one "));
-}
-
-int main(int argc, char *argv[])
-{
-       struct strbuf sb = STRBUF_INIT;
-       if (argc != 1)
-               usage("test-obj-str < script");
-
-       while (strbuf_getline(&sb, stdin, '\n') != EOF)
-               handle_line(sb.buf);
-       strbuf_release(&sb);
-       return 0;
-}
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;
+}
diff --git a/test-string-pool.c b/test-string-pool.c
deleted file mode 100644 (file)
index c5782e6..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * test-string-pool.c: code to exercise the svn importer's string pool
- */
-
-#include "git-compat-util.h"
-#include "vcs-svn/string_pool.h"
-
-int main(int argc, char *argv[])
-{
-       const uint32_t unequal = pool_intern("does not equal");
-       const uint32_t equal = pool_intern("equals");
-       uint32_t buf[3];
-       uint32_t n;
-
-       if (argc != 2)
-               usage("test-string-pool <string>,<string>");
-
-       n = pool_tok_seq(3, buf, ",-", argv[1]);
-       if (n >= 3)
-               die("too many strings");
-       if (n <= 1)
-               die("too few strings");
-
-       buf[2] = buf[1];
-       buf[1] = (buf[0] == buf[2]) ? equal : unequal;
-       pool_print_seq(3, buf, ' ', stdout);
-       fputc('\n', stdout);
-
-       pool_reset();
-       return 0;
-}
index b42ba789b176160285844e49cb834d60170d9a54..332a5f711df8f3e3fea3305eb5ecb10de5581033 100644 (file)
@@ -4,15 +4,51 @@
 
 #include "git-compat-util.h"
 #include "vcs-svn/svndump.h"
+#include "vcs-svn/svndiff.h"
+#include "vcs-svn/sliding_window.h"
+#include "vcs-svn/line_buffer.h"
 
-int main(int argc, char *argv[])
+static const char test_svnfe_usage[] =
+       "test-svn-fe (<dumpfile> | [-d] <preimage> <delta> <len>)";
+
+static int apply_delta(int argc, char *argv[])
 {
-       if (argc != 2)
-               usage("test-svn-fe <file>");
-       if (svndump_init(argv[1]))
+       struct line_buffer preimage = LINE_BUFFER_INIT;
+       struct line_buffer delta = LINE_BUFFER_INIT;
+       struct sliding_view preimage_view = SLIDING_VIEW_INIT(&preimage, -1);
+
+       if (argc != 5)
+               usage(test_svnfe_usage);
+
+       if (buffer_init(&preimage, argv[2]))
+               die_errno("cannot open preimage");
+       if (buffer_init(&delta, argv[3]))
+               die_errno("cannot open delta");
+       if (svndiff0_apply(&delta, (off_t) strtoull(argv[4], NULL, 0),
+                                       &preimage_view, stdout))
                return 1;
-       svndump_read(NULL);
-       svndump_deinit();
-       svndump_reset();
+       if (buffer_deinit(&preimage))
+               die_errno("cannot close preimage");
+       if (buffer_deinit(&delta))
+               die_errno("cannot close delta");
+       buffer_reset(&preimage);
+       strbuf_release(&preimage_view.buf);
+       buffer_reset(&delta);
        return 0;
 }
+
+int main(int argc, char *argv[])
+{
+       if (argc == 2) {
+               if (svndump_init(argv[1]))
+                       return 1;
+               svndump_read(NULL);
+               svndump_deinit();
+               svndump_reset();
+               return 0;
+       }
+
+       if (argc >= 2 && !strcmp(argv[1], "-d"))
+               return apply_delta(argc, argv);
+       usage(test_svnfe_usage);
+}
diff --git a/test-treap.c b/test-treap.c
deleted file mode 100644 (file)
index ab8c951..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * test-treap.c: code to exercise the svn importer's treap structure
- */
-
-#include "cache.h"
-#include "vcs-svn/obj_pool.h"
-#include "vcs-svn/trp.h"
-
-struct int_node {
-       uintmax_t n;
-       struct trp_node children;
-};
-
-obj_pool_gen(node, struct int_node, 3)
-
-static int node_cmp(struct int_node *a, struct int_node *b)
-{
-       return (a->n > b->n) - (a->n < b->n);
-}
-
-trp_gen(static, treap_, struct int_node, children, node, node_cmp)
-
-static void strtonode(struct int_node *item, const char *s)
-{
-       char *end;
-       item->n = strtoumax(s, &end, 10);
-       if (*s == '\0' || (*end != '\n' && *end != '\0'))
-               die("invalid integer: %s", s);
-}
-
-int main(int argc, char *argv[])
-{
-       struct strbuf sb = STRBUF_INIT;
-       struct trp_root root = { ~0 };
-       uint32_t item;
-
-       if (argc != 1)
-               usage("test-treap < ints");
-
-       while (strbuf_getline(&sb, stdin, '\n') != EOF) {
-               struct int_node *node = node_pointer(node_alloc(1));
-
-               item = node_offset(node);
-               strtonode(node, sb.buf);
-               node = treap_insert(&root, node_pointer(item));
-               if (node_offset(node) != item)
-                       die("inserted %"PRIu32" in place of %"PRIu32"",
-                               node_offset(node), item);
-       }
-
-       item = node_offset(treap_first(&root));
-       while (~item) {
-               uint32_t next;
-               struct int_node *tmp = node_pointer(node_alloc(1));
-
-               tmp->n = node_pointer(item)->n;
-               next = node_offset(treap_next(&root, node_pointer(item)));
-
-               treap_remove(&root, node_pointer(item));
-               item = node_offset(treap_nsearch(&root, tmp));
-
-               if (item != next && (!~item || node_pointer(item)->n != tmp->n))
-                       die("found %"PRIuMAX" in place of %"PRIuMAX"",
-                               ~item ? node_pointer(item)->n : ~(uintmax_t) 0,
-                               ~next ? node_pointer(next)->n : ~(uintmax_t) 0);
-               printf("%"PRIuMAX"\n", tmp->n);
-       }
-       node_reset();
-       return 0;
-}
index 51814b5da3064b617fa268a6b2bc6b3d6cb0b110..cac0c065ff9f82011b204f932932283b01a5d034 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;
        }
 
@@ -772,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;
        }
 
@@ -981,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;
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 418107ec83728473093b43dfe74ab709f312e8a8..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);
@@ -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;
 }
@@ -573,30 +577,26 @@ static int match_dir_prefix(const char *base,
  *
  * 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;
@@ -610,12 +610,13 @@ int tree_entry_interesting(const struct name_entry *entry,
                                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;
                }
 
                /* Either there must be no base, or the base must match. */
@@ -623,25 +624,25 @@ int tree_entry_interesting(const struct name_entry *entry,
                        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 8282f5e5f6c615460e1c340d66e395c2d57aef73..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];
index 470cffd7c14a9f28010423a44327084219598a35..bb08e2eb0dc52f68807a83b671fa61a2c2224f41 100644 (file)
@@ -585,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();
@@ -616,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);
@@ -720,11 +724,14 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
        static const char *capabilities = "multi_ack thin-pack side-band"
                " side-band-64k ofs-delta shallow no-progress"
                " include-tag multi_ack_detailed";
-       struct object *o = parse_object(sha1);
+       struct object *o = lookup_unknown_object(sha1);
        const char *refname_nons = strip_namespace(refname);
 
-       if (!o)
-               die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1));
+       if (o->type == OBJ_NONE) {
+               o->type = sha1_object_info(sha1, NULL);
+               if (o->type < 0)
+                   die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1));
+       }
 
        if (capabilities)
                packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), refname_nons,
@@ -738,7 +745,7 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
                nr_our_refs++;
        }
        if (o->type == OBJ_TAG) {
-               o = deref_tag(o, refname, 0);
+               o = deref_tag_noverify(o);
                if (o)
                        packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname_nons);
        }
@@ -784,6 +791,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;
index bf553ad91b55de8a762d56a6ffc6c86e959e878c..1e7184f7f00bc75872369761116141c087fa1c1c 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)[^;]*)$",
         /* -- */
@@ -207,14 +210,7 @@ static int parse_funcname(struct userdiff_funcname *f, const char *k,
        if (git_config_string(&f->pattern, k, v) < 0)
                return -1;
        f->cflags = cflags;
-       return 1;
-}
-
-static int parse_string(const char **d, const char *k, const char *v)
-{
-       if (git_config_string(d, k, v) < 0)
-               return -1;
-       return 1;
+       return 0;
 }
 
 static int parse_tristate(int *b, const char *k, const char *v)
@@ -223,13 +219,13 @@ static int parse_tristate(int *b, const char *k, const char *v)
                *b = -1;
        else
                *b = git_config_bool(k, v);
-       return 1;
+       return 0;
 }
 
 static int parse_bool(int *b, const char *k, const char *v)
 {
        *b = git_config_bool(k, v);
-       return 1;
+       return 0;
 }
 
 int userdiff_config(const char *k, const char *v)
@@ -243,13 +239,13 @@ int userdiff_config(const char *k, const char *v)
        if ((drv = parse_driver(k, v, "binary")))
                return parse_tristate(&drv->binary, k, v);
        if ((drv = parse_driver(k, v, "command")))
-               return parse_string(&drv->external, k, v);
+               return git_config_string(&drv->external, k, v);
        if ((drv = parse_driver(k, v, "textconv")))
-               return parse_string(&drv->textconv, k, v);
+               return git_config_string(&drv->textconv, k, v);
        if ((drv = parse_driver(k, v, "cachetextconv")))
                return parse_bool(&drv->textconv_want_cache, k, v);
        if ((drv = parse_driver(k, v, "wordregex")))
-               return parse_string(&drv->word_regex, k, v);
+               return git_config_string(&drv->word_regex, k, v);
 
        return 0;
 }
index 0a5e3c43a0bfc69345c6e2ab6ec4d7830f468b37..eb91858b825ba89131054444b7cac104285fad3c 100644 (file)
@@ -1,8 +1,7 @@
 Copyright (C) 2010 David Barr <david.barr@cordelta.com>.
 All rights reserved.
 
-Copyright (C) 2008 Jason Evans <jasone@canonware.com>.
-All rights reserved.
+Copyright (C) 2010 Jonathan Nieder <jrnieder@gmail.com>.
 
 Copyright (C) 2005 Stefan Hegny, hydrografix Consulting GmbH,
 Frankfurt/Main, Germany
index 99ed70b88a5aaacbb48a9463f19ec198d7bedf84..b823b8519c6dc76aa534e056c44eea7210e716f5 100644 (file)
@@ -4,34 +4,77 @@
  */
 
 #include "git-compat-util.h"
+#include "strbuf.h"
+#include "quote.h"
 #include "fast_export.h"
-#include "line_buffer.h"
 #include "repo_tree.h"
-#include "string_pool.h"
+#include "strbuf.h"
+#include "svndiff.h"
+#include "sliding_window.h"
+#include "line_buffer.h"
 
 #define MAX_GITSVN_LINE_LEN 4096
 
 static uint32_t first_commit_done;
+static struct line_buffer postimage = LINE_BUFFER_INIT;
+static struct line_buffer report_buffer = LINE_BUFFER_INIT;
+
+/* NEEDSWORK: move to fast_export_init() */
+static int init_postimage(void)
+{
+       static int postimage_initialized;
+       if (postimage_initialized)
+               return 0;
+       postimage_initialized = 1;
+       return buffer_tmpfile_init(&postimage);
+}
+
+void fast_export_init(int fd)
+{
+       first_commit_done = 0;
+       if (buffer_fdinit(&report_buffer, fd))
+               die_errno("cannot read from file descriptor %d", fd);
+}
+
+void fast_export_deinit(void)
+{
+       if (buffer_deinit(&report_buffer))
+               die_errno("error closing fast-import feedback stream");
+}
+
+void fast_export_reset(void)
+{
+       buffer_reset(&report_buffer);
+}
 
-void fast_export_delete(uint32_t depth, uint32_t *path)
+void fast_export_delete(const char *path)
 {
        putchar('D');
        putchar(' ');
-       pool_print_seq(depth, path, '/', stdout);
+       quote_c_style(path, NULL, stdout, 0);
        putchar('\n');
 }
 
-void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode,
-                       uint32_t mark)
+static void fast_export_truncate(const char *path, uint32_t mode)
+{
+       fast_export_modify(path, mode, "inline");
+       printf("data 0\n\n");
+}
+
+void fast_export_modify(const char *path, uint32_t mode, const char *dataref)
 {
        /* Mode must be 100644, 100755, 120000, or 160000. */
-       printf("M %06"PRIo32" :%"PRIu32" ", mode, mark);
-       pool_print_seq(depth, path, '/', stdout);
+       if (!dataref) {
+               fast_export_truncate(path, mode);
+               return;
+       }
+       printf("M %06"PRIo32" %s ", mode, dataref);
+       quote_c_style(path, NULL, stdout, 0);
        putchar('\n');
 }
 
 static char gitsvnline[MAX_GITSVN_LINE_LEN];
-void fast_export_commit(uint32_t revision, const char *author,
+void fast_export_begin_commit(uint32_t revision, const char *author,
                        const struct strbuf *log,
                        const char *uuid, const char *url,
                        unsigned long timestamp)
@@ -47,6 +90,7 @@ void fast_export_commit(uint32_t revision, const char *author,
                *gitsvnline = '\0';
        }
        printf("commit refs/heads/master\n");
+       printf("mark :%"PRIu32"\n", revision);
        printf("committer %s <%s@%s> %ld +0000\n",
                   *author ? author : "nobody",
                   *author ? author : "nobody",
@@ -57,15 +101,44 @@ void fast_export_commit(uint32_t revision, const char *author,
        printf("%s\n", gitsvnline);
        if (!first_commit_done) {
                if (revision > 1)
-                       printf("from refs/heads/master^0\n");
+                       printf("from :%"PRIu32"\n", revision - 1);
                first_commit_done = 1;
        }
-       repo_diff(revision - 1, revision);
-       fputc('\n', stdout);
+}
 
+void fast_export_end_commit(uint32_t revision)
+{
        printf("progress Imported commit %"PRIu32".\n\n", revision);
 }
 
+static void ls_from_rev(uint32_t rev, const char *path)
+{
+       /* ls :5 path/to/old/file */
+       printf("ls :%"PRIu32" ", rev);
+       quote_c_style(path, NULL, stdout, 0);
+       putchar('\n');
+       fflush(stdout);
+}
+
+static void ls_from_active_commit(const char *path)
+{
+       /* ls "path/to/file" */
+       printf("ls \"");
+       quote_c_style(path, NULL, stdout, 1);
+       printf("\"\n");
+       fflush(stdout);
+}
+
+static const char *get_response_line(void)
+{
+       const char *line = buffer_read_line(&report_buffer);
+       if (line)
+               return line;
+       if (buffer_ferror(&report_buffer))
+               die_errno("error reading from fast-import");
+       die("unexpected end of fast-import feedback");
+}
+
 static void die_short_read(struct line_buffer *input)
 {
        if (buffer_ferror(input))
@@ -73,16 +146,171 @@ static void die_short_read(struct line_buffer *input)
        die("invalid dump: unexpected end of file");
 }
 
-void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len, struct line_buffer *input)
+static int ends_with(const char *s, size_t len, const char *suffix)
 {
+       const size_t suffixlen = strlen(suffix);
+       if (len < suffixlen)
+               return 0;
+       return !memcmp(s + len - suffixlen, suffix, suffixlen);
+}
+
+static int parse_cat_response_line(const char *header, off_t *len)
+{
+       size_t headerlen = strlen(header);
+       uintmax_t n;
+       const char *type;
+       const char *end;
+
+       if (ends_with(header, headerlen, " missing"))
+               return error("cat-blob reports missing blob: %s", header);
+       type = memmem(header, headerlen, " blob ", strlen(" blob "));
+       if (!type)
+               return error("cat-blob header has wrong object type: %s", header);
+       n = strtoumax(type + strlen(" blob "), (char **) &end, 10);
+       if (end == type + strlen(" blob "))
+               return error("cat-blob header does not contain length: %s", header);
+       if (memchr(type + strlen(" blob "), '-', end - type - strlen(" blob ")))
+               return error("cat-blob header contains negative length: %s", header);
+       if (n == UINTMAX_MAX || n > maximum_signed_value_of_type(off_t))
+               return error("blob too large for current definition of off_t");
+       *len = n;
+       if (*end)
+               return error("cat-blob header contains garbage after length: %s", header);
+       return 0;
+}
+
+static void check_preimage_overflow(off_t a, off_t b)
+{
+       if (signed_add_overflows(a, b))
+               die("blob too large for current definition of off_t");
+}
+
+static long apply_delta(off_t len, struct line_buffer *input,
+                       const char *old_data, uint32_t old_mode)
+{
+       long ret;
+       struct sliding_view preimage = SLIDING_VIEW_INIT(&report_buffer, 0);
+       FILE *out;
+
+       if (init_postimage() || !(out = buffer_tmpfile_rewind(&postimage)))
+               die("cannot open temporary file for blob retrieval");
+       if (old_data) {
+               const char *response;
+               printf("cat-blob %s\n", old_data);
+               fflush(stdout);
+               response = get_response_line();
+               if (parse_cat_response_line(response, &preimage.max_off))
+                       die("invalid cat-blob response: %s", response);
+               check_preimage_overflow(preimage.max_off, 1);
+       }
+       if (old_mode == REPO_MODE_LNK) {
+               strbuf_addstr(&preimage.buf, "link ");
+               check_preimage_overflow(preimage.max_off, strlen("link "));
+               preimage.max_off += strlen("link ");
+               check_preimage_overflow(preimage.max_off, 1);
+       }
+       if (svndiff0_apply(input, len, &preimage, out))
+               die("cannot apply delta");
+       if (old_data) {
+               /* Read the remainder of preimage and trailing newline. */
+               assert(!signed_add_overflows(preimage.max_off, 1));
+               preimage.max_off++;     /* room for newline */
+               if (move_window(&preimage, preimage.max_off - 1, 1))
+                       die("cannot seek to end of input");
+               if (preimage.buf.buf[0] != '\n')
+                       die("missing newline after cat-blob response");
+       }
+       ret = buffer_tmpfile_prepare_to_read(&postimage);
+       if (ret < 0)
+               die("cannot read temporary file for blob retrieval");
+       strbuf_release(&preimage.buf);
+       return ret;
+}
+
+void fast_export_data(uint32_t mode, off_t len, struct line_buffer *input)
+{
+       assert(len >= 0);
        if (mode == REPO_MODE_LNK) {
                /* svn symlink blobs start with "link " */
+               if (len < 5)
+                       die("invalid dump: symlink too short for \"link\" prefix");
                len -= 5;
                if (buffer_skip_bytes(input, 5) != 5)
                        die_short_read(input);
        }
-       printf("blob\nmark :%"PRIu32"\ndata %"PRIu32"\n", mark, len);
+       printf("data %"PRIuMAX"\n", (uintmax_t) len);
        if (buffer_copy_bytes(input, len) != len)
                die_short_read(input);
        fputc('\n', stdout);
 }
+
+static int parse_ls_response(const char *response, uint32_t *mode,
+                                       struct strbuf *dataref)
+{
+       const char *tab;
+       const char *response_end;
+
+       assert(response);
+       response_end = response + strlen(response);
+
+       if (*response == 'm') { /* Missing. */
+               errno = ENOENT;
+               return -1;
+       }
+
+       /* Mode. */
+       if (response_end - response < strlen("100644") ||
+           response[strlen("100644")] != ' ')
+               die("invalid ls response: missing mode: %s", response);
+       *mode = 0;
+       for (; *response != ' '; response++) {
+               char ch = *response;
+               if (ch < '0' || ch > '7')
+                       die("invalid ls response: mode is not octal: %s", response);
+               *mode *= 8;
+               *mode += ch - '0';
+       }
+
+       /* ' blob ' or ' tree ' */
+       if (response_end - response < strlen(" blob ") ||
+           (response[1] != 'b' && response[1] != 't'))
+               die("unexpected ls response: not a tree or blob: %s", response);
+       response += strlen(" blob ");
+
+       /* Dataref. */
+       tab = memchr(response, '\t', response_end - response);
+       if (!tab)
+               die("invalid ls response: missing tab: %s", response);
+       strbuf_add(dataref, response, tab - response);
+       return 0;
+}
+
+int fast_export_ls_rev(uint32_t rev, const char *path,
+                               uint32_t *mode, struct strbuf *dataref)
+{
+       ls_from_rev(rev, path);
+       return parse_ls_response(get_response_line(), mode, dataref);
+}
+
+int fast_export_ls(const char *path, uint32_t *mode, struct strbuf *dataref)
+{
+       ls_from_active_commit(path);
+       return parse_ls_response(get_response_line(), mode, dataref);
+}
+
+void fast_export_blob_delta(uint32_t mode,
+                               uint32_t old_mode, const char *old_data,
+                               off_t len, struct line_buffer *input)
+{
+       long postimage_len;
+
+       assert(len >= 0);
+       postimage_len = apply_delta(len, input, old_data, old_mode);
+       if (mode == REPO_MODE_LNK) {
+               buffer_skip_bytes(&postimage, strlen("link "));
+               postimage_len -= strlen("link ");
+       }
+       printf("data %ld\n", postimage_len);
+       buffer_copy_bytes(&postimage, postimage_len);
+       fputc('\n', stdout);
+}
index 33a8fe996f5fc025f587c1d09ae3f81e1786dbbb..aa629f54ff5b49075eb6dafd1d480077fa8c16f2 100644 (file)
@@ -1,16 +1,28 @@
 #ifndef FAST_EXPORT_H_
 #define FAST_EXPORT_H_
 
-#include "line_buffer.h"
 struct strbuf;
+struct line_buffer;
 
-void fast_export_delete(uint32_t depth, uint32_t *path);
-void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode,
-                       uint32_t mark);
-void fast_export_commit(uint32_t revision, const char *author,
+void fast_export_init(int fd);
+void fast_export_deinit(void);
+void fast_export_reset(void);
+
+void fast_export_delete(const char *path);
+void fast_export_modify(const char *path, uint32_t mode, const char *dataref);
+void fast_export_begin_commit(uint32_t revision, const char *author,
                        const struct strbuf *log, const char *uuid,
                        const char *url, unsigned long timestamp);
-void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len,
-                     struct line_buffer *input);
+void fast_export_end_commit(uint32_t revision);
+void fast_export_data(uint32_t mode, off_t len, struct line_buffer *input);
+void fast_export_blob_delta(uint32_t mode,
+                       uint32_t old_mode, const char *old_data,
+                       off_t len, struct line_buffer *input);
+
+/* If there is no such file at that rev, returns -1, errno == ENOENT. */
+int fast_export_ls_rev(uint32_t rev, const char *path,
+                       uint32_t *mode_out, struct strbuf *dataref_out);
+int fast_export_ls(const char *path,
+                       uint32_t *mode_out, struct strbuf *dataref_out);
 
 #endif
index c39038723ed4a90f99f70a694eae58ba488556b6..01fcb842f1dcc27517109f0d317a239d3493dc4a 100644 (file)
@@ -91,10 +91,10 @@ char *buffer_read_line(struct line_buffer *buf)
        return buf->line_buffer;
 }
 
-void buffer_read_binary(struct line_buffer *buf,
-                               struct strbuf *sb, uint32_t size)
+size_t buffer_read_binary(struct line_buffer *buf,
+                               struct strbuf *sb, size_t size)
 {
-       strbuf_fread(sb, size, buf->infile);
+       return strbuf_fread(sb, size, buf->infile);
 }
 
 off_t buffer_copy_bytes(struct line_buffer *buf, off_t nbytes)
index d0b22dda76e8a193f15fa5483abd217cdde101da..8901f214bafce3917025721137a85d827cd1c1a6 100644 (file)
@@ -23,7 +23,7 @@ long buffer_tmpfile_prepare_to_read(struct line_buffer *buf);
 int buffer_ferror(struct line_buffer *buf);
 char *buffer_read_line(struct line_buffer *buf);
 int buffer_read_char(struct line_buffer *buf);
-void buffer_read_binary(struct line_buffer *buf, struct strbuf *sb, uint32_t len);
+size_t buffer_read_binary(struct line_buffer *buf, struct strbuf *sb, size_t len);
 /* Returns number of bytes read (not necessarily written). */
 off_t buffer_copy_bytes(struct line_buffer *buf, off_t len);
 off_t buffer_skip_bytes(struct line_buffer *buf, off_t len);
diff --git a/vcs-svn/obj_pool.h b/vcs-svn/obj_pool.h
deleted file mode 100644 (file)
index deb6eb8..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed under a two-clause BSD-style license.
- * See LICENSE for details.
- */
-
-#ifndef OBJ_POOL_H_
-#define OBJ_POOL_H_
-
-#include "git-compat-util.h"
-
-#define MAYBE_UNUSED __attribute__((__unused__))
-
-#define obj_pool_gen(pre, obj_t, initial_capacity) \
-static struct { \
-       uint32_t committed; \
-       uint32_t size; \
-       uint32_t capacity; \
-       obj_t *base; \
-} pre##_pool = {0, 0, 0, NULL}; \
-static MAYBE_UNUSED uint32_t pre##_alloc(uint32_t count) \
-{ \
-       uint32_t offset; \
-       if (pre##_pool.size + count > pre##_pool.capacity) { \
-               while (pre##_pool.size + count > pre##_pool.capacity) \
-                       if (pre##_pool.capacity) \
-                               pre##_pool.capacity *= 2; \
-                       else \
-                               pre##_pool.capacity = initial_capacity; \
-               pre##_pool.base = realloc(pre##_pool.base, \
-                                       pre##_pool.capacity * sizeof(obj_t)); \
-       } \
-       offset = pre##_pool.size; \
-       pre##_pool.size += count; \
-       return offset; \
-} \
-static MAYBE_UNUSED void pre##_free(uint32_t count) \
-{ \
-       pre##_pool.size -= count; \
-} \
-static MAYBE_UNUSED uint32_t pre##_offset(obj_t *obj) \
-{ \
-       return obj == NULL ? ~0 : obj - pre##_pool.base; \
-} \
-static MAYBE_UNUSED obj_t *pre##_pointer(uint32_t offset) \
-{ \
-       return offset >= pre##_pool.size ? NULL : &pre##_pool.base[offset]; \
-} \
-static MAYBE_UNUSED void pre##_commit(void) \
-{ \
-       pre##_pool.committed = pre##_pool.size; \
-} \
-static MAYBE_UNUSED void pre##_reset(void) \
-{ \
-       free(pre##_pool.base); \
-       pre##_pool.base = NULL; \
-       pre##_pool.size = 0; \
-       pre##_pool.capacity = 0; \
-       pre##_pool.committed = 0; \
-}
-
-#endif
index a21d89de97404479ecf2ca46824f61acb7af56e6..67d27f0b6ca95ad62f95bb6c4fb38c58065dc8f0 100644 (file)
  */
 
 #include "git-compat-util.h"
-
-#include "string_pool.h"
+#include "strbuf.h"
 #include "repo_tree.h"
-#include "obj_pool.h"
 #include "fast_export.h"
 
-#include "trp.h"
-
-struct repo_dirent {
-       uint32_t name_offset;
-       struct trp_node children;
-       uint32_t mode;
-       uint32_t content_offset;
-};
-
-struct repo_dir {
-       struct trp_root entries;
-};
-
-struct repo_commit {
-       uint32_t root_dir_offset;
-};
-
-/* Memory pools for commit, dir and dirent */
-obj_pool_gen(commit, struct repo_commit, 4096)
-obj_pool_gen(dir, struct repo_dir, 4096)
-obj_pool_gen(dent, struct repo_dirent, 4096)
-
-static uint32_t active_commit;
-static uint32_t mark;
-
-static int repo_dirent_name_cmp(const void *a, const void *b);
-
-/* Treap for directory entries */
-trp_gen(static, dent_, struct repo_dirent, children, dent, repo_dirent_name_cmp)
-
-uint32_t next_blob_mark(void)
+const char *repo_read_path(const char *path, uint32_t *mode_out)
 {
-       return mark++;
-}
+       int err;
+       static struct strbuf buf = STRBUF_INIT;
 
-static struct repo_dir *repo_commit_root_dir(struct repo_commit *commit)
-{
-       return dir_pointer(commit->root_dir_offset);
-}
-
-static struct repo_dirent *repo_first_dirent(struct repo_dir *dir)
-{
-       return dent_first(&dir->entries);
-}
-
-static int repo_dirent_name_cmp(const void *a, const void *b)
-{
-       const struct repo_dirent *dent1 = a, *dent2 = b;
-       uint32_t a_offset = dent1->name_offset;
-       uint32_t b_offset = dent2->name_offset;
-       return (a_offset > b_offset) - (a_offset < b_offset);
-}
-
-static int repo_dirent_is_dir(struct repo_dirent *dent)
-{
-       return dent != NULL && dent->mode == REPO_MODE_DIR;
-}
-
-static struct repo_dir *repo_dir_from_dirent(struct repo_dirent *dent)
-{
-       if (!repo_dirent_is_dir(dent))
+       strbuf_reset(&buf);
+       err = fast_export_ls(path, mode_out, &buf);
+       if (err) {
+               if (errno != ENOENT)
+                       die_errno("BUG: unexpected fast_export_ls error");
+               /* Treat missing paths as directories. */
+               *mode_out = REPO_MODE_DIR;
                return NULL;
-       return dir_pointer(dent->content_offset);
-}
-
-static struct repo_dir *repo_clone_dir(struct repo_dir *orig_dir)
-{
-       uint32_t orig_o, new_o;
-       orig_o = dir_offset(orig_dir);
-       if (orig_o >= dir_pool.committed)
-               return orig_dir;
-       new_o = dir_alloc(1);
-       orig_dir = dir_pointer(orig_o);
-       *dir_pointer(new_o) = *orig_dir;
-       return dir_pointer(new_o);
-}
-
-static struct repo_dirent *repo_read_dirent(uint32_t revision,
-                                           const uint32_t *path)
-{
-       uint32_t name = 0;
-       struct repo_dirent *key = dent_pointer(dent_alloc(1));
-       struct repo_dir *dir = NULL;
-       struct repo_dirent *dent = NULL;
-       dir = repo_commit_root_dir(commit_pointer(revision));
-       while (~(name = *path++)) {
-               key->name_offset = name;
-               dent = dent_search(&dir->entries, key);
-               if (dent == NULL || !repo_dirent_is_dir(dent))
-                       break;
-               dir = repo_dir_from_dirent(dent);
        }
-       dent_free(1);
-       return dent;
+       return buf.buf;
 }
 
-static void repo_write_dirent(const uint32_t *path, uint32_t mode,
-                             uint32_t content_offset, uint32_t del)
+void repo_copy(uint32_t revision, const char *src, const char *dst)
 {
-       uint32_t name, revision, dir_o = ~0, parent_dir_o = ~0;
-       struct repo_dir *dir;
-       struct repo_dirent *key;
-       struct repo_dirent *dent = NULL;
-       revision = active_commit;
-       dir = repo_commit_root_dir(commit_pointer(revision));
-       dir = repo_clone_dir(dir);
-       commit_pointer(revision)->root_dir_offset = dir_offset(dir);
-       while (~(name = *path++)) {
-               parent_dir_o = dir_offset(dir);
-
-               key = dent_pointer(dent_alloc(1));
-               key->name_offset = name;
-
-               dent = dent_search(&dir->entries, key);
-               if (dent == NULL)
-                       dent = key;
-               else
-                       dent_free(1);
-
-               if (dent == key) {
-                       dent->mode = REPO_MODE_DIR;
-                       dent->content_offset = 0;
-                       dent = dent_insert(&dir->entries, dent);
-               }
-
-               if (dent_offset(dent) < dent_pool.committed) {
-                       dir_o = repo_dirent_is_dir(dent) ?
-                                       dent->content_offset : ~0;
-                       dent_remove(&dir->entries, dent);
-                       dent = dent_pointer(dent_alloc(1));
-                       dent->name_offset = name;
-                       dent->mode = REPO_MODE_DIR;
-                       dent->content_offset = dir_o;
-                       dent = dent_insert(&dir->entries, dent);
-               }
-
-               dir = repo_dir_from_dirent(dent);
-               dir = repo_clone_dir(dir);
-               dent->content_offset = dir_offset(dir);
-       }
-       if (dent == NULL)
+       int err;
+       uint32_t mode;
+       static struct strbuf data = STRBUF_INIT;
+
+       strbuf_reset(&data);
+       err = fast_export_ls_rev(revision, src, &mode, &data);
+       if (err) {
+               if (errno != ENOENT)
+                       die_errno("BUG: unexpected fast_export_ls_rev error");
+               fast_export_delete(dst);
                return;
-       dent->mode = mode;
-       dent->content_offset = content_offset;
-       if (del && ~parent_dir_o)
-               dent_remove(&dir_pointer(parent_dir_o)->entries, dent);
-}
-
-uint32_t repo_read_path(const uint32_t *path)
-{
-       uint32_t content_offset = 0;
-       struct repo_dirent *dent = repo_read_dirent(active_commit, path);
-       if (dent != NULL)
-               content_offset = dent->content_offset;
-       return content_offset;
-}
-
-uint32_t repo_read_mode(const uint32_t *path)
-{
-       struct repo_dirent *dent = repo_read_dirent(active_commit, path);
-       if (dent == NULL)
-               die("invalid dump: path to be modified is missing");
-       return dent->mode;
-}
-
-void repo_copy(uint32_t revision, const uint32_t *src, const uint32_t *dst)
-{
-       uint32_t mode = 0, content_offset = 0;
-       struct repo_dirent *src_dent;
-       src_dent = repo_read_dirent(revision, src);
-       if (src_dent != NULL) {
-               mode = src_dent->mode;
-               content_offset = src_dent->content_offset;
-               repo_write_dirent(dst, mode, content_offset, 0);
-       }
-}
-
-void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark)
-{
-       repo_write_dirent(path, mode, blob_mark, 0);
-}
-
-void repo_delete(uint32_t *path)
-{
-       repo_write_dirent(path, 0, 0, 1);
-}
-
-static void repo_git_add_r(uint32_t depth, uint32_t *path, struct repo_dir *dir);
-
-static void repo_git_add(uint32_t depth, uint32_t *path, struct repo_dirent *dent)
-{
-       if (repo_dirent_is_dir(dent))
-               repo_git_add_r(depth, path, repo_dir_from_dirent(dent));
-       else
-               fast_export_modify(depth, path,
-                                  dent->mode, dent->content_offset);
-}
-
-static void repo_git_add_r(uint32_t depth, uint32_t *path, struct repo_dir *dir)
-{
-       struct repo_dirent *de = repo_first_dirent(dir);
-       while (de) {
-               path[depth] = de->name_offset;
-               repo_git_add(depth + 1, path, de);
-               de = dent_next(&dir->entries, de);
-       }
-}
-
-static void repo_diff_r(uint32_t depth, uint32_t *path, struct repo_dir *dir1,
-                       struct repo_dir *dir2)
-{
-       struct repo_dirent *de1, *de2;
-       de1 = repo_first_dirent(dir1);
-       de2 = repo_first_dirent(dir2);
-
-       while (de1 && de2) {
-               if (de1->name_offset < de2->name_offset) {
-                       path[depth] = de1->name_offset;
-                       fast_export_delete(depth + 1, path);
-                       de1 = dent_next(&dir1->entries, de1);
-                       continue;
-               }
-               if (de1->name_offset > de2->name_offset) {
-                       path[depth] = de2->name_offset;
-                       repo_git_add(depth + 1, path, de2);
-                       de2 = dent_next(&dir2->entries, de2);
-                       continue;
-               }
-               path[depth] = de1->name_offset;
-
-               if (de1->mode == de2->mode &&
-                   de1->content_offset == de2->content_offset) {
-                       ; /* No change. */
-               } else if (repo_dirent_is_dir(de1) && repo_dirent_is_dir(de2)) {
-                       repo_diff_r(depth + 1, path,
-                                   repo_dir_from_dirent(de1),
-                                   repo_dir_from_dirent(de2));
-               } else if (!repo_dirent_is_dir(de1) && !repo_dirent_is_dir(de2)) {
-                       repo_git_add(depth + 1, path, de2);
-               } else {
-                       fast_export_delete(depth + 1, path);
-                       repo_git_add(depth + 1, path, de2);
-               }
-               de1 = dent_next(&dir1->entries, de1);
-               de2 = dent_next(&dir2->entries, de2);
-       }
-       while (de1) {
-               path[depth] = de1->name_offset;
-               fast_export_delete(depth + 1, path);
-               de1 = dent_next(&dir1->entries, de1);
-       }
-       while (de2) {
-               path[depth] = de2->name_offset;
-               repo_git_add(depth + 1, path, de2);
-               de2 = dent_next(&dir2->entries, de2);
-       }
-}
-
-static uint32_t path_stack[REPO_MAX_PATH_DEPTH];
-
-void repo_diff(uint32_t r1, uint32_t r2)
-{
-       repo_diff_r(0,
-                   path_stack,
-                   repo_commit_root_dir(commit_pointer(r1)),
-                   repo_commit_root_dir(commit_pointer(r2)));
-}
-
-void repo_commit(uint32_t revision, const char *author,
-               const struct strbuf *log, const char *uuid, const char *url,
-               unsigned long timestamp)
-{
-       fast_export_commit(revision, author, log, uuid, url, timestamp);
-       dent_commit();
-       dir_commit();
-       active_commit = commit_alloc(1);
-       commit_pointer(active_commit)->root_dir_offset =
-               commit_pointer(active_commit - 1)->root_dir_offset;
-}
-
-static void mark_init(void)
-{
-       uint32_t i;
-       mark = 0;
-       for (i = 0; i < dent_pool.size; i++)
-               if (!repo_dirent_is_dir(dent_pointer(i)) &&
-                   dent_pointer(i)->content_offset > mark)
-                       mark = dent_pointer(i)->content_offset;
-       mark++;
-}
-
-void repo_init(void)
-{
-       mark_init();
-       if (commit_pool.size == 0) {
-               /* Create empty tree for commit 0. */
-               commit_alloc(1);
-               commit_pointer(0)->root_dir_offset = dir_alloc(1);
-               dir_pointer(0)->entries.trp_root = ~0;
-               dir_commit();
        }
-       /* Preallocate next commit, ready for changes. */
-       active_commit = commit_alloc(1);
-       commit_pointer(active_commit)->root_dir_offset =
-               commit_pointer(active_commit - 1)->root_dir_offset;
+       fast_export_modify(dst, mode, data.buf);
 }
 
-void repo_reset(void)
+void repo_delete(const char *path)
 {
-       pool_reset();
-       commit_reset();
-       dir_reset();
-       dent_reset();
+       fast_export_delete(path);
 }
index 37bde2e37484071998edbe790d38d7b19eb24fb5..889c6a3c954375ab167f8b77b9f9a64dd0ccc2aa 100644 (file)
@@ -8,15 +8,11 @@ struct strbuf;
 #define REPO_MODE_EXE 0100755
 #define REPO_MODE_LNK 0120000
 
-#define REPO_MAX_PATH_LEN 4096
-#define REPO_MAX_PATH_DEPTH 1000
-
 uint32_t next_blob_mark(void);
-void repo_copy(uint32_t revision, const uint32_t *src, const uint32_t *dst);
-void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark);
-uint32_t repo_read_path(const uint32_t *path);
-uint32_t repo_read_mode(const uint32_t *path);
-void repo_delete(uint32_t *path);
+void repo_copy(uint32_t revision, const char *src, const char *dst);
+void repo_add(const char *path, uint32_t mode, uint32_t blob_mark);
+const char *repo_read_path(const char *path, uint32_t *mode_out);
+void repo_delete(const char *path);
 void repo_commit(uint32_t revision, const char *author,
                const struct strbuf *log, const char *uuid, const char *url,
                long unsigned timestamp);
diff --git a/vcs-svn/sliding_window.c b/vcs-svn/sliding_window.c
new file mode 100644 (file)
index 0000000..ec2707c
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "git-compat-util.h"
+#include "sliding_window.h"
+#include "line_buffer.h"
+#include "strbuf.h"
+
+static int input_error(struct line_buffer *file)
+{
+       if (!buffer_ferror(file))
+               return error("delta preimage ends early");
+       return error("cannot read delta preimage: %s", strerror(errno));
+}
+
+static int skip_or_whine(struct line_buffer *file, off_t gap)
+{
+       if (buffer_skip_bytes(file, gap) != gap)
+               return input_error(file);
+       return 0;
+}
+
+static int read_to_fill_or_whine(struct line_buffer *file,
+                               struct strbuf *buf, size_t width)
+{
+       buffer_read_binary(file, buf, width - buf->len);
+       if (buf->len != width)
+               return input_error(file);
+       return 0;
+}
+
+static int check_offset_overflow(off_t offset, uintmax_t len)
+{
+       if (len > maximum_signed_value_of_type(off_t))
+               return error("unrepresentable length in delta: "
+                               "%"PRIuMAX" > OFF_MAX", len);
+       if (signed_add_overflows(offset, (off_t) len))
+               return error("unrepresentable offset in delta: "
+                               "%"PRIuMAX" + %"PRIuMAX" > OFF_MAX",
+                               (uintmax_t) offset, len);
+       return 0;
+}
+
+int move_window(struct sliding_view *view, off_t off, size_t width)
+{
+       off_t file_offset;
+       assert(view);
+       assert(view->width <= view->buf.len);
+       assert(!check_offset_overflow(view->off, view->buf.len));
+
+       if (check_offset_overflow(off, width))
+               return -1;
+       if (off < view->off || off + width < view->off + view->width)
+               return error("invalid delta: window slides left");
+       if (view->max_off >= 0 && view->max_off < off + width)
+               return error("delta preimage ends early");
+
+       file_offset = view->off + view->buf.len;
+       if (off < file_offset) {
+               /* Move the overlapping region into place. */
+               strbuf_remove(&view->buf, 0, off - view->off);
+       } else {
+               /* Seek ahead to skip the gap. */
+               if (skip_or_whine(view->file, off - file_offset))
+                       return -1;
+               strbuf_setlen(&view->buf, 0);
+       }
+
+       if (view->buf.len > width)
+               ; /* Already read. */
+       else if (read_to_fill_or_whine(view->file, &view->buf, width))
+               return -1;
+
+       view->off = off;
+       view->width = width;
+       return 0;
+}
diff --git a/vcs-svn/sliding_window.h b/vcs-svn/sliding_window.h
new file mode 100644 (file)
index 0000000..b43a825
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef SLIDING_WINDOW_H_
+#define SLIDING_WINDOW_H_
+
+#include "strbuf.h"
+
+struct sliding_view {
+       struct line_buffer *file;
+       off_t off;
+       size_t width;
+       off_t max_off;  /* -1 means unlimited */
+       struct strbuf buf;
+};
+
+#define SLIDING_VIEW_INIT(input, len)  { (input), 0, 0, (len), STRBUF_INIT }
+
+extern int move_window(struct sliding_view *view, off_t off, size_t width);
+
+#endif
diff --git a/vcs-svn/string_pool.c b/vcs-svn/string_pool.c
deleted file mode 100644 (file)
index 8af8d54..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Licensed under a two-clause BSD-style license.
- * See LICENSE for details.
- */
-
-#include "git-compat-util.h"
-#include "trp.h"
-#include "obj_pool.h"
-#include "string_pool.h"
-
-static struct trp_root tree = { ~0 };
-
-struct node {
-       uint32_t offset;
-       struct trp_node children;
-};
-
-/* Two memory pools: one for struct node, and another for strings */
-obj_pool_gen(node, struct node, 4096)
-obj_pool_gen(string, char, 4096)
-
-static char *node_value(struct node *node)
-{
-       return node ? string_pointer(node->offset) : NULL;
-}
-
-static int node_cmp(struct node *a, struct node *b)
-{
-       return strcmp(node_value(a), node_value(b));
-}
-
-/* Build a Treap from the node structure (a trp_node w/ offset) */
-trp_gen(static, tree_, struct node, children, node, node_cmp)
-
-const char *pool_fetch(uint32_t entry)
-{
-       return node_value(node_pointer(entry));
-}
-
-uint32_t pool_intern(const char *key)
-{
-       /* Canonicalize key */
-       struct node *match = NULL, *node;
-       uint32_t key_len;
-       if (key == NULL)
-               return ~0;
-       key_len = strlen(key) + 1;
-       node = node_pointer(node_alloc(1));
-       node->offset = string_alloc(key_len);
-       strcpy(node_value(node), key);
-       match = tree_search(&tree, node);
-       if (!match) {
-               tree_insert(&tree, node);
-       } else {
-               node_free(1);
-               string_free(key_len);
-               node = match;
-       }
-       return node_offset(node);
-}
-
-uint32_t pool_tok_r(char *str, const char *delim, char **saveptr)
-{
-       char *token = strtok_r(str, delim, saveptr);
-       return token ? pool_intern(token) : ~0;
-}
-
-void pool_print_seq(uint32_t len, uint32_t *seq, char delim, FILE *stream)
-{
-       uint32_t i;
-       for (i = 0; i < len && ~seq[i]; i++) {
-               fputs(pool_fetch(seq[i]), stream);
-               if (i < len - 1 && ~seq[i + 1])
-                       fputc(delim, stream);
-       }
-}
-
-uint32_t pool_tok_seq(uint32_t sz, uint32_t *seq, const char *delim, char *str)
-{
-       char *context = NULL;
-       uint32_t token = ~0;
-       uint32_t length;
-
-       if (sz == 0)
-               return ~0;
-       if (str)
-               token = pool_tok_r(str, delim, &context);
-       for (length = 0; length < sz; length++) {
-               seq[length] = token;
-               if (token == ~0)
-                       return length;
-               token = pool_tok_r(NULL, delim, &context);
-       }
-       seq[sz - 1] = ~0;
-       return sz;
-}
-
-void pool_reset(void)
-{
-       node_reset();
-       string_reset();
-}
diff --git a/vcs-svn/string_pool.h b/vcs-svn/string_pool.h
deleted file mode 100644 (file)
index 222fb66..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-#ifndef STRING_POOL_H_
-#define STRING_POOL_H_
-
-uint32_t pool_intern(const char *key);
-const char *pool_fetch(uint32_t entry);
-uint32_t pool_tok_r(char *str, const char *delim, char **saveptr);
-void pool_print_seq(uint32_t len, uint32_t *seq, char delim, FILE *stream);
-uint32_t pool_tok_seq(uint32_t sz, uint32_t *seq, const char *delim, char *str);
-void pool_reset(void);
-
-#endif
diff --git a/vcs-svn/string_pool.txt b/vcs-svn/string_pool.txt
deleted file mode 100644 (file)
index 1b41f15..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-string_pool API
-===============
-
-The string_pool API provides facilities for replacing strings
-with integer keys that can be more easily compared and stored.
-The facilities are designed so that one could teach Git without
-too much trouble to store the information needed for these keys to
-remain valid over multiple executions.
-
-Functions
----------
-
-pool_intern::
-       Include a string in the string pool and get its key.
-       If that string is already in the pool, retrieves its
-       existing key.
-
-pool_fetch::
-       Retrieve the string associated to a given key.
-
-pool_tok_r::
-       Extract the key of the next token from a string.
-       Interface mimics strtok_r.
-
-pool_print_seq::
-       Print a sequence of strings named by key to a file, using the
-       specified delimiter to separate them.
-
-       If NULL (key ~0) appears in the sequence, the sequence ends
-       early.
-
-pool_tok_seq::
-       Split a string into tokens, storing the keys of segments
-       into a caller-provided array.
-
-       Unless sz is 0, the array will always be ~0-terminated.
-       If there is not enough room for all the tokens, the
-       array holds as many tokens as fit in the entries before
-       the terminating ~0.  Return value is the index after the
-       last token, or sz if the tokens did not fit.
-
-pool_reset::
-       Deallocate storage for the string pool.
diff --git a/vcs-svn/svndiff.c b/vcs-svn/svndiff.c
new file mode 100644 (file)
index 0000000..1647c1a
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "git-compat-util.h"
+#include "sliding_window.h"
+#include "line_buffer.h"
+#include "svndiff.h"
+
+/*
+ * svndiff0 applier
+ *
+ * See http://svn.apache.org/repos/asf/subversion/trunk/notes/svndiff.
+ *
+ * svndiff0 ::= 'SVN\0' window*
+ * window ::= int int int int int instructions inline_data;
+ * instructions ::= instruction*;
+ * instruction ::= view_selector int int
+ *   | copyfrom_data int
+ *   | packed_view_selector int
+ *   | packed_copyfrom_data
+ *   ;
+ * view_selector ::= copyfrom_source
+ *   | copyfrom_target
+ *   ;
+ * copyfrom_source ::= # binary 00 000000;
+ * copyfrom_target ::= # binary 01 000000;
+ * copyfrom_data ::= # binary 10 000000;
+ * packed_view_selector ::= # view_selector OR-ed with 6 bit value;
+ * packed_copyfrom_data ::= # copyfrom_data OR-ed with 6 bit value;
+ * int ::= highdigit* lowdigit;
+ * highdigit ::= # binary 1000 0000 OR-ed with 7 bit value;
+ * lowdigit ::= # 7 bit value;
+ */
+
+#define INSN_MASK      0xc0
+#define INSN_COPYFROM_SOURCE   0x00
+#define INSN_COPYFROM_TARGET   0x40
+#define INSN_COPYFROM_DATA     0x80
+#define OPERAND_MASK   0x3f
+
+#define VLI_CONTINUE   0x80
+#define VLI_DIGIT_MASK 0x7f
+#define VLI_BITS_PER_DIGIT 7
+
+struct window {
+       struct sliding_view *in;
+       struct strbuf out;
+       struct strbuf instructions;
+       struct strbuf data;
+};
+
+#define WINDOW_INIT(w) { (w), STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }
+
+static void window_release(struct window *ctx)
+{
+       strbuf_release(&ctx->out);
+       strbuf_release(&ctx->instructions);
+       strbuf_release(&ctx->data);
+}
+
+static int write_strbuf(struct strbuf *sb, FILE *out)
+{
+       if (fwrite(sb->buf, 1, sb->len, out) == sb->len)        /* Success. */
+               return 0;
+       return error("cannot write delta postimage: %s", strerror(errno));
+}
+
+static int error_short_read(struct line_buffer *input)
+{
+       if (buffer_ferror(input))
+               return error("error reading delta: %s", strerror(errno));
+       return error("invalid delta: unexpected end of file");
+}
+
+static int read_chunk(struct line_buffer *delta, off_t *delta_len,
+                     struct strbuf *buf, size_t len)
+{
+       strbuf_reset(buf);
+       if (len > *delta_len ||
+           buffer_read_binary(delta, buf, len) != len)
+               return error_short_read(delta);
+       *delta_len -= buf->len;
+       return 0;
+}
+
+static int read_magic(struct line_buffer *in, off_t *len)
+{
+       static const char magic[] = {'S', 'V', 'N', '\0'};
+       struct strbuf sb = STRBUF_INIT;
+
+       if (read_chunk(in, len, &sb, sizeof(magic))) {
+               strbuf_release(&sb);
+               return -1;
+       }
+       if (memcmp(sb.buf, magic, sizeof(magic))) {
+               strbuf_release(&sb);
+               return error("invalid delta: unrecognized file type");
+       }
+       strbuf_release(&sb);
+       return 0;
+}
+
+static int read_int(struct line_buffer *in, uintmax_t *result, off_t *len)
+{
+       uintmax_t rv = 0;
+       off_t sz;
+       for (sz = *len; sz; sz--) {
+               const int ch = buffer_read_char(in);
+               if (ch == EOF)
+                       break;
+
+               rv <<= VLI_BITS_PER_DIGIT;
+               rv += (ch & VLI_DIGIT_MASK);
+               if (ch & VLI_CONTINUE)
+                       continue;
+
+               *result = rv;
+               *len = sz - 1;
+               return 0;
+       }
+       return error_short_read(in);
+}
+
+static int parse_int(const char **buf, size_t *result, const char *end)
+{
+       size_t rv = 0;
+       const char *pos;
+       for (pos = *buf; pos != end; pos++) {
+               unsigned char ch = *pos;
+
+               rv <<= VLI_BITS_PER_DIGIT;
+               rv += (ch & VLI_DIGIT_MASK);
+               if (ch & VLI_CONTINUE)
+                       continue;
+
+               *result = rv;
+               *buf = pos + 1;
+               return 0;
+       }
+       return error("invalid delta: unexpected end of instructions section");
+}
+
+static int read_offset(struct line_buffer *in, off_t *result, off_t *len)
+{
+       uintmax_t val;
+       if (read_int(in, &val, len))
+               return -1;
+       if (val > maximum_signed_value_of_type(off_t))
+               return error("unrepresentable offset in delta: %"PRIuMAX"", val);
+       *result = val;
+       return 0;
+}
+
+static int read_length(struct line_buffer *in, size_t *result, off_t *len)
+{
+       uintmax_t val;
+       if (read_int(in, &val, len))
+               return -1;
+       if (val > SIZE_MAX)
+               return error("unrepresentable length in delta: %"PRIuMAX"", val);
+       *result = val;
+       return 0;
+}
+
+static int copyfrom_source(struct window *ctx, const char **instructions,
+                          size_t nbytes, const char *insns_end)
+{
+       size_t offset;
+       if (parse_int(instructions, &offset, insns_end))
+               return -1;
+       if (unsigned_add_overflows(offset, nbytes) ||
+           offset + nbytes > ctx->in->width)
+               return error("invalid delta: copies source data outside view");
+       strbuf_add(&ctx->out, ctx->in->buf.buf + offset, nbytes);
+       return 0;
+}
+
+static int copyfrom_target(struct window *ctx, const char **instructions,
+                          size_t nbytes, const char *instructions_end)
+{
+       size_t offset;
+       if (parse_int(instructions, &offset, instructions_end))
+               return -1;
+       if (offset >= ctx->out.len)
+               return error("invalid delta: copies from the future");
+       for (; nbytes > 0; nbytes--)
+               strbuf_addch(&ctx->out, ctx->out.buf[offset++]);
+       return 0;
+}
+
+static int copyfrom_data(struct window *ctx, size_t *data_pos, size_t nbytes)
+{
+       const size_t pos = *data_pos;
+       if (unsigned_add_overflows(pos, nbytes) ||
+           pos + nbytes > ctx->data.len)
+               return error("invalid delta: copies unavailable inline data");
+       strbuf_add(&ctx->out, ctx->data.buf + pos, nbytes);
+       *data_pos += nbytes;
+       return 0;
+}
+
+static int parse_first_operand(const char **buf, size_t *out, const char *end)
+{
+       size_t result = (unsigned char) *(*buf)++ & OPERAND_MASK;
+       if (result) {   /* immediate operand */
+               *out = result;
+               return 0;
+       }
+       return parse_int(buf, out, end);
+}
+
+static int execute_one_instruction(struct window *ctx,
+                               const char **instructions, size_t *data_pos)
+{
+       unsigned int instruction;
+       const char *insns_end = ctx->instructions.buf + ctx->instructions.len;
+       size_t nbytes;
+       assert(ctx);
+       assert(instructions && *instructions);
+       assert(data_pos);
+
+       instruction = (unsigned char) **instructions;
+       if (parse_first_operand(instructions, &nbytes, insns_end))
+               return -1;
+       switch (instruction & INSN_MASK) {
+       case INSN_COPYFROM_SOURCE:
+               return copyfrom_source(ctx, instructions, nbytes, insns_end);
+       case INSN_COPYFROM_TARGET:
+               return copyfrom_target(ctx, instructions, nbytes, insns_end);
+       case INSN_COPYFROM_DATA:
+               return copyfrom_data(ctx, data_pos, nbytes);
+       default:
+               return error("invalid delta: unrecognized instruction");
+       }
+}
+
+static int apply_window_in_core(struct window *ctx)
+{
+       const char *instructions;
+       size_t data_pos = 0;
+
+       /*
+        * Fill ctx->out.buf using data from the source, target,
+        * and inline data views.
+        */
+       for (instructions = ctx->instructions.buf;
+            instructions != ctx->instructions.buf + ctx->instructions.len;
+            )
+               if (execute_one_instruction(ctx, &instructions, &data_pos))
+                       return -1;
+       if (data_pos != ctx->data.len)
+               return error("invalid delta: does not copy all inline data");
+       return 0;
+}
+
+static int apply_one_window(struct line_buffer *delta, off_t *delta_len,
+                           struct sliding_view *preimage, FILE *out)
+{
+       struct window ctx = WINDOW_INIT(preimage);
+       size_t out_len;
+       size_t instructions_len;
+       size_t data_len;
+       assert(delta_len);
+
+       /* "source view" offset and length already handled; */
+       if (read_length(delta, &out_len, delta_len) ||
+           read_length(delta, &instructions_len, delta_len) ||
+           read_length(delta, &data_len, delta_len) ||
+           read_chunk(delta, delta_len, &ctx.instructions, instructions_len) ||
+           read_chunk(delta, delta_len, &ctx.data, data_len))
+               goto error_out;
+       strbuf_grow(&ctx.out, out_len);
+       if (apply_window_in_core(&ctx))
+               goto error_out;
+       if (ctx.out.len != out_len) {
+               error("invalid delta: incorrect postimage length");
+               goto error_out;
+       }
+       if (write_strbuf(&ctx.out, out))
+               goto error_out;
+       window_release(&ctx);
+       return 0;
+error_out:
+       window_release(&ctx);
+       return -1;
+}
+
+int svndiff0_apply(struct line_buffer *delta, off_t delta_len,
+                       struct sliding_view *preimage, FILE *postimage)
+{
+       assert(delta && preimage && postimage);
+
+       if (read_magic(delta, &delta_len))
+               return -1;
+       while (delta_len) {     /* For each window: */
+               off_t pre_off = pre_off; /* stupid GCC... */
+               size_t pre_len;
+
+               if (read_offset(delta, &pre_off, &delta_len) ||
+                   read_length(delta, &pre_len, &delta_len) ||
+                   move_window(preimage, pre_off, pre_len) ||
+                   apply_one_window(delta, &delta_len, preimage, postimage))
+                       return -1;
+       }
+       return 0;
+}
diff --git a/vcs-svn/svndiff.h b/vcs-svn/svndiff.h
new file mode 100644 (file)
index 0000000..74eb464
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef SVNDIFF_H_
+#define SVNDIFF_H_
+
+struct line_buffer;
+struct sliding_view;
+
+extern int svndiff0_apply(struct line_buffer *delta, off_t delta_len,
+               struct sliding_view *preimage, FILE *postimage);
+
+#endif
index bc792223b2638bce4196eb0dc6626beb32f48da4..644fdc71ba8c665f5dfadd552c3da63f4d8498e9 100644 (file)
@@ -11,7 +11,6 @@
 #include "repo_tree.h"
 #include "fast_export.h"
 #include "line_buffer.h"
-#include "string_pool.h"
 #include "strbuf.h"
 #include "svndump.h"
 
  */
 #define constcmp(s, ref) memcmp(s, ref, sizeof(ref) - 1)
 
+#define REPORT_FILENO 3
+
 #define NODEACT_REPLACE 4
 #define NODEACT_DELETE 3
 #define NODEACT_ADD 2
 #define NODEACT_CHANGE 1
 #define NODEACT_UNKNOWN 0
 
-#define DUMP_CTX 0
-#define REV_CTX  1
-#define NODE_CTX 2
+/* States: */
+#define DUMP_CTX 0     /* dump metadata */
+#define REV_CTX  1     /* revision metadata */
+#define NODE_CTX 2     /* node metadata */
+#define INTERNODE_CTX 3        /* between nodes */
 
 #define LENGTH_UNKNOWN (~0)
 #define DATE_RFC2822_LEN 31
@@ -37,8 +40,9 @@
 static struct line_buffer input = LINE_BUFFER_INIT;
 
 static struct {
-       uint32_t action, propLength, textLength, srcRev, type;
-       uint32_t src[REPO_MAX_PATH_DEPTH], dst[REPO_MAX_PATH_DEPTH];
+       uint32_t action, propLength, srcRev, type;
+       off_t text_length;
+       struct strbuf src, dst;
        uint32_t text_delta, prop_delta;
 } node_ctx;
 
@@ -58,10 +62,12 @@ static void reset_node_ctx(char *fname)
        node_ctx.type = 0;
        node_ctx.action = NODEACT_UNKNOWN;
        node_ctx.propLength = LENGTH_UNKNOWN;
-       node_ctx.textLength = LENGTH_UNKNOWN;
-       node_ctx.src[0] = ~0;
+       node_ctx.text_length = -1;
+       strbuf_reset(&node_ctx.src);
        node_ctx.srcRev = 0;
-       pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.dst, "/", fname);
+       strbuf_reset(&node_ctx.dst);
+       if (fname)
+               strbuf_addstr(&node_ctx.dst, fname);
        node_ctx.text_delta = 0;
        node_ctx.prop_delta = 0;
 }
@@ -202,28 +208,32 @@ static void read_props(void)
 
 static void handle_node(void)
 {
-       uint32_t mark = 0;
        const uint32_t type = node_ctx.type;
        const int have_props = node_ctx.propLength != LENGTH_UNKNOWN;
-       const int have_text = node_ctx.textLength != LENGTH_UNKNOWN;
+       const int have_text = node_ctx.text_length != -1;
+       /*
+        * Old text for this node:
+        *  NULL        - directory or bug
+        *  empty_blob  - empty
+        *  "<dataref>" - data retrievable from fast-import
+        */
+       static const char *const empty_blob = "::empty::";
+       const char *old_data = NULL;
+       uint32_t old_mode = REPO_MODE_BLB;
 
-       if (node_ctx.text_delta)
-               die("text deltas not supported");
-       if (have_text)
-               mark = next_blob_mark();
        if (node_ctx.action == NODEACT_DELETE) {
                if (have_text || have_props || node_ctx.srcRev)
                        die("invalid dump: deletion node has "
                                "copyfrom info, text, or properties");
-               repo_delete(node_ctx.dst);
+               repo_delete(node_ctx.dst.buf);
                return;
        }
        if (node_ctx.action == NODEACT_REPLACE) {
-               repo_delete(node_ctx.dst);
+               repo_delete(node_ctx.dst.buf);
                node_ctx.action = NODEACT_ADD;
        }
        if (node_ctx.srcRev) {
-               repo_copy(node_ctx.srcRev, node_ctx.src, node_ctx.dst);
+               repo_copy(node_ctx.srcRev, node_ctx.src.buf, node_ctx.dst.buf);
                if (node_ctx.action == NODEACT_ADD)
                        node_ctx.action = NODEACT_CHANGE;
        }
@@ -231,23 +241,27 @@ static void handle_node(void)
                die("invalid dump: directories cannot have text attached");
 
        /*
-        * Decide on the new content (mark) and mode (node_ctx.type).
+        * Find old content (old_data) and decide on the new mode.
         */
-       if (node_ctx.action == NODEACT_CHANGE && !~*node_ctx.dst) {
+       if (node_ctx.action == NODEACT_CHANGE && !*node_ctx.dst.buf) {
                if (type != REPO_MODE_DIR)
                        die("invalid dump: root of tree is not a regular file");
+               old_data = NULL;
        } else if (node_ctx.action == NODEACT_CHANGE) {
                uint32_t mode;
-               if (!have_text)
-                       mark = repo_read_path(node_ctx.dst);
-               mode = repo_read_mode(node_ctx.dst);
+               old_data = repo_read_path(node_ctx.dst.buf, &mode);
                if (mode == REPO_MODE_DIR && type != REPO_MODE_DIR)
                        die("invalid dump: cannot modify a directory into a file");
                if (mode != REPO_MODE_DIR && type == REPO_MODE_DIR)
                        die("invalid dump: cannot modify a file into a directory");
                node_ctx.type = mode;
+               old_mode = mode;
        } else if (node_ctx.action == NODEACT_ADD) {
-               if (!have_text && type != REPO_MODE_DIR)
+               if (type == REPO_MODE_DIR)
+                       old_data = NULL;
+               else if (have_text)
+                       old_data = empty_blob;
+               else
                        die("invalid dump: adds node without text");
        } else {
                die("invalid dump: Node-path block lacks Node-action");
@@ -266,18 +280,39 @@ static void handle_node(void)
        /*
         * Save the result.
         */
-       repo_add(node_ctx.dst, node_ctx.type, mark);
-       if (have_text)
-               fast_export_blob(node_ctx.type, mark,
-                                node_ctx.textLength, &input);
+       if (type == REPO_MODE_DIR)      /* directories are not tracked. */
+               return;
+       assert(old_data);
+       if (old_data == empty_blob)
+               /* For the fast_export_* functions, NULL means empty. */
+               old_data = NULL;
+       if (!have_text) {
+               fast_export_modify(node_ctx.dst.buf, node_ctx.type, old_data);
+               return;
+       }
+       if (!node_ctx.text_delta) {
+               fast_export_modify(node_ctx.dst.buf, node_ctx.type, "inline");
+               fast_export_data(node_ctx.type, node_ctx.text_length, &input);
+               return;
+       }
+       fast_export_modify(node_ctx.dst.buf, node_ctx.type, "inline");
+       fast_export_blob_delta(node_ctx.type, old_mode, old_data,
+                               node_ctx.text_length, &input);
 }
 
-static void handle_revision(void)
+static void begin_revision(void)
+{
+       if (!rev_ctx.revision)  /* revision 0 gets no git commit. */
+               return;
+       fast_export_begin_commit(rev_ctx.revision, rev_ctx.author.buf,
+               &rev_ctx.log, dump_ctx.uuid.buf, dump_ctx.url.buf,
+               rev_ctx.timestamp);
+}
+
+static void end_revision(void)
 {
        if (rev_ctx.revision)
-               repo_commit(rev_ctx.revision, rev_ctx.author.buf,
-                       &rev_ctx.log, dump_ctx.uuid.buf, dump_ctx.url.buf,
-                       rev_ctx.timestamp);
+               fast_export_end_commit(rev_ctx.revision);
 }
 
 void svndump_read(const char *url)
@@ -318,8 +353,10 @@ void svndump_read(const char *url)
                                continue;
                        if (active_ctx == NODE_CTX)
                                handle_node();
+                       if (active_ctx == REV_CTX)
+                               begin_revision();
                        if (active_ctx != DUMP_CTX)
-                               handle_revision();
+                               end_revision();
                        active_ctx = REV_CTX;
                        reset_rev_ctx(atoi(val));
                        break;
@@ -329,6 +366,8 @@ void svndump_read(const char *url)
                        if (!constcmp(t + strlen("Node-"), "path")) {
                                if (active_ctx == NODE_CTX)
                                        handle_node();
+                               if (active_ctx == REV_CTX)
+                                       begin_revision();
                                active_ctx = NODE_CTX;
                                reset_node_ctx(val);
                                break;
@@ -361,7 +400,8 @@ void svndump_read(const char *url)
                case sizeof("Node-copyfrom-path"):
                        if (constcmp(t, "Node-copyfrom-path"))
                                continue;
-                       pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.src, "/", val);
+                       strbuf_reset(&node_ctx.src);
+                       strbuf_addstr(&node_ctx.src, val);
                        break;
                case sizeof("Node-copyfrom-rev"):
                        if (constcmp(t, "Node-copyfrom-rev"))
@@ -370,7 +410,15 @@ void svndump_read(const char *url)
                        break;
                case sizeof("Text-content-length"):
                        if (!constcmp(t, "Text-content-length")) {
-                               node_ctx.textLength = atoi(val);
+                               char *end;
+                               uintmax_t textlen;
+
+                               textlen = strtoumax(val, &end, 10);
+                               if (!isdigit(*val) || *end)
+                                       die("invalid dump: non-numeric length %s", val);
+                               if (textlen > maximum_signed_value_of_type(off_t))
+                                       die("unrepresentable length in dump: %s", val);
+                               node_ctx.text_length = (off_t) textlen;
                                break;
                        }
                        if (constcmp(t, "Prop-content-length"))
@@ -399,7 +447,7 @@ void svndump_read(const char *url)
                                read_props();
                        } else if (active_ctx == NODE_CTX) {
                                handle_node();
-                               active_ctx = REV_CTX;
+                               active_ctx = INTERNODE_CTX;
                        } else {
                                fprintf(stderr, "Unexpected content length header: %"PRIu32"\n", len);
                                if (buffer_skip_bytes(&input, len) != len)
@@ -411,19 +459,23 @@ void svndump_read(const char *url)
                die_short_read();
        if (active_ctx == NODE_CTX)
                handle_node();
+       if (active_ctx == REV_CTX)
+               begin_revision();
        if (active_ctx != DUMP_CTX)
-               handle_revision();
+               end_revision();
 }
 
 int svndump_init(const char *filename)
 {
        if (buffer_init(&input, filename))
                return error("cannot open %s: %s", filename, strerror(errno));
-       repo_init();
+       fast_export_init(REPORT_FILENO);
        strbuf_init(&dump_ctx.uuid, 4096);
        strbuf_init(&dump_ctx.url, 4096);
        strbuf_init(&rev_ctx.log, 4096);
        strbuf_init(&rev_ctx.author, 4096);
+       strbuf_init(&node_ctx.src, 4096);
+       strbuf_init(&node_ctx.dst, 4096);
        reset_dump_ctx(NULL);
        reset_rev_ctx(0);
        reset_node_ctx(NULL);
@@ -432,11 +484,13 @@ int svndump_init(const char *filename)
 
 void svndump_deinit(void)
 {
-       repo_reset();
+       fast_export_deinit();
        reset_dump_ctx(NULL);
        reset_rev_ctx(0);
        reset_node_ctx(NULL);
        strbuf_release(&rev_ctx.log);
+       strbuf_release(&node_ctx.src);
+       strbuf_release(&node_ctx.dst);
        if (buffer_deinit(&input))
                fprintf(stderr, "Input error\n");
        if (ferror(stdout))
@@ -445,8 +499,8 @@ void svndump_deinit(void)
 
 void svndump_reset(void)
 {
+       fast_export_reset();
        buffer_reset(&input);
-       repo_reset();
        strbuf_release(&dump_ctx.uuid);
        strbuf_release(&dump_ctx.url);
        strbuf_release(&rev_ctx.log);
diff --git a/vcs-svn/trp.h b/vcs-svn/trp.h
deleted file mode 100644 (file)
index c32b918..0000000
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * C macro implementation of treaps.
- *
- * Usage:
- *   #include <stdint.h>
- *   #include "trp.h"
- *   trp_gen(...)
- *
- * Licensed under a two-clause BSD-style license.
- * See LICENSE for details.
- */
-
-#ifndef TRP_H_
-#define TRP_H_
-
-#define MAYBE_UNUSED __attribute__((__unused__))
-
-/* Node structure. */
-struct trp_node {
-       uint32_t trpn_left;
-       uint32_t trpn_right;
-};
-
-/* Root structure. */
-struct trp_root {
-       uint32_t trp_root;
-};
-
-/* Pointer/Offset conversion. */
-#define trpn_pointer(a_base, a_offset) (a_base##_pointer(a_offset))
-#define trpn_offset(a_base, a_pointer) (a_base##_offset(a_pointer))
-#define trpn_modify(a_base, a_offset) \
-       do { \
-               if ((a_offset) < a_base##_pool.committed) { \
-                       uint32_t old_offset = (a_offset);\
-                       (a_offset) = a_base##_alloc(1); \
-                       *trpn_pointer(a_base, a_offset) = \
-                               *trpn_pointer(a_base, old_offset); \
-               } \
-       } while (0)
-
-/* Left accessors. */
-#define trp_left_get(a_base, a_field, a_node) \
-       (trpn_pointer(a_base, a_node)->a_field.trpn_left)
-#define trp_left_set(a_base, a_field, a_node, a_left) \
-       do { \
-               trpn_modify(a_base, a_node); \
-               trp_left_get(a_base, a_field, a_node) = (a_left); \
-       } while (0)
-
-/* Right accessors. */
-#define trp_right_get(a_base, a_field, a_node) \
-       (trpn_pointer(a_base, a_node)->a_field.trpn_right)
-#define trp_right_set(a_base, a_field, a_node, a_right) \
-       do { \
-               trpn_modify(a_base, a_node); \
-               trp_right_get(a_base, a_field, a_node) = (a_right); \
-       } while (0)
-
-/*
- * Fibonacci hash function.
- * The multiplier is the nearest prime to (2^32 times (√5 - 1)/2).
- * See Knuth §6.4: volume 3, 3rd ed, p518.
- */
-#define trpn_hash(a_node) (uint32_t) (2654435761u * (a_node))
-
-/* Priority accessors. */
-#define trp_prio_get(a_node) trpn_hash(a_node)
-
-/* Node initializer. */
-#define trp_node_new(a_base, a_field, a_node) \
-       do { \
-               trp_left_set(a_base, a_field, (a_node), ~0); \
-               trp_right_set(a_base, a_field, (a_node), ~0); \
-       } while (0)
-
-/* Internal utility macros. */
-#define trpn_first(a_base, a_field, a_root, r_node) \
-       do { \
-               (r_node) = (a_root); \
-               if ((r_node) == ~0) \
-                       return NULL; \
-               while (~trp_left_get(a_base, a_field, (r_node))) \
-                       (r_node) = trp_left_get(a_base, a_field, (r_node)); \
-       } while (0)
-
-#define trpn_rotate_left(a_base, a_field, a_node, r_node) \
-       do { \
-               (r_node) = trp_right_get(a_base, a_field, (a_node)); \
-               trp_right_set(a_base, a_field, (a_node), \
-                       trp_left_get(a_base, a_field, (r_node))); \
-               trp_left_set(a_base, a_field, (r_node), (a_node)); \
-       } while (0)
-
-#define trpn_rotate_right(a_base, a_field, a_node, r_node) \
-       do { \
-               (r_node) = trp_left_get(a_base, a_field, (a_node)); \
-               trp_left_set(a_base, a_field, (a_node), \
-                       trp_right_get(a_base, a_field, (r_node))); \
-               trp_right_set(a_base, a_field, (r_node), (a_node)); \
-       } while (0)
-
-#define trp_gen(a_attr, a_pre, a_type, a_field, a_base, a_cmp) \
-a_attr a_type MAYBE_UNUSED *a_pre##first(struct trp_root *treap) \
-{ \
-       uint32_t ret; \
-       trpn_first(a_base, a_field, treap->trp_root, ret); \
-       return trpn_pointer(a_base, ret); \
-} \
-a_attr a_type MAYBE_UNUSED *a_pre##next(struct trp_root *treap, a_type *node) \
-{ \
-       uint32_t ret; \
-       uint32_t offset = trpn_offset(a_base, node); \
-       if (~trp_right_get(a_base, a_field, offset)) { \
-               trpn_first(a_base, a_field, \
-                       trp_right_get(a_base, a_field, offset), ret); \
-       } else { \
-               uint32_t tnode = treap->trp_root; \
-               ret = ~0; \
-               while (1) { \
-                       int cmp = (a_cmp)(trpn_pointer(a_base, offset), \
-                               trpn_pointer(a_base, tnode)); \
-                       if (cmp < 0) { \
-                               ret = tnode; \
-                               tnode = trp_left_get(a_base, a_field, tnode); \
-                       } else if (cmp > 0) { \
-                               tnode = trp_right_get(a_base, a_field, tnode); \
-                       } else { \
-                               break; \
-                       } \
-               } \
-       } \
-       return trpn_pointer(a_base, ret); \
-} \
-a_attr a_type MAYBE_UNUSED *a_pre##search(struct trp_root *treap, a_type *key) \
-{ \
-       int cmp; \
-       uint32_t ret = treap->trp_root; \
-       while (~ret && (cmp = (a_cmp)(key, trpn_pointer(a_base, ret)))) { \
-               if (cmp < 0) { \
-                       ret = trp_left_get(a_base, a_field, ret); \
-               } else { \
-                       ret = trp_right_get(a_base, a_field, ret); \
-               } \
-       } \
-       return trpn_pointer(a_base, ret); \
-} \
-a_attr a_type MAYBE_UNUSED *a_pre##nsearch(struct trp_root *treap, a_type *key) \
-{ \
-       int cmp; \
-       uint32_t ret = treap->trp_root; \
-       while (~ret && (cmp = (a_cmp)(key, trpn_pointer(a_base, ret)))) { \
-               if (cmp < 0) { \
-                       if (!~trp_left_get(a_base, a_field, ret)) \
-                               break; \
-                       ret = trp_left_get(a_base, a_field, ret); \
-               } else { \
-                       ret = trp_right_get(a_base, a_field, ret); \
-               } \
-       } \
-       return trpn_pointer(a_base, ret); \
-} \
-a_attr uint32_t MAYBE_UNUSED a_pre##insert_recurse(uint32_t cur_node, uint32_t ins_node) \
-{ \
-       if (cur_node == ~0) { \
-               return ins_node; \
-       } else { \
-               uint32_t ret; \
-               int cmp = (a_cmp)(trpn_pointer(a_base, ins_node), \
-                                       trpn_pointer(a_base, cur_node)); \
-               if (cmp < 0) { \
-                       uint32_t left = a_pre##insert_recurse( \
-                               trp_left_get(a_base, a_field, cur_node), ins_node); \
-                       trp_left_set(a_base, a_field, cur_node, left); \
-                       if (trp_prio_get(left) < trp_prio_get(cur_node)) \
-                               trpn_rotate_right(a_base, a_field, cur_node, ret); \
-                       else \
-                               ret = cur_node; \
-               } else { \
-                       uint32_t right = a_pre##insert_recurse( \
-                               trp_right_get(a_base, a_field, cur_node), ins_node); \
-                       trp_right_set(a_base, a_field, cur_node, right); \
-                       if (trp_prio_get(right) < trp_prio_get(cur_node)) \
-                               trpn_rotate_left(a_base, a_field, cur_node, ret); \
-                       else \
-                               ret = cur_node; \
-               } \
-               return ret; \
-       } \
-} \
-a_attr a_type *MAYBE_UNUSED a_pre##insert(struct trp_root *treap, a_type *node) \
-{ \
-       uint32_t offset = trpn_offset(a_base, node); \
-       trp_node_new(a_base, a_field, offset); \
-       treap->trp_root = a_pre##insert_recurse(treap->trp_root, offset); \
-       return trpn_pointer(a_base, offset); \
-} \
-a_attr uint32_t MAYBE_UNUSED a_pre##remove_recurse(uint32_t cur_node, uint32_t rem_node) \
-{ \
-       int cmp = a_cmp(trpn_pointer(a_base, rem_node), \
-                       trpn_pointer(a_base, cur_node)); \
-       if (cmp == 0) { \
-               uint32_t ret; \
-               uint32_t left = trp_left_get(a_base, a_field, cur_node); \
-               uint32_t right = trp_right_get(a_base, a_field, cur_node); \
-               if (left == ~0) { \
-                       if (right == ~0) \
-                               return ~0; \
-               } else if (right == ~0 || trp_prio_get(left) < trp_prio_get(right)) { \
-                       trpn_rotate_right(a_base, a_field, cur_node, ret); \
-                       right = a_pre##remove_recurse(cur_node, rem_node); \
-                       trp_right_set(a_base, a_field, ret, right); \
-                       return ret; \
-               } \
-               trpn_rotate_left(a_base, a_field, cur_node, ret); \
-               left = a_pre##remove_recurse(cur_node, rem_node); \
-               trp_left_set(a_base, a_field, ret, left); \
-               return ret; \
-       } else if (cmp < 0) { \
-               uint32_t left = a_pre##remove_recurse( \
-                       trp_left_get(a_base, a_field, cur_node), rem_node); \
-               trp_left_set(a_base, a_field, cur_node, left); \
-               return cur_node; \
-       } else { \
-               uint32_t right = a_pre##remove_recurse( \
-                       trp_right_get(a_base, a_field, cur_node), rem_node); \
-               trp_right_set(a_base, a_field, cur_node, right); \
-               return cur_node; \
-       } \
-} \
-a_attr void MAYBE_UNUSED a_pre##remove(struct trp_root *treap, a_type *node) \
-{ \
-       treap->trp_root = a_pre##remove_recurse(treap->trp_root, \
-               trpn_offset(a_base, node)); \
-} \
-
-#endif
diff --git a/vcs-svn/trp.txt b/vcs-svn/trp.txt
deleted file mode 100644 (file)
index 177ebca..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-Motivation
-==========
-
-Treaps provide a memory-efficient binary search tree structure.
-Insertion/deletion/search are about as about as fast in the average
-case as red-black trees and the chances of worst-case behavior are
-vanishingly small, thanks to (pseudo-)randomness.  The bad worst-case
-behavior is a small price to pay, given that treaps are much simpler
-to implement.
-
-API
-===
-
-The trp API generates a data structure and functions to handle a
-large growing set of objects stored in a pool.
-
-The caller:
-
-. Specifies parameters for the generated functions with the
-  trp_gen(static, foo_, ...) macro.
-
-. Allocates a `struct trp_root` variable and sets it to {~0}.
-
-. Adds new nodes to the set using `foo_insert`.  Any pointers
-  to existing nodes cannot be relied upon any more, so the caller
-  might retrieve them anew with `foo_pointer`.
-
-. Can find a specific item in the set using `foo_search`.
-
-. Can iterate over items in the set using `foo_first` and `foo_next`.
-
-. Can remove an item from the set using `foo_remove`.
-
-Example:
-
-----
-struct ex_node {
-       const char *s;
-       struct trp_node ex_link;
-};
-static struct trp_root ex_base = {~0};
-obj_pool_gen(ex, struct ex_node, 4096);
-trp_gen(static, ex_, struct ex_node, ex_link, ex, strcmp)
-struct ex_node *item;
-
-item = ex_pointer(ex_alloc(1));
-item->s = "hello";
-ex_insert(&ex_base, item);
-item = ex_pointer(ex_alloc(1));
-item->s = "goodbye";
-ex_insert(&ex_base, item);
-for (item = ex_first(&ex_base); item; item = ex_next(&ex_base, item))
-       printf("%s\n", item->s);
-----
-
-Functions
----------
-
-trp_gen(attr, foo_, node_type, link_field, pool, cmp)::
-
-       Generate a type-specific treap implementation.
-+
-. The storage class for generated functions will be 'attr' (e.g., `static`).
-. Generated function names are prefixed with 'foo_' (e.g., `treap_`).
-. Treap nodes will be of type 'node_type' (e.g., `struct treap_node`).
-  This type must be a struct with at least one `struct trp_node` field
-  to point to its children.
-. The field used to access child nodes will be 'link_field'.
-. All treap nodes must lie in the 'pool' object pool.
-. Treap nodes must be totally ordered by the 'cmp' relation, with the
-  following prototype:
-+
-int (*cmp)(node_type \*a, node_type \*b)
-+
-and returning a value less than, equal to, or greater than zero
-according to the result of comparison.
-
-node_type {asterisk}foo_insert(struct trp_root *treap, node_type \*node)::
-
-       Insert node into treap.  If inserted multiple times,
-       a node will appear in the treap multiple times.
-+
-The return value is the address of the node within the treap,
-which might differ from `node` if `pool_alloc` had to call
-`realloc` to expand the pool.
-
-void foo_remove(struct trp_root *treap, node_type \*node)::
-
-       Remove node from treap.  Caller must ensure node is
-       present in treap before using this function.
-
-node_type *foo_search(struct trp_root \*treap, node_type \*key)::
-
-       Search for a node that matches key.  If no match is found,
-       result is NULL.
-
-node_type *foo_nsearch(struct trp_root \*treap, node_type \*key)::
-
-       Like `foo_search`, but if the key is missing return what
-       would be key's successor, were key in treap (NULL if no
-       successor).
-
-node_type *foo_first(struct trp_root \*treap)::
-
-       Find the first item from the treap, in sorted order.
-
-node_type *foo_next(struct trp_root \*treap, node_type \*node)::
-
-       Find the next item.
index 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 2e669c3e2570332ee50a72e06a77ad4a356ecfd6..d11dbf9f13c13db16fa05f7539170862d6da9795 100644 (file)
@@ -87,7 +87,7 @@ static long def_ff(const char *rec, long len, char *buf, long sz, void *priv)
 
 static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                            xdemitconf_t const *xecfg) {
-       xdfile_t *xdf = &xe->xdf1;
+       xdfile_t *xdf = &xe->xdf2;
        const char *rchg = xdf->rchg;
        long ix;
 
@@ -204,8 +204,8 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                /*
                 * Emit pre-context.
                 */
-               for (; s1 < xch->i1; s1++)
-                       if (xdl_emit_record(&xe->xdf1, s1, " ", ecb) < 0)
+               for (; s2 < xch->i2; s2++)
+                       if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
                                return -1;
 
                for (s1 = xch->i1, s2 = xch->i2;; xch = xch->next) {
@@ -213,7 +213,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                         * Merge previous with current change atom.
                         */
                        for (; s1 < xch->i1 && s2 < xch->i2; s1++, s2++)
-                               if (xdl_emit_record(&xe->xdf1, s1, " ", ecb) < 0)
+                               if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
                                        return -1;
 
                        /*
@@ -239,8 +239,8 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                /*
                 * Emit post-context.
                 */
-               for (s1 = xche->i1 + xche->chg1; s1 < e1; s1++)
-                       if (xdl_emit_record(&xe->xdf1, s1, " ", ecb) < 0)
+               for (s2 = xche->i2 + xche->chg2; s2 < e2; s2++)
+                       if (xdl_emit_record(&xe->xdf2, s2, " ", 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),