summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Documentation/Makefile6
-rw-r--r--Documentation/RelNotes-1.6.1.txt184
-rw-r--r--Documentation/SubmittingPatches2
-rw-r--r--Documentation/config.txt57
-rw-r--r--Documentation/diff-options.txt9
-rw-r--r--Documentation/git-add.txt13
-rw-r--r--Documentation/git-apply.txt14
-rw-r--r--Documentation/git-check-attr.txt9
-rw-r--r--Documentation/git-checkout.txt58
-rw-r--r--Documentation/git-clone.txt5
-rw-r--r--Documentation/git-commit.txt10
-rw-r--r--Documentation/git-count-objects.txt5
-rw-r--r--Documentation/git-daemon.txt18
-rw-r--r--Documentation/git-describe.txt9
-rw-r--r--Documentation/git-diff-tree.txt19
-rw-r--r--Documentation/git-filter-branch.txt4
-rw-r--r--Documentation/git-for-each-ref.txt1
-rw-r--r--Documentation/git-format-patch.txt8
-rw-r--r--Documentation/git-grep.txt6
-rw-r--r--Documentation/git-hash-object.txt20
-rw-r--r--Documentation/git-help.txt4
-rw-r--r--Documentation/git-imap-send.txt78
-rw-r--r--Documentation/git-merge-base.txt77
-rw-r--r--Documentation/git-merge.txt89
-rw-r--r--Documentation/git-prune.txt5
-rw-r--r--Documentation/git-read-tree.txt2
-rw-r--r--Documentation/git-rebase.txt146
-rw-r--r--Documentation/git-reset.txt4
-rw-r--r--Documentation/git-send-email.txt242
-rw-r--r--Documentation/git-submodule.txt26
-rw-r--r--Documentation/git-svn.txt24
-rw-r--r--Documentation/git-web--browse.txt1
-rw-r--r--Documentation/gitattributes.txt8
-rw-r--r--Documentation/gittutorial-2.txt15
-rw-r--r--Documentation/gitworkflows.txt364
-rw-r--r--Documentation/howto/rebase-and-edit.txt79
-rw-r--r--Documentation/pretty-formats.txt1
-rw-r--r--Documentation/rev-list-options.txt42
-rw-r--r--Documentation/technical/api-run-command.txt2
-rw-r--r--Documentation/user-manual.txt4
-rw-r--r--INSTALL21
-rw-r--r--Makefile61
l---------RelNotes2
-rw-r--r--abspath.c13
-rw-r--r--archive-tar.c6
-rw-r--r--archive.c8
-rw-r--r--arm/sha1.c16
-rw-r--r--arm/sha1.h15
-rw-r--r--arm/sha1_arm.S4
-rw-r--r--branch.c5
-rw-r--r--builtin-add.c116
-rw-r--r--builtin-apply.c74
-rw-r--r--builtin-blame.c9
-rw-r--r--builtin-branch.c3
-rw-r--r--builtin-cat-file.c3
-rw-r--r--builtin-check-attr.c109
-rw-r--r--builtin-checkout-index.c4
-rw-r--r--builtin-checkout.c237
-rw-r--r--builtin-clean.c6
-rw-r--r--builtin-clone.c22
-rw-r--r--builtin-commit-tree.c14
-rw-r--r--builtin-commit.c139
-rw-r--r--builtin-count-objects.c9
-rw-r--r--builtin-describe.c6
-rw-r--r--builtin-diff-tree.c57
-rw-r--r--builtin-diff.c6
-rw-r--r--builtin-fetch--tool.c3
-rw-r--r--builtin-fetch-pack.c7
-rw-r--r--builtin-fetch.c19
-rw-r--r--builtin-fmt-merge-msg.c53
-rw-r--r--builtin-for-each-ref.c135
-rw-r--r--builtin-gc.c9
-rw-r--r--builtin-grep.c8
-rw-r--r--builtin-help.c463
-rw-r--r--builtin-http-fetch.c2
-rw-r--r--builtin-log.c59
-rw-r--r--builtin-ls-files.c33
-rw-r--r--builtin-merge-base.c43
-rw-r--r--builtin-merge-file.c73
-rw-r--r--builtin-merge-recursive.c1387
-rw-r--r--builtin-merge.c196
-rw-r--r--builtin-pack-objects.c12
-rw-r--r--builtin-prune.c10
-rw-r--r--builtin-push.c13
-rw-r--r--builtin-receive-pack.c (renamed from receive-pack.c)51
-rw-r--r--builtin-reflog.c8
-rw-r--r--builtin-remote.c19
-rw-r--r--builtin-rev-list.c3
-rw-r--r--builtin-rev-parse.c4
-rw-r--r--builtin-revert.c88
-rw-r--r--builtin-rm.c3
-rw-r--r--builtin-send-pack.c21
-rw-r--r--builtin-show-branch.c3
-rw-r--r--builtin-stripspace.c3
-rw-r--r--builtin-tag.c3
-rw-r--r--builtin-unpack-objects.c13
-rw-r--r--builtin-update-index.c17
-rw-r--r--builtin.h6
-rw-r--r--bundle.c3
-rw-r--r--cache.h30
-rw-r--r--combine-diff.c19
-rw-r--r--commit.c2
-rw-r--r--commit.h2
-rw-r--r--compat/cygwin.c137
-rw-r--r--compat/cygwin.h9
-rw-r--r--compat/mingw.c86
-rw-r--r--compat/mingw.h16
-rw-r--r--compat/win32.h34
-rw-r--r--config.c29
-rw-r--r--config.mak.in2
-rw-r--r--configure.ac32
-rw-r--r--connect.c19
-rwxr-xr-xcontrib/completion/git-completion.bash32
-rwxr-xr-xcontrib/fast-import/git-p439
-rw-r--r--contrib/hooks/pre-auto-gc-battery13
-rwxr-xr-xcontrib/rerere-train.sh52
-rw-r--r--contrib/vim/README38
-rw-r--r--contrib/vim/syntax/gitcommit.vim18
-rw-r--r--convert.c3
-rw-r--r--csum-file.c33
-rw-r--r--csum-file.h2
-rw-r--r--ctype.c10
-rw-r--r--daemon.c312
-rw-r--r--date.c26
-rw-r--r--diff-lib.c3
-rw-r--r--diff-no-index.c1
-rw-r--r--diff.c442
-rw-r--r--diff.h3
-rw-r--r--diffcore.h8
-rw-r--r--dir.c24
-rw-r--r--dir.h1
-rw-r--r--editor.c3
-rw-r--r--environment.c5
-rw-r--r--exec_cmd.c4
-rw-r--r--fast-import.c24
-rw-r--r--fsck.c3
-rwxr-xr-xgit-bisect.sh173
-rw-r--r--git-compat-util.h17
-rwxr-xr-xgit-filter-branch.sh44
-rw-r--r--git-gui/.gitattributes3
-rwxr-xr-xgit-gui/git-gui.sh609
-rw-r--r--git-gui/lib/blame.tcl217
-rw-r--r--git-gui/lib/browser.tcl2
-rw-r--r--git-gui/lib/commit.tcl11
-rw-r--r--git-gui/lib/diff.tcl165
-rw-r--r--git-gui/lib/encoding.tcl250
-rw-r--r--git-gui/lib/index.tcl12
-rw-r--r--git-gui/lib/mergetool.tcl400
-rw-r--r--git-gui/lib/option.tcl37
-rw-r--r--git-gui/po/de.po193
-rwxr-xr-xgit-merge-octopus.sh11
-rwxr-xr-xgit-pull.sh4
-rwxr-xr-xgit-rebase--interactive.sh47
-rwxr-xr-xgit-rebase.sh29
-rwxr-xr-xgit-send-email.perl117
-rwxr-xr-xgit-submodule.sh103
-rwxr-xr-xgit-svn.perl172
-rwxr-xr-xgit-web--browse.sh8
-rw-r--r--git.c10
-rw-r--r--git.spec.in4
-rw-r--r--gitk-git/gitk530
-rw-r--r--gitk-git/po/de.po460
-rw-r--r--gitk-git/po/es.po507
-rw-r--r--gitk-git/po/it.po500
-rw-r--r--gitk-git/po/sv.po448
-rw-r--r--gitweb/gitweb.css17
-rwxr-xr-xgitweb/gitweb.perl582
-rw-r--r--graph.c70
-rw-r--r--graph.h40
-rw-r--r--grep.c14
-rw-r--r--grep.h1
-rw-r--r--hash-object.c163
-rw-r--r--help.c656
-rw-r--r--help.h29
-rw-r--r--http-push.c16
-rw-r--r--http-walker.c10
-rw-r--r--http.c2
-rw-r--r--imap-send.c925
-rw-r--r--index-pack.c13
-rw-r--r--levenshtein.c47
-rw-r--r--levenshtein.h8
-rw-r--r--ll-merge.c18
-rw-r--r--lockfile.c29
-rw-r--r--log-tree.c41
-rw-r--r--log-tree.h1
-rw-r--r--merge-recursive.c1356
-rw-r--r--merge-recursive.h42
-rw-r--r--merge-tree.c3
-rw-r--r--mktag.c3
-rw-r--r--mktree.c6
-rw-r--r--mozilla-sha1/sha1.c18
-rw-r--r--mozilla-sha1/sha1.h13
-rw-r--r--object.h13
-rw-r--r--pack-check.c16
-rw-r--r--pack-refs.c3
-rw-r--r--pack-write.c30
-rw-r--r--pager.c55
-rw-r--r--patch-id.c12
-rw-r--r--ppc/sha1.c18
-rw-r--r--ppc/sha1.h15
-rw-r--r--ppc/sha1ppc.S4
-rw-r--r--pretty.c24
-rw-r--r--read-cache.c189
-rw-r--r--refs.c29
-rw-r--r--remote.c87
-rw-r--r--remote.h5
-rw-r--r--rerere.c49
-rw-r--r--revision.c240
-rw-r--r--revision.h3
-rw-r--r--run-command.c10
-rw-r--r--run-command.h2
-rw-r--r--sha1_file.c103
-rw-r--r--sha1_name.c5
-rw-r--r--t/.gitignore2
-rw-r--r--t/Makefile8
-rw-r--r--t/lib-git-svn.sh9
-rw-r--r--t/lib-httpd.sh2
-rwxr-xr-xt/t0002-gitfile.sh4
-rwxr-xr-xt/t0003-attributes.sh17
-rwxr-xr-xt/t0022-crlf-rename.sh4
-rwxr-xr-xt/t0055-beyond-symlinks.sh25
-rwxr-xr-xt/t1000-read-tree-m-3way.sh2
-rwxr-xr-xt/t1005-read-tree-reset.sh60
-rwxr-xr-xt/t1007-hash-object.sh56
-rwxr-xr-xt/t1501-worktree.sh2
-rwxr-xr-xt/t2203-add-intent.sh36
-rwxr-xr-xt/t3409-rebase-hook.sh16
-rwxr-xr-xt/t3409-rebase-preserve-merges.sh61
-rwxr-xr-xt/t3410-rebase-preserve-dropped-merges.sh139
-rwxr-xr-xt/t3504-cherry-pick-rerere.sh45
-rwxr-xr-xt/t3600-rm.sh13
-rwxr-xr-xt/t3700-add.sh2
-rwxr-xr-xt/t3900-i18n-commit.sh18
-rwxr-xr-xt/t3901-i18n-patch.sh28
-rwxr-xr-xt/t4000-diff-format.sh2
-rwxr-xr-xt/t4001-diff-rename.sh2
-rwxr-xr-xt/t4002-diff-basic.sh16
-rwxr-xr-xt/t4003-diff-rename-1.sh6
-rwxr-xr-xt/t4004-diff-rename-symlink.sh2
-rwxr-xr-xt/t4005-diff-rename-2.sh6
-rwxr-xr-xt/t4007-rename-3.sh4
-rwxr-xr-xt/t4008-diff-break-rewrite.sh6
-rwxr-xr-xt/t4009-diff-rename-4.sh6
-rwxr-xr-xt/t4010-diff-pathspec.sh2
-rwxr-xr-xt/t4011-diff-symlink.sh2
-rwxr-xr-xt/t4012-diff-binary.sh6
-rwxr-xr-xt/t4013-diff-various.sh6
-rw-r--r--t/t4013/diff.diff_master_master^_side29
-rw-r--r--t/t4013/diff.format-patch_--attach_--stdout_initial..master6
-rw-r--r--t/t4013/diff.format-patch_--attach_--stdout_initial..master^4
-rw-r--r--t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master6
-rw-r--r--t/t4013/diff.format-patch_--inline_--stdout_initial..master6
-rw-r--r--t/t4013/diff.format-patch_--inline_--stdout_initial..master^4
-rw-r--r--t/t4013/diff.format-patch_--stdout_--no-numbered_initial..master127
-rw-r--r--t/t4013/diff.format-patch_--stdout_--numbered_initial..master127
-rw-r--r--t/t4013/diff.format-patch_--stdout_initial..master6
-rw-r--r--t/t4013/diff.format-patch_--stdout_initial..master^4
-rwxr-xr-xt/t4014-format-patch.sh25
-rwxr-xr-xt/t4015-diff-whitespace.sh2
-rwxr-xr-xt/t4018-diff-funcname.sh2
-rwxr-xr-xt/t4020-diff-external.sh2
-rwxr-xr-xt/t4021-format-patch-numbered.sh15
-rwxr-xr-xt/t4022-diff-rewrite.sh4
-rwxr-xr-xt/t4023-diff-rename-typechange.sh14
-rwxr-xr-xt/t4027-diff-submodule.sh2
-rwxr-xr-xt/t4029-diff-trailing-space.sh39
-rwxr-xr-xt/t4100-apply-stat.sh4
-rwxr-xr-xt/t4101-apply-nonl.sh7
-rwxr-xr-xt/t4150-am.sh2
-rwxr-xr-xt/t4151-am-abort.sh2
-rwxr-xr-xt/t5100-mailinfo.sh23
-rwxr-xr-xt/t5300-pack-object.sh3
-rwxr-xr-xt/t5405-send-pack-rewind.sh2
-rwxr-xr-xt/t5500-fetch-pack.sh2
-rwxr-xr-xt/t5505-remote.sh16
-rwxr-xr-xt/t5510-fetch.sh32
-rwxr-xr-xt/t5515-fetch-merge-logic.sh4
-rwxr-xr-xt/t5520-pull.sh12
-rwxr-xr-xt/t5540-http-push.sh2
-rwxr-xr-xt/t5702-clone-options.sh13
-rwxr-xr-xt/t6002-rev-list-bisect.sh2
-rwxr-xr-xt/t6003-rev-list-topo-order.sh2
-rwxr-xr-xt/t6010-merge-base.sh48
-rwxr-xr-xt/t6012-rev-list-simplify.sh93
-rwxr-xr-xt/t6013-rev-list-reverse-parents.sh42
-rwxr-xr-xt/t6023-merge-file.sh50
-rwxr-xr-xt/t6026-merge-attr.sh22
-rwxr-xr-xt/t6027-merge-binary.sh2
-rwxr-xr-xt/t6030-bisect-porcelain.sh114
-rwxr-xr-xt/t6040-tracking-info.sh4
-rwxr-xr-xt/t6101-rev-parse-parents.sh2
-rwxr-xr-xt/t6120-describe.sh8
-rwxr-xr-xt/t6200-fmt-merge-msg.sh4
-rwxr-xr-xt/t6300-for-each-ref.sh44
-rwxr-xr-xt/t7001-mv.sh4
-rwxr-xr-xt/t7002-grep.sh2
-rwxr-xr-xt/t7003-filter-branch.sh16
-rwxr-xr-xt/t7004-tag.sh2
-rwxr-xr-xt/t7101-reset.sh10
-rwxr-xr-xt/t7201-co.sh179
-rwxr-xr-xt/t7403-submodule-sync.sh64
-rwxr-xr-xt/t7500-commit.sh41
-rwxr-xr-xt/t7502-status.sh16
-rwxr-xr-xt/t7600-merge.sh27
-rwxr-xr-xt/t7603-merge-reduce-heads.sh53
-rwxr-xr-xt/t7606-merge-custom.sh49
-rwxr-xr-xt/t7701-repack-unpack-unreachable.sh4
-rwxr-xr-xt/t8001-annotate.sh2
-rwxr-xr-xt/t8002-blame.sh2
-rwxr-xr-xt/t9001-send-email.sh2
-rwxr-xr-xt/t9100-git-svn-basic.sh90
-rwxr-xr-xt/t9101-git-svn-props.sh72
-rwxr-xr-xt/t9102-git-svn-deep-rmdir.sh14
-rwxr-xr-xt/t9103-git-svn-tracked-directory-removed.sh2
-rwxr-xr-xt/t9104-git-svn-follow-parent.sh38
-rwxr-xr-xt/t9105-git-svn-commit-diff.sh12
-rwxr-xr-xt/t9106-git-svn-commit-diff-clobber.sh22
-rwxr-xr-xt/t9106-git-svn-dcommit-clobber-series.sh2
-rwxr-xr-xt/t9107-git-svn-migrate.sh34
-rwxr-xr-xt/t9108-git-svn-glob.sh8
-rwxr-xr-xt/t9108-git-svn-multi-glob.sh10
-rwxr-xr-xt/t9110-git-svn-use-svm-props.sh34
-rwxr-xr-xt/t9111-git-svn-use-svnsync-props.sh30
-rwxr-xr-xt/t9112-git-svn-md5less-file.sh4
-rwxr-xr-xt/t9113-git-svn-dcommit-new-file.sh2
-rwxr-xr-xt/t9114-git-svn-dcommit-merge.sh2
-rwxr-xr-xt/t9115-git-svn-dcommit-funky-renames.sh6
-rwxr-xr-xt/t9116-git-svn-log.sh6
-rwxr-xr-xt/t9117-git-svn-init-clone.sh2
-rwxr-xr-xt/t9118-git-svn-funky-branch-names.sh2
-rwxr-xr-xt/t9119-git-svn-info.sh213
-rwxr-xr-xt/t9120-git-svn-clone-with-percent-escapes.sh4
-rwxr-xr-xt/t9121-git-svn-fetch-renamed-dir.sh4
-rwxr-xr-xt/t9122-git-svn-author.sh2
-rwxr-xr-xt/t9123-git-svn-rebuild-with-rewriteroot.sh6
-rwxr-xr-xt/t9124-git-svn-dcommit-auto-props.sh33
-rwxr-xr-xt/t9125-git-svn-multi-glob-branch-names.sh2
-rwxr-xr-xt/t9127-git-svn-partial-rebuild.sh59
-rwxr-xr-xt/t9128-git-svn-cmd-branch.sh59
-rwxr-xr-xt/t9200-git-cvsexportcommit.sh28
-rwxr-xr-xt/t9300-fast-import.sh84
-rwxr-xr-xt/t9301-fast-export.sh4
-rwxr-xr-xt/t9500-gitweb-standalone-no-errors.sh18
-rwxr-xr-xt/t9600-cvsimport.sh2
-rwxr-xr-xt/t9700-perl-git.sh18
-rwxr-xr-xt/t9700/test.pl3
-rw-r--r--t/test-lib.sh10
-rw-r--r--test-sha1.c8
-rw-r--r--transport.c16
-rw-r--r--transport.h2
-rw-r--r--usage.c16
-rw-r--r--userdiff.c175
-rw-r--r--userdiff.h22
-rw-r--r--walker.c5
-rw-r--r--ws.c3
-rw-r--r--wt-status.c60
-rw-r--r--xdiff-interface.c107
-rw-r--r--xdiff-interface.h15
-rw-r--r--xdiff/xdiff.h6
-rw-r--r--xdiff/xmerge.c237
361 files changed, 13803 insertions, 6923 deletions
diff --git a/.gitignore b/.gitignore
index a213e8e..bbaf9de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,6 +51,7 @@ git-gc
git-get-tar-commit-id
git-grep
git-hash-object
+git-help
git-http-fetch
git-http-push
git-imap-send
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 62269e3..e33ddcb 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -6,7 +6,7 @@ MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \
gitrepository-layout.txt
MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \
gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \
- gitdiffcore.txt
+ gitdiffcore.txt gitworkflows.txt
MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
@@ -44,6 +44,7 @@ MANPAGE_XSL = callouts.xsl
INSTALL?=install
RM ?= rm -f
DOC_REF = origin/man
+HTML_REF = origin/html
infodir?=$(prefix)/share/info
MAKEINFO=makeinfo
@@ -222,4 +223,7 @@ install-webdoc : html
quick-install:
sh ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir)
+quick-install-html:
+ sh ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir)
+
.PHONY: .FORCE-GIT-VERSION-FILE
diff --git a/Documentation/RelNotes-1.6.1.txt b/Documentation/RelNotes-1.6.1.txt
new file mode 100644
index 0000000..afd1150
--- /dev/null
+++ b/Documentation/RelNotes-1.6.1.txt
@@ -0,0 +1,184 @@
+GIT v1.6.1 Release Notes
+========================
+
+Updates since v1.6.0
+--------------------
+
+When some commands (e.g. "git log", "git diff") spawn pager internally, we
+used to make the pager the parent process of the git command that produces
+output. This meant that the exit status of the whole thing comes from the
+pager, not the underlying git command. We swapped the order of the
+processes around and you will see the exit code from the command from now
+on.
+
+(subsystems)
+
+* gitk can call out to git-gui to view "git blame" output; git-gui in turn
+ can run gitk from its blame view.
+
+* Various git-gui updates including updated translations.
+
+* Various gitweb updates from repo.or.cz installation.
+
+(portability)
+
+* A few test scripts used nonportable "grep" that did not work well on
+ some platforms, e.g. Solaris.
+
+* Sample pre-auto-gc script has OS X support.
+
+(performance)
+
+* The underlying diff machinery to produce textual output has been
+ optimized, which would result in faster "git blame" processing.
+
+* Most of the test scripts (but not the ones that try to run servers)
+ can be run in parallel.
+
+* Bash completion of refnames in a repository with massive number of
+ refs has been optimized.
+
+* Cygwin port uses native stat/lstat implementations when applicable,
+ which leads to improved performance.
+
+* "git push" pays attention to alternate repositories to avoid sending
+ unnecessary objects.
+
+* "git svn" can rebuild an out-of-date rev_map file.
+
+(usability, bells and whistles)
+
+* When you mistype a command name, git helpfully suggests what it guesses
+ you might have meant to say. help.autocorrect configuration can be set
+ to a non-zero value to accept the suggestion when git can uniquely
+ guess.
+
+* "git add -N path..." adds the named paths as an empty blob, so that
+ subsequent "git diff" will show a diff as if they are creation events.
+
+* "git apply" learned --include=paths option, similar to the existing
+ --exclude=paths option.
+
+* "git bisect" is careful about a user mistake and suggests testing of
+ merge base first when good is not a strict ancestor of bad.
+
+* "git checkout --track origin/hack" used to be a syntax error. It now
+ DWIMs to create a corresponding local branch "hack", i.e. acts as if you
+ said "git checkout --track -b hack origin/hack".
+
+* "git checkout --ours/--theirs" can be used to check out one side of a
+ conflicting merge during conflict resolution.
+
+* "git checkout -m" can be used to recreate the initial conflicted state
+ during conflict resolution.
+
+* "git cherry-pick" can also utilize rerere for conflict resolution.
+
+* "git commit --author=$name" can look up author name from existing
+ commits.
+
+* output from "git commit" has been reworded in a more concise and yet
+ more informative way.
+
+* "git count-objects" reports the on-disk footprint for packfiles and
+ their corresponding idx files.
+
+* "git daemon" learned --max-connections=<count> option.
+
+* "git diff" learned to mimic --suppress-blank-empty from GNU diff via a
+ configuration option.
+
+* "git diff" learned to put more sensible hunk headers for Python,
+ HTML and ObjC contents.
+
+* "git diff" learned to vary the a/ vs b/ prefix depending on what are
+ being compared, controlled by diff.mnemonicprefix configuration.
+
+* "git diff" learned --dirstat-by-file to count changed files, not number
+ of lines, when summarizing the global picture.
+
+* "git diff" hunk header pattern for ObjC has been added.
+
+* "git for-each-ref" learned "refname:short" token that gives an
+ unambiguously abbreviated refname.
+
+* "git grep" learned to accept -z similar to GNU grep.
+
+* "git help" learned to use GIT_MAN_VIEWER environment variable before
+ using "man" program.
+
+* "git imap-send" can optionally talk SSL.
+
+* "git index-pack" is more careful against disk corruption while
+ completing a thin pack.
+
+* "git log --check" and "git log --exit-code" passes their underlying diff
+ status with their exit status code.
+
+* "git log" learned --simplify-merges, a milder variant of --full-history;
+ "gitk --simplify-merges" is easier to view than with --full-history.
+
+* "git log --pretty=format:" learned "%d" format element that inserts
+ names of tags that point at the commit.
+
+* "git merge --squash" and "git merge --no-ff" into an unborn branch are
+ noticed as user errors.
+
+* "git merge -s $strategy" can use a custom built strategy if you have a
+ command "git-merge-$strategy" on your $PATH.
+
+* "git reflog expire branch" can be used in place of "git reflog expire
+ refs/heads/branch".
+
+* "git submodule foreach" subcommand allows you to iterate over checked
+ out submodules.
+
+* "git submodule sync" subcommands allows you to update the origin URL
+ recorded in submodule directories from the toplevel .gitmodules file.
+
+* "git svn branch" can create new branches on the other end.
+
+(internal)
+
+* "git hash-object" learned to lie about the path being hashed, so that
+ correct gitattributes processing can be done while hashing contents
+ stored in a temporary file.
+
+* various callers of git-merge-recursive avoid forking it as an external
+ process.
+
+
+Fixes since v1.6.0
+------------------
+
+All of the fixes in v1.6.0.X maintenance series are included in this
+release, unless otherwise noted.
+
+* "git add" and "git update-index" incorrectly allowed adding S/F when S
+ is a tracked symlink that points at a directory D that has a path F in
+ it (we still need to fix a similar nonsense when S is a submodule and F
+ is a path in it).
+
+* "git diff --stdin" used to take two trees on a line and compared them,
+ but we dropped support for such a use case long time ago. This has
+ been resurrected.
+
+* Giving 3 or more tree-ish to "git diff" is supposed to show the combined
+ diff from second and subsequent trees to the first one. b75271d ("git
+ diff <tree>{3,}": do not reverse order of arguments, 2008-10-10) needs
+ to be cherry-picked to 'maint'.
+
+* "git filter-branch" failed to rewrite a tag name with slashes in it.
+
+* "git push --tags --all $there" failed with generic usage message without
+ telling saying these two options are incompatible.
+
+* "git log --author/--committer" match used to potentially match the
+ timestamp part, exposing internal implementation detail. Also these did
+ not work with --fixed-strings match at all.
+
+--
+exec >/var/tmp/1
+O=v1.6.0.2-553-g58e0fa5
+echo O=$(git describe master)
+git shortlog --no-merges $O..master ^maint
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index 841bead..a1e9100 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -71,7 +71,7 @@ run git diff --check on your changes before you commit.
(1a) Try to be nice to older C compilers
-We try to support wide range of C compilers to compile
+We try to support a wide range of C compilers to compile
git with. That means that you should not use C99 initializers, even
if a lot of compilers grok it.
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 87b028f..29369d0 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -117,6 +117,17 @@ core.fileMode::
the working copy are ignored; useful on broken filesystems like FAT.
See linkgit:git-update-index[1]. True by default.
+core.ignoreCygwinFSTricks::
+ This option is only used by Cygwin implementation of Git. If false,
+ the Cygwin stat() and lstat() functions are used. This may be useful
+ if your repository consists of a few separate directories joined in
+ one hierarchy using Cygwin mount. If true, Git uses native Win32 API
+ whenever it is possible and falls back to Cygwin functions only to
+ handle symbol links. The native mode is more than twice faster than
+ normal Cygwin l/stat() functions. True by default, unless core.filemode
+ is true, in which case ignoreCygwinFSTricks is ignored as Cygwin's
+ POSIX emulation is required to support core.filemode.
+
core.trustctime::
If false, the ctime differences between the index and the
working copy are ignored; useful when the inode change time
@@ -581,6 +592,10 @@ diff.autorefreshindex::
affects only 'git-diff' Porcelain, and not lower level
'diff' commands, such as 'git-diff-files'.
+diff.suppress-blank-empty::
+ A boolean to inhibit the standard behavior of printing a space
+ before each empty output line. Defaults to false.
+
diff.external::
If this config variable is set, diff generation is not
performed using the internal diff machinery, but using the
@@ -590,6 +605,22 @@ diff.external::
you want to use an external diff program only on a subset of
your files, you might want to use linkgit:gitattributes[5] instead.
+diff.mnemonicprefix::
+ If set, 'git-diff' uses a prefix pair that is different from the
+ standard "a/" and "b/" depending on what is being compared. When
+ this configuration is in effect, reverse diff output also swaps
+ the order of the prefixes:
+'git-diff';;
+ compares the (i)ndex and the (w)ork tree;
+'git-diff HEAD';;
+ compares a (c)ommit and the (w)ork tree;
+'git diff --cached';;
+ compares a (c)ommit and the (i)ndex;
+'git-diff HEAD:file1 file2';;
+ compares an (o)bject and a (w)ork tree entity;
+'git diff --no-index a b';;
+ compares two non-git things (1) and (2).
+
diff.renameLimit::
The number of files to consider when performing the copy/rename
detection; equivalent to the 'git-diff' option '-l'.
@@ -611,10 +642,11 @@ fetch.unpackLimit::
`transfer.unpackLimit` is used instead.
format.numbered::
- A boolean which can enable sequence numbers in patch subjects.
- Setting this option to "auto" will enable it only if there is
- more than one patch. See --numbered option in
- linkgit:git-format-patch[1].
+ A boolean which can enable or disable sequence numbers in patch
+ subjects. It defaults to "auto" which enables it only if there
+ is more than one patch. It can be enabled or disabled for all
+ messages by setting it to "true" or "false". See --numbered
+ option in linkgit:git-format-patch[1].
format.headers::
Additional email headers to include in a patch to be submitted
@@ -795,6 +827,15 @@ help.format::
Values 'man', 'info', 'web' and 'html' are supported. 'man' is
the default. 'web' and 'html' are the same.
+help.autocorrect::
+ Automatically correct and execute mistyped commands after
+ waiting for the given number of deciseconds (0.1 sec). If more
+ than one command can be deduced from the entered text, nothing
+ will be executed. If the value of this option is negative,
+ the corrected command will be executed immediately. If the
+ value is 0 - the command will be just shown but not executed.
+ This is the default.
+
http.proxy::
Override the HTTP proxy, normally configured using the 'http_proxy'
environment variable (see linkgit:curl[1]). This can be overridden
@@ -898,6 +939,14 @@ man.<tool>.path::
Override the path for the given tool that may be used to
display help in the 'man' format. See linkgit:git-help[1].
+merge.conflictstyle::
+ Specify the style in which conflicted hunks are written out to
+ working tree files upon merge. The default is "merge", which
+ shows `<<<<<<<` conflict marker, change made by one side,
+ `=======` marker, change made by the other side, and then
+ `>>>>>>>` marker. An alternate style, "diff3", adds `|||||||`
+ marker and the original text before `=======` marker.
+
mergetool.<tool>.path::
Override the path for the given tool. This is useful in case
your tool is not in the PATH.
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 45885bb..c62b45c 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -65,6 +65,9 @@ endif::git-format-patch[]
can be set with "--dirstat=limit". Changes in a child directory is not
counted for the parent directory, unless "--cumulative" is used.
+--dirstat-by-file[=limit]::
+ Same as --dirstat, but counts changed files instead of lines.
+
--summary::
Output a condensed summary of extended header information
such as creations, renames and mode changes.
@@ -106,9 +109,9 @@ endif::git-format-patch[]
--exit-code.
--full-index::
- Instead of the first handful characters, show full
- object name of pre- and post-image blob on the "index"
- line when generating a patch format output.
+ Instead of the first handful of characters, show the full
+ pre- and post-image blob object names on the "index"
+ line when generating patch format output.
--binary::
In addition to --full-index, output "binary diff" that
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
index 2b6d6c8..6fc20b0 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -9,8 +9,8 @@ SYNOPSIS
--------
[verse]
'git add' [-n] [-v] [--force | -f] [--interactive | -i] [--patch | -p]
- [--all | [--update | -u]] [--refresh] [--ignore-errors] [--]
- <filepattern>...
+ [--all | [--update | -u]] [--intent-to-add | -N]
+ [--refresh] [--ignore-errors] [--] <filepattern>...
DESCRIPTION
-----------
@@ -92,6 +92,15 @@ OPTIONS
and add all untracked files that are not ignored by '.gitignore'
mechanism.
+
+-N::
+--intent-to-add::
+ Record only the fact that the path will be added later. An entry
+ for the path is placed in the index with no content. This is
+ useful for, among other things, showing the unstaged content of
+ such files with 'git diff' and commiting them with 'git commit
+ -a'.
+
--refresh::
Don't add the file(s), but only refresh their stat()
information in the index.
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index feb51f1..e726510 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -14,7 +14,8 @@ SYNOPSIS
[--allow-binary-replacement | --binary] [--reject] [-z]
[-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached]
[--whitespace=<nowarn|warn|fix|error|error-all>]
- [--exclude=PATH] [--directory=<root>] [--verbose] [<patch>...]
+ [--exclude=PATH] [--include=PATH] [--directory=<root>]
+ [--verbose] [<patch>...]
DESCRIPTION
-----------
@@ -137,6 +138,17 @@ discouraged.
be useful when importing patchsets, where you want to exclude certain
files or directories.
+--include=<path-pattern>::
+ Apply changes to files matching the given path pattern. This can
+ be useful when importing patchsets, where you want to include certain
+ files or directories.
++
+When --exclude and --include patterns are used, they are examined in the
+order they appear on the command line, and the first match determines if a
+patch to each path is used. A patch to a path that does not match any
+include/exclude pattern is used by default if there is no include pattern
+on the command line, and ignored if there is any include pattern.
+
--whitespace=<action>::
When applying a patch, detect a new or modified line that has
whitespace errors. What are considered whitespace errors is
diff --git a/Documentation/git-check-attr.txt b/Documentation/git-check-attr.txt
index 4b3c2b0..256659a 100644
--- a/Documentation/git-check-attr.txt
+++ b/Documentation/git-check-attr.txt
@@ -8,7 +8,9 @@ git-check-attr - Display gitattributes information.
SYNOPSIS
--------
+[verse]
'git check-attr' attr... [--] pathname...
+'git check-attr' --stdin [-z] attr... < <list-of-paths>
DESCRIPTION
-----------
@@ -17,6 +19,13 @@ For every pathname, this command will list if each attr is 'unspecified',
OPTIONS
-------
+--stdin::
+ Read file names from stdin instead of from the command-line.
+
+-z::
+ Only meaningful with `--stdin`; paths are separated with
+ NUL character instead of LF.
+
\--::
Interpret all preceding arguments as attributes, and all following
arguments as path names. If not supplied, only the first argument will
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 5aa69c0..168333a 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -8,8 +8,8 @@ git-checkout - Checkout a branch or paths to the working tree
SYNOPSIS
--------
[verse]
-'git checkout' [-q] [-f] [[--track | --no-track] -b <new_branch> [-l]] [-m] [<branch>]
-'git checkout' [<tree-ish>] [--] <paths>...
+'git checkout' [-q] [-f] [--track | --no-track] [-b <new_branch> [-l]] [-m] [<branch>]
+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
DESCRIPTION
-----------
@@ -21,16 +21,26 @@ specified, <new_branch>. Using -b will cause <new_branch> to
be created; in this case you can use the --track or --no-track
options, which will be passed to `git branch`.
+As a convenience, --track will default to create a branch whose
+name is constructed from the specified branch name by stripping
+the first namespace level.
+
When <paths> are given, this command does *not* switch
branches. It updates the named paths in the working tree from
-the index file (i.e. it runs `git checkout-index -f -u`), or
-from a named commit. In
-this case, the `-f` and `-b` options are meaningless and giving
+the index file, or from a named commit. In
+this case, the `-b` options is meaningless and giving
either of them results in an error. <tree-ish> argument can be
used to specify a specific tree-ish (i.e. commit, tag or tree)
to update the index for the given paths before updating the
working tree.
+The index may contain unmerged entries after a failed merge. By
+default, if you try to check out such an entry from the index, the
+checkout operation will fail and nothing will be checked out.
+Using -f will ignore these unmerged entries. The contents from a
+specific side of the merge can be checked out of the index by
+using --ours or --theirs. With -m, changes made to the working tree
+file can be discarded to recreate the original conflicted merge result.
OPTIONS
-------
@@ -38,8 +48,17 @@ OPTIONS
Quiet, suppress feedback messages.
-f::
- Proceed even if the index or the working tree differs
- from HEAD. This is used to throw away local changes.
+ When switching branches, proceed even if the index or the
+ working tree differs from HEAD. This is used to throw away
+ local changes.
++
+When checking out paths from the index, do not fail upon unmerged
+entries; instead, unmerged entries are ignored.
+
+--ours::
+--theirs::
+ When checking out paths from the index, check out stage #2
+ ('ours') or #3 ('theirs') for unmerged paths.
-b::
Create a new branch named <new_branch> and start it at
@@ -59,6 +78,17 @@ OPTIONS
'git-checkout' and 'git-branch' to always behave as if '--no-track' were
given. Set it to `always` if you want this behavior when the
start-point is either a local or remote branch.
++
+If no '-b' option was given, the name of the new branch will be
+derived from the remote branch, by attempting to guess the name
+of the branch on remote system. If "remotes/" or "refs/remotes/"
+are prefixed, it is stripped away, and then the part up to the
+next slash (which would be the nickname of the remote) is removed.
+This would tell us to use "hack" as the local branch when branching
+off of "origin/hack" (or "remotes/origin/hack", or even
+"refs/remotes/origin/hack"). If the given name has no slash, or the above
+guessing results in an empty name, the guessing is aborted. You can
+explicitly give a name with '-b' in such a case.
--no-track::
Ignore the branch.autosetupmerge configuration variable.
@@ -69,7 +99,9 @@ OPTIONS
based sha1 expressions such as "<branchname>@\{yesterday}".
-m::
- If you have local modifications to one or more files that
+--merge::
+ When switching branches,
+ if you have local modifications to one or more files that
are different between the current branch and the branch to
which you are switching, the command refuses to switch
branches in order to preserve your modifications in context.
@@ -81,6 +113,16 @@ When a merge conflict happens, the index entries for conflicting
paths are left unmerged, and you need to resolve the conflicts
and mark the resolved paths with `git add` (or `git rm` if the merge
should result in deletion of the path).
++
+When checking out paths from the index, this option lets you recreate
+the conflicted merge in the specified paths.
+
+--conflict=<style>::
+ The same as --merge option above, but changes the way the
+ conflicting hunks are presented, overriding the
+ merge.conflictstyle configuration variable. Possible values are
+ "merge" (default) and "diff3" (in addition to what is shown by
+ "merge" style, shows the original contents).
<new_branch>::
Name for the new branch.
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 0e14e73..95f08b9 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -90,6 +90,11 @@ then the cloned repository will become corrupt.
Operate quietly. This flag is also passed to the `rsync'
command when given.
+--verbose::
+-v::
+ Display the progressbar, even in case the standard output is not
+ a terminal.
+
--no-checkout::
-n::
No checkout of HEAD is performed after the clone is complete.
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 79be4f1..2e62165 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -75,8 +75,10 @@ OPTIONS
read the message from the standard input.
--author=<author>::
- Override the author name used in the commit. Use
- `A U Thor <author@example.com>` format.
+ Override the author name used in the commit. You can use the
+ standard `A U Thor <author@example.com>` format. Otherwise,
+ an existing commit that matches the given string and its author
+ name is used.
-m <msg>::
--message=<msg>::
@@ -143,6 +145,10 @@ It is a rough equivalent for:
------
but can be used to amend a merge commit.
--
++
+You should understand the implications of rewriting history if you
+amend a commit that has already been published. (See the "RECOVERING
+FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].)
-i::
--include::
diff --git a/Documentation/git-count-objects.txt b/Documentation/git-count-objects.txt
index 75a8da1..6bc1c21 100644
--- a/Documentation/git-count-objects.txt
+++ b/Documentation/git-count-objects.txt
@@ -21,8 +21,9 @@ OPTIONS
--verbose::
In addition to the number of loose objects and disk
space consumed, it reports the number of in-pack
- objects, number of packs, and number of objects that can be
- removed by running `git prune-packed`.
+ objects, number of packs, disk space consumed by those packs,
+ and number of objects that can be removed by running
+ `git prune-packed`.
Author
diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt
index 4ba4b75..f1a570a 100644
--- a/Documentation/git-daemon.txt
+++ b/Documentation/git-daemon.txt
@@ -9,8 +9,9 @@ SYNOPSIS
--------
[verse]
'git daemon' [--verbose] [--syslog] [--export-all]
- [--timeout=n] [--init-timeout=n] [--strict-paths]
- [--base-path=path] [--user-path | --user-path=path]
+ [--timeout=n] [--init-timeout=n] [--max-connections=n]
+ [--strict-paths] [--base-path=path] [--base-path-relaxed]
+ [--user-path | --user-path=path]
[--interpolated-path=pathtemplate]
[--reuseaddr] [--detach] [--pid-file=file]
[--enable=service] [--disable=service]
@@ -99,6 +100,10 @@ OPTIONS
it takes for the server to process the sub-request and time spent
waiting for next client's request.
+--max-connections::
+ Maximum number of concurrent clients, defaults to 32. Set it to
+ zero for no limit.
+
--syslog::
Log to syslog instead of stderr. Note that this option does not imply
--verbose, thus by default only error conditions will be logged.
@@ -265,6 +270,15 @@ selectively enable/disable services per repository::
----------------------------------------------------------------
+ENVIRONMENT
+-----------
+'git-daemon' will set REMOTE_ADDR to the IP address of the client
+that connected to it, if the IP address is available. REMOTE_ADDR will
+be available in the environment of hooks called when
+services are performed.
+
+
+
Author
------
Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki
diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt
index c4dbc2a..3d79f05 100644
--- a/Documentation/git-describe.txt
+++ b/Documentation/git-describe.txt
@@ -18,6 +18,9 @@ shown. Otherwise, it suffixes the tag name with the number of
additional commits on top of the tagged object and the
abbreviated object name of the most recent commit.
+By default (without --all or --tags) `git describe` only shows
+annotated tags. For more information about creating annotated tags
+see the -a and -s options to linkgit:git-tag[1].
OPTIONS
-------
@@ -26,11 +29,13 @@ OPTIONS
--all::
Instead of using only the annotated tags, use any ref
- found in `.git/refs/`.
+ found in `.git/refs/`. This option enables matching
+ any known branch, remote branch, or lightweight tag.
--tags::
Instead of using only the annotated tags, use any tag
- found in `.git/refs/tags`.
+ found in `.git/refs/tags`. This option enables matching
+ a lightweight (non-annotated) tag.
--contains::
Instead of finding the tag that predates the commit, find
diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt
index 1fdf20d..5d48664 100644
--- a/Documentation/git-diff-tree.txt
+++ b/Documentation/git-diff-tree.txt
@@ -49,13 +49,22 @@ include::diff-options.txt[]
--stdin::
When '--stdin' is specified, the command does not take
<tree-ish> arguments from the command line. Instead, it
- reads either one <commit> or a list of <commit>
- separated with a single space from its standard input.
+ reads lines containing either two <tree>, one <commit>, or a
+ list of <commit> from its standard input. (Use a single space
+ as separator.)
+
-When a single commit is given on one line of such input, it compares
-the commit with its parents. The following flags further affects its
-behavior. The remaining commits, when given, are used as if they are
+When two trees are given, it compares the first tree with the second.
+When a single commit is given, it compares the commit with its
+parents. The remaining commits, when given, are used as if they are
parents of the first commit.
++
+When comparing two trees, the ID of both trees (separated by a space
+and terminated by a newline) is printed before the difference. When
+comparing commits, the ID of the first (or only) commit, followed by a
+newline, is printed.
++
+The following flags further affects the behavior when comparing
+commits (but not trees).
-m::
By default, 'git-diff-tree --stdin' does not show
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index b0e710d..fed6de6 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -36,7 +36,9 @@ the objects and will not converge with the original branch. You will not
be able to easily push and distribute the rewritten branch on top of the
original branch. Please do not use this command if you do not know the
full implications, and avoid using it anyway, if a simple single commit
-would suffice to fix your problem.
+would suffice to fix your problem. (See the "RECOVERING FROM UPSTREAM
+REBASE" section in linkgit:git-rebase[1] for further information about
+rewriting published history.)
Always verify that the rewritten version is correct: The original refs,
if different from the rewritten ones, will be stored in the namespace
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
index ebd7c5f..5061d3e 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -74,6 +74,7 @@ For all objects, the following names can be used:
refname::
The name of the ref (the part after $GIT_DIR/).
+ For a non-ambiguous short name of the ref append `:short`.
objecttype::
The type of the object (`blob`, `tree`, `commit`, `tag`).
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index adb4ea7..ac36ce8 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -58,8 +58,10 @@ output, unless the --stdout option is specified.
If -o is specified, output files are created in <dir>. Otherwise
they are created in the current working directory.
-If -n is specified, instead of "[PATCH] Subject", the first line
-is formatted as "[PATCH n/m] Subject".
+By default, the subject of a single patch is "[PATCH] First Line" and
+the subject when multiple patches are output is "[PATCH n/m] First
+Line". To force 1/1 to be added for a single patch, use -n. To omit
+patch numbers from the subject, use -N
If given --thread, 'git-format-patch' will generate In-Reply-To and
References headers to make the second and subsequent patch mails appear
@@ -81,7 +83,7 @@ include::diff-options.txt[]
-n::
--numbered::
- Name output in '[PATCH n/m]' format.
+ Name output in '[PATCH n/m]' format, even with a single patch.
-N::
--no-numbered::
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index fa4d133..553da6c 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -15,6 +15,7 @@ SYNOPSIS
[-E | --extended-regexp] [-G | --basic-regexp]
[-F | --fixed-strings] [-n]
[-l | --files-with-matches] [-L | --files-without-match]
+ [-z | --null]
[-c | --count] [--all-match]
[-A <post-context>] [-B <pre-context>] [-C <context>]
[-f <file>] [-e] <pattern>
@@ -94,6 +95,11 @@ OPTIONS
For better compatibility with 'git-diff', --name-only is a
synonym for --files-with-matches.
+-z::
+--null::
+ Output \0 instead of the character that normally follows a
+ file name.
+
-c::
--count::
Instead of showing every matched line, show the number of
diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt
index ac928e1..0af40cf 100644
--- a/Documentation/git-hash-object.txt
+++ b/Documentation/git-hash-object.txt
@@ -8,7 +8,9 @@ git-hash-object - Compute object ID and optionally creates a blob from a file
SYNOPSIS
--------
-'git hash-object' [-t <type>] [-w] [--stdin | --stdin-paths] [--] <file>...
+[verse]
+'git hash-object' [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...
+'git hash-object' [-t <type>] [-w] --stdin-paths < <list-of-paths>
DESCRIPTION
-----------
@@ -35,6 +37,22 @@ OPTIONS
--stdin-paths::
Read file names from stdin instead of from the command-line.
+--path::
+ Hash object as it were located at the given path. The location of
+ file does not directly influence on the hash value, but path is
+ used to determine what git filters should be applied to the object
+ before it can be placed to the object database, and, as result of
+ applying filters, the actual blob put into the object database may
+ differ from the given file. This option is mainly useful for hashing
+ temporary files located outside of the working directory or files
+ read from stdin.
+
+--no-filters::
+ Hash the contents as is, ignoring any input filter that would
+ have been chosen by the attributes mechanism, including crlf
+ conversion. If the file is read from standard input then this
+ is always implied, unless the --path option is given.
+
Author
------
Written by Junio C Hamano <gitster@pobox.com>
diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt
index f414583..d9b9c34 100644
--- a/Documentation/git-help.txt
+++ b/Documentation/git-help.txt
@@ -112,7 +112,9 @@ For example, this configuration:
will try to use konqueror first. But this may fail (for example if
DISPLAY is not set) and in that case emacs' woman mode will be tried.
-If everything fails the 'man' program will be tried anyway.
+If everything fails, or if no viewer is configured, the viewer specified
+in the GIT_MAN_VIEWER environment variable will be tried. If that
+fails too, the 'man' program will be tried anyway.
man.<tool>.path
~~~~~~~~~~~~~~~
diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt
index b3d8da3..bd49a0a 100644
--- a/Documentation/git-imap-send.txt
+++ b/Documentation/git-imap-send.txt
@@ -3,7 +3,7 @@ git-imap-send(1)
NAME
----
-git-imap-send - Dump a mailbox from stdin into an imap folder
+git-imap-send - Send a collection of patches from stdin to an IMAP folder
SYNOPSIS
@@ -13,9 +13,9 @@ SYNOPSIS
DESCRIPTION
-----------
-This command uploads a mailbox generated with git-format-patch
-into an imap drafts folder. This allows patches to be sent as
-other email is sent with mail clients that cannot read mailbox
+This command uploads a mailbox generated with 'git-format-patch'
+into an IMAP drafts folder. This allows patches to be sent as
+other email is when using mail clients that cannot read mailbox
files directly.
Typical usage is something like:
@@ -26,21 +26,75 @@ git format-patch --signoff --stdout --attach origin | git imap-send
CONFIGURATION
-------------
-'git-imap-send' requires the following values in the repository
-configuration file (shown with examples):
+To use the tool, imap.folder and either imap.tunnel or imap.host must be set
+to appropriate values.
+
+Variables
+~~~~~~~~~
+
+imap.folder::
+ The folder to drop the mails into, which is typically the Drafts
+ folder. For example: "INBOX.Drafts", "INBOX/Drafts" or
+ "[Gmail]/Drafts". Required to use imap-send.
+
+imap.tunnel::
+ Command used to setup a tunnel to the IMAP server through which
+ commands will be piped instead of using a direct network connection
+ to the server. Required when imap.host is not set to use imap-send.
+
+imap.host::
+ A URL identifying the server. Use a `imap://` prefix for non-secure
+ connections and a `imaps://` prefix for secure connections.
+ Ignored when imap.tunnel is set, but required to use imap-send
+ otherwise.
+
+imap.user::
+ The username to use when logging in to the server.
+
+imap.password::
+ The password to use when logging in to the server.
+
+imap.port::
+ An integer port number to connect to on the server.
+ Defaults to 143 for imap:// hosts and 993 for imaps:// hosts.
+ Ignored when imap.tunnel is set.
+
+imap.sslverify::
+ A boolean to enable/disable verification of the server certificate
+ used by the SSL/TLS connection. Default is `true`. Ignored when
+ imap.tunnel is set.
+
+Examples
+~~~~~~~~
+
+Using tunnel mode:
..........................
[imap]
- Folder = "INBOX.Drafts"
+ folder = "INBOX.Drafts"
+ tunnel = "ssh -q -C user@example.com /usr/bin/imapd ./Maildir 2> /dev/null"
+..........................
+Using direct mode:
+
+.........................
[imap]
- Tunnel = "ssh -q user@server.com /usr/bin/imapd ./Maildir 2> /dev/null"
+ folder = "INBOX.Drafts"
+ host = imap://imap.example.com
+ user = bob
+ pass = p4ssw0rd
+..........................
+
+Using direct mode with SSL:
+.........................
[imap]
- Host = imap.server.com
- User = bob
- Pass = pwd
- Port = 143
+ folder = "INBOX.Drafts"
+ host = imaps://imap.example.com
+ user = bob
+ pass = p4ssw0rd
+ port = 123
+ sslverify = false
..........................
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
index 1a7ecbf..2f0c525 100644
--- a/Documentation/git-merge-base.txt
+++ b/Documentation/git-merge-base.txt
@@ -8,26 +8,81 @@ git-merge-base - Find as good common ancestors as possible for a merge
SYNOPSIS
--------
-'git merge-base' [--all] <commit> <commit>
+'git merge-base' [--all] <commit> <commit>...
DESCRIPTION
-----------
-'git-merge-base' finds as good a common ancestor as possible between
-the two commits. That is, given two commits A and B, `git merge-base A
-B` will output a commit which is reachable from both A and B through
-the parent relationship.
+'git-merge-base' finds best common ancestor(s) between two commits to use
+in a three-way merge. One common ancestor is 'better' than another common
+ancestor if the latter is an ancestor of the former. A common ancestor
+that does not have any better common ancestor than it is a 'best common
+ancestor', i.e. a 'merge base'. Note that there can be more than one
+merge bases between two commits.
-Given a selection of equally good common ancestors it should not be
-relied on to decide in any particular way.
-
-The 'git-merge-base' algorithm is still in flux - use the source...
+Among the two commits to compute their merge bases, one is specified by
+the first commit argument on the command line; the other commit is a
+(possibly hypothetical) commit that is a merge across all the remaining
+commits on the command line. As the most common special case, giving only
+two commits from the command line means computing the merge base between
+the given two commits.
OPTIONS
-------
--all::
- Output all common ancestors for the two commits instead of
- just one.
+ Output all merge bases for the commits, instead of just one.
+
+DISCUSSION
+----------
+
+Given two commits 'A' and 'B', `git merge-base A B` will output a commit
+which is reachable from both 'A' and 'B' through the parent relationship.
+
+For example, with this topology:
+
+ o---o---o---B
+ /
+ ---o---1---o---o---o---A
+
+the merge base between 'A' and 'B' is '1'.
+
+Given three commits 'A', 'B' and 'C', `git merge-base A B C` will compute the
+merge base between 'A' and an hypothetical commit 'M', which is a merge
+between 'B' and 'C'. For example, with this topology:
+
+ o---o---o---o---C
+ /
+ / o---o---o---B
+ / /
+ ---2---1---o---o---o---A
+
+the result of `git merge-base A B C` is '1'. This is because the
+equivalent topology with a merge commit 'M' between 'B' and 'C' is:
+
+
+ o---o---o---o---o
+ / \
+ / o---o---o---o---M
+ / /
+ ---2---1---o---o---o---A
+
+and the result of `git merge-base A M` is '1'. Commit '2' is also a
+common ancestor between 'A' and 'M', but '1' is a better common ancestor,
+because '2' is an ancestor of '1'. Hence, '2' is not a merge base.
+
+When the history involves criss-cross merges, there can be more than one
+'best' common ancestors between two commits. For example, with this
+topology:
+
+ ---1---o---A
+ \ /
+ X
+ / \
+ ---2---o---o---B
+
+both '1' and '2' are merge-base of A and B. Neither one is better than
+the other (both are 'best' merge base). When `--all` option is not given,
+it is unspecified which best one is output.
Author
------
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index 17a15ac..1f30830 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -119,6 +119,71 @@ When there are conflicts, these things happen:
same and the index entries for them stay as they were,
i.e. matching `HEAD`.
+HOW CONFLICTS ARE PRESENTED
+---------------------------
+
+During a merge, the working tree files are updated to reflect the result
+of the merge. Among the changes made to the common ancestor's version,
+non-overlapping ones (that is, you changed an area of the file while the
+other side left that area intact, or vice versa) are incorporated in the
+final result verbatim. When both sides made changes to the same area,
+however, git cannot randomly pick one side over the other, and asks you to
+resolve it by leaving what both sides did to that area.
+
+By default, git uses the same style as that is used by "merge" program
+from the RCS suite to present such a conflicted hunk, like this:
+
+------------
+Here are lines that are either unchanged from the common
+ancestor, or cleanly resolved because only one side changed.
+<<<<<<< yours:sample.txt
+Conflict resolution is hard;
+let's go shopping.
+=======
+Git makes conflict resolution easy.
+>>>>>>> theirs:sample.txt
+And here is another line that is cleanly resolved or unmodified.
+------------
+
+The area a pair of conflicting changes happened is marked with markers
+"`<<<<<<<`", "`=======`", and "`>>>>>>>`". The part before the "`=======`"
+is typically your side, and the part after it is typically their side.
+
+The default format does not show what the original said in the conflicted
+area. You cannot tell how many lines are deleted and replaced with the
+Barbie's remark by your side. The only thing you can tell is that your
+side wants to say it is hard and you'd prefer to go shopping, while the
+other side wants to claim it is easy.
+
+An alternative style can be used by setting the "merge.conflictstyle"
+configuration variable to "diff3". In "diff3" style, the above conflict
+may look like this:
+
+------------
+Here are lines that are either unchanged from the common
+ancestor, or cleanly resolved because only one side changed.
+<<<<<<< yours:sample.txt
+Conflict resolution is hard;
+let's go shopping.
+|||||||
+Conflict resolution is hard.
+=======
+Git makes conflict resolution easy.
+>>>>>>> theirs:sample.txt
+And here is another line that is cleanly resolved or unmodified.
+------------
+
+In addition to the "`<<<<<<<`", "`=======`", and "`>>>>>>>`" markers, it uses
+another "`|||||||`" marker that is followed by the original text. You can
+tell that the original just stated a fact, and your side simply gave in to
+that statement and gave up, while the other side tried to have a more
+positive attitude. You can sometimes come up with a better resolution by
+viewing the original.
+
+
+HOW TO RESOLVE CONFLICTS
+------------------------
+
After seeing a conflict, you can do two things:
* Decide not to merge. The only clean-up you need are to reset
@@ -126,13 +191,25 @@ After seeing a conflict, you can do two things:
up working tree changes made by 2. and 3.; 'git-reset --hard' can
be used for this.
- * Resolve the conflicts. `git diff` would report only the
- conflicting paths because of the above 2. and 3.
- Edit the working tree files into a desirable shape
- ('git mergetool' can ease this task), 'git-add' or 'git-rm'
- them, to make the index file contain what the merge result
- should be, and run 'git-commit' to commit the result.
+ * Resolve the conflicts. Git will mark the conflicts in
+ the working tree. Edit the files into shape and
+ 'git-add' to the index. 'git-commit' to seal the deal.
+
+You can work through the conflict with a number of tools:
+
+ * Use a mergetool. 'git mergetool' to launch a graphical
+ mergetool which will work you through the merge.
+
+ * Look at the diffs. 'git diff' will show a three-way diff,
+ highlighting changes from both the HEAD and remote versions.
+
+ * Look at the diffs on their own. 'git log --merge -p <path>'
+ will show diffs first for the HEAD version and then the
+ remote version.
+ * Look at the originals. 'git show :1:filename' shows the
+ common ancestor, 'git show :2:filename' shows the HEAD
+ version and 'git show :3:filename' shows the remote version.
SEE ALSO
--------
diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt
index 54f1dab..da6055d 100644
--- a/Documentation/git-prune.txt
+++ b/Documentation/git-prune.txt
@@ -8,7 +8,7 @@ git-prune - Prune all unreachable objects from the object database
SYNOPSIS
--------
-'git-prune' [-n] [--expire <expire>] [--] [<head>...]
+'git-prune' [-n] [-v] [--expire <expire>] [--] [<head>...]
DESCRIPTION
-----------
@@ -34,6 +34,9 @@ OPTIONS
Do not remove anything; just report what it would
remove.
+-v::
+ Report all removed objects.
+
\--::
Do not interpret any more arguments as options.
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
index 309deac..7160fa1 100644
--- a/Documentation/git-read-tree.txt
+++ b/Documentation/git-read-tree.txt
@@ -212,7 +212,7 @@ output after two-tree merge.
Case #3 is slightly tricky and needs explanation. The result from this
rule logically should be to remove the path if the user staged the removal
-of the path and then swiching to a new branch. That however will prevent
+of the path and then switching to a new branch. That however will prevent
the initial checkout from happening, so the rule is modified to use M (new
tree) only when the contents of the index is empty. Otherwise the removal
of the path is kept as long as $H and $M are the same.
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 59c1b02..c8ad86a 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git rebase' [-i | --interactive] [-v | --verbose] [-m | --merge]
- [-s <strategy> | --strategy=<strategy>]
+ [-s <strategy> | --strategy=<strategy>] [--no-verify]
[-C<n>] [ --whitespace=<option>] [-p | --preserve-merges]
[--onto <newbase>] <upstream> [<branch>]
'git rebase' --continue | --skip | --abort
@@ -92,7 +92,7 @@ branch to another, to pretend that you forked the topic branch
from the latter branch, using `rebase --onto`.
First let's assume your 'topic' is based on branch 'next'.
-For example feature developed in 'topic' depends on some
+For example, a feature developed in 'topic' depends on some
functionality which is found in 'next'.
------------
@@ -103,9 +103,9 @@ functionality which is found in 'next'.
o---o---o topic
------------
-We would want to make 'topic' forked from branch 'master',
-for example because the functionality 'topic' branch depend on
-got merged into more stable 'master' branch, like this:
+We want to make 'topic' forked from branch 'master'; for example,
+because the functionality on which 'topic' depends was merged into the
+more stable 'master' branch. We want our tree to look like this:
------------
o---o---o---o---o master
@@ -232,6 +232,9 @@ OPTIONS
--verbose::
Display a diffstat of what changed upstream since the last rebase.
+--no-verify::
+ This option bypasses the pre-rebase hook. See also linkgit:githooks[5].
+
-C<n>::
Ensure at least <n> lines of surrounding context match before
and after each change. When fewer lines of surrounding
@@ -250,18 +253,16 @@ OPTIONS
-p::
--preserve-merges::
- Instead of ignoring merges, try to recreate them. This option
- only works in interactive mode.
+ Instead of ignoring merges, try to recreate them.
include::merge-strategies.txt[]
NOTES
-----
-When you rebase a branch, you are changing its history in a way that
-will cause problems for anyone who already has a copy of the branch
-in their repository and tries to pull updates from you. You should
-understand the implications of using 'git-rebase' on a repository that
-you share.
+
+You should understand the implications of using 'git-rebase' on a
+repository that you share. See also RECOVERING FROM UPSTREAM REBASE
+below.
When the git-rebase command is run, it will first execute a "pre-rebase"
hook if one exists. You can use this hook to do sanity checks and
@@ -396,6 +397,127 @@ consistent (they compile, pass the testsuite, etc.) you should use
after each commit, test, and amend the commit if fixes are necessary.
+RECOVERING FROM UPSTREAM REBASE
+-------------------------------
+
+Rebasing (or any other form of rewriting) a branch that others have
+based work on is a bad idea: anyone downstream of it is forced to
+manually fix their history. This section explains how to do the fix
+from the downstream's point of view. The real fix, however, would be
+to avoid rebasing the upstream in the first place.
+
+To illustrate, suppose you are in a situation where someone develops a
+'subsystem' branch, and you are working on a 'topic' that is dependent
+on this 'subsystem'. You might end up with a history like the
+following:
+
+------------
+ o---o---o---o---o---o---o---o---o master
+ \
+ o---o---o---o---o subsystem
+ \
+ *---*---* topic
+------------
+
+If 'subsystem' is rebased against 'master', the following happens:
+
+------------
+ o---o---o---o---o---o---o---o master
+ \ \
+ o---o---o---o---o o'--o'--o'--o'--o' subsystem
+ \
+ *---*---* topic
+------------
+
+If you now continue development as usual, and eventually merge 'topic'
+to 'subsystem', the commits from 'subsystem' will remain duplicated forever:
+
+------------
+ o---o---o---o---o---o---o---o master
+ \ \
+ o---o---o---o---o o'--o'--o'--o'--o'--M subsystem
+ \ /
+ *---*---*-..........-*--* topic
+------------
+
+Such duplicates are generally frowned upon because they clutter up
+history, making it harder to follow. To clean things up, you need to
+transplant the commits on 'topic' to the new 'subsystem' tip, i.e.,
+rebase 'topic'. This becomes a ripple effect: anyone downstream from
+'topic' is forced to rebase too, and so on!
+
+There are two kinds of fixes, discussed in the following subsections:
+
+Easy case: The changes are literally the same.::
+
+ This happens if the 'subsystem' rebase was a simple rebase and
+ had no conflicts.
+
+Hard case: The changes are not the same.::
+
+ This happens if the 'subsystem' rebase had conflicts, or used
+ `\--interactive` to omit, edit, or squash commits; or if the
+ upstream used one of `commit \--amend`, `reset`, or
+ `filter-branch`.
+
+
+The easy case
+~~~~~~~~~~~~~
+
+Only works if the changes (patch IDs based on the diff contents) on
+'subsystem' are literally the same before and after the rebase
+'subsystem' did.
+
+In that case, the fix is easy because 'git-rebase' knows to skip
+changes that are already present in the new upstream. So if you say
+(assuming you're on 'topic')
+------------
+ $ git rebase subsystem
+------------
+you will end up with the fixed history
+------------
+ o---o---o---o---o---o---o---o master
+ \
+ o'--o'--o'--o'--o' subsystem
+ \
+ *---*---* topic
+------------
+
+
+The hard case
+~~~~~~~~~~~~~
+
+Things get more complicated if the 'subsystem' changes do not exactly
+correspond to the ones before the rebase.
+
+NOTE: While an "easy case recovery" sometimes appears to be successful
+ even in the hard case, it may have unintended consequences. For
+ example, a commit that was removed via `git rebase
+ \--interactive` will be **resurrected**!
+
+The idea is to manually tell 'git-rebase' "where the old 'subsystem'
+ended and your 'topic' began", that is, what the old merge-base
+between them was. You will have to find a way to name the last commit
+of the old 'subsystem', for example:
+
+* With the 'subsystem' reflog: after 'git-fetch', the old tip of
+ 'subsystem' is at `subsystem@\{1}`. Subsequent fetches will
+ increase the number. (See linkgit:git-reflog[1].)
+
+* Relative to the tip of 'topic': knowing that your 'topic' has three
+ commits, the old tip of 'subsystem' must be `topic~3`.
+
+You can then transplant the old `subsystem..topic` to the new tip by
+saying (for the reflog case, and assuming you are on 'topic' already):
+------------
+ $ git rebase --onto subsystem subsystem@{1}
+------------
+
+The ripple effect of a "hard case" recovery is especially bad:
+'everyone' downstream from 'topic' will now have to perform a "hard
+case" recovery too!
+
+
Authors
------
Written by Junio C Hamano <gitster@pobox.com> and
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 6abaeac..52aab5e 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -82,7 +82,9 @@ $ git reset --hard HEAD~3 <1>
+
<1> The last three commits (HEAD, HEAD^, and HEAD~2) were bad
and you do not want to ever see them again. Do *not* do this if
-you have already given these commits to somebody else.
+you have already given these commits to somebody else. (See the
+"RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1] for
+the implications of doing so.)
Undo a commit, making it a topic branch::
+
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 3c3e1b0..82f5056 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -11,7 +11,6 @@ SYNOPSIS
'git send-email' [options] <file|directory> [... file|directory]
-
DESCRIPTION
-----------
Takes the patches given on the command line and emails them out.
@@ -20,12 +19,16 @@ The header of the email is configurable by command line options. If not
specified on the command line, the user will be prompted with a ReadLine
enabled interface to provide the necessary information.
+
OPTIONS
-------
-The options available are:
+
+Composing
+~~~~~~~~~
--bcc::
- Specify a "Bcc:" value for each email.
+ Specify a "Bcc:" value for each email. Default is the value of
+ 'sendemail.bcc'.
+
The --bcc option must be repeated for each user you want on the bcc list.
@@ -34,22 +37,6 @@ The --bcc option must be repeated for each user you want on the bcc list.
+
The --cc option must be repeated for each user you want on the cc list.
---cc-cmd::
- Specify a command to execute once per patch file which
- should generate patch file specific "Cc:" entries.
- Output of this command must be single email address per line.
- Default is the value of 'sendemail.cccmd' configuration value.
-
---chain-reply-to::
---no-chain-reply-to::
- If this is set, each email will be sent as a reply to the previous
- email sent. If disabled with "--no-chain-reply-to", all emails after
- the first will be sent as replies to the first email sent. When using
- this, it is recommended that the first file given be an overview of the
- entire patch series.
- Default is the value of the 'sendemail.chainreplyto' configuration
- value; if that is unspecified, default to --chain-reply-to.
-
--compose::
Use $GIT_EDITOR, core.editor, $VISUAL, or $EDITOR to edit an
introductory message for the patch series.
@@ -66,22 +53,47 @@ The --cc option must be repeated for each user you want on the cc list.
Only necessary if --compose is also set. If --compose
is not set, this will be prompted for.
---signed-off-by-cc::
---no-signed-off-by-cc::
- If this is set, add emails found in Signed-off-by: or Cc: lines to the
- cc list.
- Default is the value of 'sendemail.signedoffcc' configuration value;
- if that is unspecified, default to --signed-off-by-cc.
+--subject::
+ Specify the initial subject of the email thread.
+ Only necessary if --compose is also set. If --compose
+ is not set, this will be prompted for.
+
+--to::
+ Specify the primary recipient of the emails generated. Generally, this
+ will be the upstream maintainer of the project involved. Default is the
+ value of the 'sendemail.to' configuration value; if that is unspecified,
+ this will be prompted for.
++
+The --to option must be repeated for each user you want on the to list.
---quiet::
- Make git-send-email less verbose. One line per email should be
- all that is output.
---identity::
- A configuration identity. When given, causes values in the
- 'sendemail.<identity>' subsection to take precedence over
- values in the 'sendemail' section. The default identity is
- the value of 'sendemail.identity'.
+Sending
+~~~~~~~
+
+--envelope-sender::
+ Specify the envelope sender used to send the emails.
+ This is useful if your default address is not the address that is
+ subscribed to a list. If you use the sendmail binary, you must have
+ suitable privileges for the -f parameter. Default is the value of
+ the 'sendemail.envelopesender' configuration variable; if that is
+ unspecified, choosing the envelope sender is left to your MTA.
+
+--smtp-encryption::
+ Specify the encryption to use, either 'ssl' or 'tls'. Any other
+ value reverts to plain SMTP. Default is the value of
+ 'sendemail.smtpencryption'.
+
+--smtp-pass::
+ Password for SMTP-AUTH. The argument is optional: If no
+ argument is specified, then the empty string is used as
+ the password. Default is the value of 'sendemail.smtppass',
+ however '--smtp-pass' always overrides this value.
++
+Furthermore, passwords need not be specified in configuration files
+or on the command line. If a username has been specified (with
+'--smtp-user' or a 'sendemail.smtpuser'), but no password has been
+specified (with '--smtp-pass' or 'sendemail.smtppass'), then the
+user is prompted for a password while the input is masked for privacy.
--smtp-server::
If set, specifies the outgoing SMTP server to use (e.g.
@@ -96,61 +108,44 @@ The --cc option must be repeated for each user you want on the cc list.
--smtp-server-port::
Specifies a port different from the default port (SMTP
servers typically listen to smtp port 25 and ssmtp port
- 465).
+ 465). This can be set with 'sendemail.smtpserverport'.
+
+--smtp-ssl::
+ Legacy alias for '--smtp-encryption ssl'.
--smtp-user::
- Username for SMTP-AUTH. In place of this option, the following
- configuration variables can be specified:
-+
---
- * sendemail.smtpuser
- * sendemail.<identity>.smtpuser (see sendemail.identity).
---
-+
-However, --smtp-user always overrides these variables.
-+
-If a username is not specified (with --smtp-user or a
-configuration variable), then authentication is not attempted.
+ Username for SMTP-AUTH. Default is the value of 'sendemail.smtpuser';
+ if a username is not specified (with '--smtp-user' or 'sendemail.smtpuser'),
+ then authentication is not attempted.
---smtp-pass::
- Password for SMTP-AUTH. The argument is optional: If no
- argument is specified, then the empty string is used as
- the password.
-+
-In place of this option, the following configuration variables
-can be specified:
-+
---
- * sendemail.smtppass
- * sendemail.<identity>.smtppass (see sendemail.identity).
---
-+
-However, --smtp-pass always overrides these variables.
-+
-Furthermore, passwords need not be specified in configuration files
-or on the command line. If a username has been specified (with
---smtp-user or a configuration variable), but no password has been
-specified (with --smtp-pass or a configuration variable), then the
-user is prompted for a password while the input is masked for privacy.
---smtp-encryption::
- Specify the encryption to use, either 'ssl' or 'tls'. Any other
- value reverts to plain SMTP. Default is the value of
- 'sendemail.smtpencryption'.
+Automating
+~~~~~~~~~~
---smtp-ssl::
- Legacy alias for '--smtp-encryption=ssl'.
+--cc-cmd::
+ Specify a command to execute once per patch file which
+ should generate patch file specific "Cc:" entries.
+ Output of this command must be single email address per line.
+ Default is the value of 'sendemail.cccmd' configuration value.
---subject::
- Specify the initial subject of the email thread.
- Only necessary if --compose is also set. If --compose
- is not set, this will be prompted for.
+--[no-]chain-reply-to::
+ If this is set, each email will be sent as a reply to the previous
+ email sent. If disabled with "--no-chain-reply-to", all emails after
+ the first will be sent as replies to the first email sent. When using
+ this, it is recommended that the first file given be an overview of the
+ entire patch series. Default is the value of the 'sendemail.chainreplyto'
+ configuration value; if that is unspecified, default to --chain-reply-to.
+
+--identity::
+ A configuration identity. When given, causes values in the
+ 'sendemail.<identity>' subsection to take precedence over
+ values in the 'sendemail' section. The default identity is
+ the value of 'sendemail.identity'.
---suppress-from::
---no-suppress-from::
- If this is set, do not add the From: address to the cc: list.
- Default is the value of 'sendemail.suppressfrom' configuration value;
- if that is unspecified, default to --no-suppress-from.
+--[no-]signed-off-by-cc::
+ If this is set, add emails found in Signed-off-by: or Cc: lines to the
+ cc list. Default is the value of 'sendemail.signedoffbycc' configuration
+ value; if that is unspecified, default to --signed-off-by-cc.
--suppress-cc::
Specify an additional category of recipients to suppress the
@@ -163,44 +158,43 @@ user is prompted for a password while the input is masked for privacy.
if that is unspecified, default to 'self' if --suppress-from is
specified, as well as 'sob' if --no-signed-off-cc is specified.
---thread::
---no-thread::
+--[no-]suppress-from::
+ If this is set, do not add the From: address to the cc: list.
+ Default is the value of 'sendemail.suppressfrom' configuration
+ value; if that is unspecified, default to --no-suppress-from.
+
+--[no-]thread::
If this is set, the In-Reply-To header will be set on each email sent.
If disabled with "--no-thread", no emails will have the In-Reply-To
- header set.
- Default is the value of the 'sendemail.thread' configuration value;
- if that is unspecified, default to --thread.
+ header set. Default is the value of the 'sendemail.thread' configuration
+ value; if that is unspecified, default to --thread.
+
+
+Administering
+~~~~~~~~~~~~~
--dry-run::
Do everything except actually send the emails.
---envelope-sender::
- Specify the envelope sender used to send the emails.
- This is useful if your default address is not the address that is
- subscribed to a list. If you use the sendmail binary, you must have
- suitable privileges for the -f parameter.
- Default is the value of the 'sendemail.envelopesender' configuration
- variable; if that is unspecified, choosing the envelope sender is left
- to your MTA.
+--quiet::
+ Make git-send-email less verbose. One line per email should be
+ all that is output.
---to::
- Specify the primary recipient of the emails generated.
- Generally, this will be the upstream maintainer of the
- project involved.
- Default is the value of the 'sendemail.to' configuration value;
- if that is unspecified, this will be prompted for.
+--[no-]validate::
+ Perform sanity checks on patches.
+ Currently, validation means the following:
+
-The --to option must be repeated for each user you want on the to list.
+--
+ * Warn of patches that contain lines longer than 998 characters; this
+ is due to SMTP limits as described by http://www.ietf.org/rfc/rfc2821.txt.
+--
++
+Default is the value of 'sendemail.validate'; if this is not set,
+default to '--validate'.
CONFIGURATION
-------------
-sendemail.identity::
- The default configuration identity. When specified,
- 'sendemail.<identity>.<item>' will have higher precedence than
- 'sendemail.<item>'. This is useful to declare multiple SMTP
- identities and to hoist sensitive authentication information
- out of the repository and into the global configuration file.
sendemail.aliasesfile::
To avoid typing long email addresses, point this to one or more
@@ -210,38 +204,6 @@ sendemail.aliasfiletype::
Format of the file(s) specified in sendemail.aliasesfile. Must be
one of 'mutt', 'mailrc', 'pine', or 'gnus'.
-sendemail.to::
- Email address (or alias) to always send to.
-
-sendemail.cccmd::
- Command to execute to generate per patch file specific "Cc:"s.
-
-sendemail.bcc::
- Email address (or alias) to always bcc.
-
-sendemail.chainreplyto::
- Boolean value specifying the default to the '--chain_reply_to'
- parameter.
-
-sendemail.smtpserver::
- Default SMTP server to use.
-
-sendemail.smtpserverport::
- Default SMTP server port to use.
-
-sendemail.smtpuser::
- Default SMTP-AUTH username.
-
-sendemail.smtppass::
- Default SMTP-AUTH password.
-
-sendemail.smtpencryption::
- Default encryption method. Use 'ssl' for SSL (and specify an
- appropriate port), or 'tls' for TLS. Takes precedence over
- 'smtpssl' if both are specified.
-
-sendemail.smtpssl::
- Legacy boolean that sets 'smtpencryption=ssl' if enabled.
Author
------
@@ -250,10 +212,12 @@ Written by Ryan Anderson <ryan@michonline.com>
git-send-email is originally based upon
send_lots_of_email.pl by Greg Kroah-Hartman.
+
Documentation
--------------
Documentation by Ryan Anderson
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt
index bf33b0c..babaa9b 100644
--- a/Documentation/git-submodule.txt
+++ b/Documentation/git-submodule.txt
@@ -14,6 +14,8 @@ SYNOPSIS
'git submodule' [--quiet] init [--] [<path>...]
'git submodule' [--quiet] update [--init] [--] [<path>...]
'git submodule' [--quiet] summary [--summary-limit <n>] [commit] [--] [<path>...]
+'git submodule' [--quiet] foreach <command>
+'git submodule' [--quiet] sync [--] [<path>...]
DESCRIPTION
@@ -123,6 +125,30 @@ summary::
in the submodule between the given super project commit and the
index or working tree (switched by --cached) are shown.
+foreach::
+ Evaluates an arbitrary shell command in each checked out submodule.
+ The command has access to the variables $path and $sha1:
+ $path is the name of the submodule directory relative to the
+ superproject, and $sha1 is the commit as recorded in the superproject.
+ Any submodules defined in the superproject but not checked out are
+ ignored by this command. Unless given --quiet, foreach prints the name
+ of each submodule before evaluating the command.
+ A non-zero return from the command in any submodule causes
+ the processing to terminate. This can be overridden by adding '|| :'
+ to the end of the command.
++
+As an example, "git submodule foreach 'echo $path `git rev-parse HEAD`' will
+show the path and currently checked out commit for each submodule.
+
+sync::
+ Synchronizes submodules' remote URL configuration setting
+ to the value specified in .gitmodules. This is useful when
+ submodule URLs change upstream and you need to update your local
+ repositories accordingly.
++
+"git submodule sync" synchronizes all submodules while
+"git submodule sync -- A" synchronizes submodule "A" only.
+
OPTIONS
-------
-q::
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index 82d03b4..84c8f3c 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -149,6 +149,22 @@ and have no uncommitted changes.
is very strongly discouraged.
--
+'branch'::
+ Create a branch in the SVN repository.
+
+-m;;
+--message;;
+ Allows to specify the commit message.
+
+-t;;
+--tag;;
+ Create a tag by using the tags_subdir instead of the branches_subdir
+ specified during git svn init.
+
+'tag'::
+ Create a tag in the SVN repository. This is a shorthand for
+ 'branch -t'.
+
'log'::
This should make it easy to look up svn log messages when svn
users refer to -r/--revision numbers.
@@ -372,7 +388,8 @@ Passed directly to 'git-rebase' when using 'dcommit' if a
-n::
--dry-run::
-This can be used with the 'dcommit' and 'rebase' commands.
+This can be used with the 'dcommit', 'rebase', 'branch' and 'tag'
+commands.
For 'dcommit', print out the series of git arguments that would show
which diffs would be committed to SVN.
@@ -381,6 +398,9 @@ For 'rebase', display the local branch associated with the upstream svn
repository associated with the current branch and the URL of svn
repository that will be fetched from.
+For 'branch' and 'tag', display the urls that will be used for copying when
+creating the branch or tag.
+
--
ADVANCED OPTIONS
@@ -498,6 +518,8 @@ Tracking and contributing to an entire Subversion-managed project
git svn clone http://svn.example.com/project -T trunk -b branches -t tags
# View all branches and tags you have cloned:
git branch -r
+# Create a new branch in SVN
+ git svn branch waldo
# Reset your master to trunk (or any other branch, replacing 'trunk'
# with the appropriate name):
git reset --hard remotes/trunk
diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt
index 7f7a45b..278cf73 100644
--- a/Documentation/git-web--browse.txt
+++ b/Documentation/git-web--browse.txt
@@ -26,6 +26,7 @@ The following browsers (or commands) are currently supported:
* lynx
* dillo
* open (this is the default under Mac OS X GUI)
+* start (this is the default under MinGW)
Custom commands may also be specified.
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 53da9b4..2694559 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -311,10 +311,18 @@ patterns are available:
- `bibtex` suitable for files with BibTeX coded references.
+- `html` suitable for HTML/XHTML documents.
+
- `java` suitable for source code in the Java language.
+- `objc` suitable for source code in the Objective-C language.
+
- `pascal` suitable for source code in the Pascal/Delphi language.
+- `php` suitable for source code in the PHP language.
+
+- `python` suitable for source code in the Python language.
+
- `ruby` suitable for source code in the Ruby language.
- `tex` suitable for source code for LaTeX documents.
diff --git a/Documentation/gittutorial-2.txt b/Documentation/gittutorial-2.txt
index 6609046..bab0f34 100644
--- a/Documentation/gittutorial-2.txt
+++ b/Documentation/gittutorial-2.txt
@@ -32,22 +32,27 @@ Initialized empty Git repository in .git/
$ echo 'hello world' > file.txt
$ git add .
$ git commit -a -m "initial commit"
-Created initial commit 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
+[master (root-commit)] created 54196cc: "initial commit"
+ 1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 file.txt
$ echo 'hello world!' >file.txt
$ git commit -a -m "add emphasis"
-Created commit c4d59f390b9cfd4318117afde11d601c1085f241
+[master] created c4d59f3: "add emphasis"
+ 1 files changed, 1 insertions(+), 1 deletions(-)
------------------------------------------------
-What are the 40 digits of hex that git responded to the commit with?
+What are the 7 digits of hex that git responded to the commit with?
We saw in part one of the tutorial that commits have names like this.
It turns out that every object in the git history is stored under
-such a 40-digit hex name. That name is the SHA1 hash of the object's
+a 40-digit hex name. That name is the SHA1 hash of the object's
contents; among other things, this ensures that git will never store
the same data twice (since identical data is given an identical SHA1
name), and that the contents of a git object will never change (since
-that would change the object's name as well).
+that would change the object's name as well). The 7 char hex strings
+here are simply the abbreviation of such 40 character long strings.
+Abbreviations can be used everywhere where the 40 character strings
+can be used, so long as they are unambiguous.
It is expected that the content of the commit object you created while
following the example above generates a different SHA1 hash than
diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt
new file mode 100644
index 0000000..2b021e3
--- /dev/null
+++ b/Documentation/gitworkflows.txt
@@ -0,0 +1,364 @@
+gitworkflows(7)
+===============
+
+NAME
+----
+gitworkflows - An overview of recommended workflows with git
+
+SYNOPSIS
+--------
+git *
+
+
+DESCRIPTION
+-----------
+
+This document attempts to write down and motivate some of the workflow
+elements used for `git.git` itself. Many ideas apply in general,
+though the full workflow is rarely required for smaller projects with
+fewer people involved.
+
+We formulate a set of 'rules' for quick reference, while the prose
+tries to motivate each of them. Do not always take them literally;
+you should value good reasons for your actions higher than manpages
+such as this one.
+
+
+SEPARATE CHANGES
+----------------
+
+As a general rule, you should try to split your changes into small
+logical steps, and commit each of them. They should be consistent,
+working independently of any later commits, pass the test suite, etc.
+This makes the review process much easier, and the history much more
+useful for later inspection and analysis, for example with
+linkgit:git-blame[1] and linkgit:git-bisect[1].
+
+To achieve this, try to split your work into small steps from the very
+beginning. It is always easier to squash a few commits together than
+to split one big commit into several. Don't be afraid of making too
+small or imperfect steps along the way. You can always go back later
+and edit the commits with `git rebase \--interactive` before you
+publish them. You can use `git stash save \--keep-index` to run the
+test suite independent of other uncommitted changes; see the EXAMPLES
+section of linkgit:git-stash[1].
+
+
+MANAGING BRANCHES
+-----------------
+
+There are two main tools that can be used to include changes from one
+branch on another: linkgit:git-merge[1] and
+linkgit:git-cherry-pick[1].
+
+Merges have many advantages, so we try to solve as many problems as
+possible with merges alone. Cherry-picking is still occasionally
+useful; see "Merging upwards" below for an example.
+
+Most importantly, merging works at the branch level, while
+cherry-picking works at the commit level. This means that a merge can
+carry over the changes from 1, 10, or 1000 commits with equal ease,
+which in turn means the workflow scales much better to a large number
+of contributors (and contributions). Merges are also easier to
+understand because a merge commit is a "promise" that all changes from
+all its parents are now included.
+
+There is a tradeoff of course: merges require a more careful branch
+management. The following subsections discuss the important points.
+
+
+Graduation
+~~~~~~~~~~
+
+As a given feature goes from experimental to stable, it also
+"graduates" between the corresponding branches of the software.
+`git.git` uses the following 'integration branches':
+
+* 'maint' tracks the commits that should go into the next "maintenance
+ release", i.e., update of the last released stable version;
+
+* 'master' tracks the commits that should go into the next release;
+
+* 'next' is intended as a testing branch for topics being tested for
+ stability for master.
+
+There is a fourth official branch that is used slightly differently:
+
+* 'pu' (proposed updates) is an integration branch for things that are
+ not quite ready for inclusion yet (see "Integration Branches"
+ below).
+
+Each of the four branches is usually a direct descendant of the one
+above it.
+
+Conceptually, the feature enters at an unstable branch (usually 'next'
+or 'pu'), and "graduates" to 'master' for the next release once it is
+considered stable enough.
+
+
+Merging upwards
+~~~~~~~~~~~~~~~
+
+The "downwards graduation" discussed above cannot be done by actually
+merging downwards, however, since that would merge 'all' changes on
+the unstable branch into the stable one. Hence the following:
+
+.Merge upwards
+[caption="Rule: "]
+=====================================
+Always commit your fixes to the oldest supported branch that require
+them. Then (periodically) merge the integration branches upwards into each
+other.
+=====================================
+
+This gives a very controlled flow of fixes. If you notice that you
+have applied a fix to e.g. 'master' that is also required in 'maint',
+you will need to cherry-pick it (using linkgit:git-cherry-pick[1])
+downwards. This will happen a few times and is nothing to worry about
+unless you do it very frequently.
+
+
+Topic branches
+~~~~~~~~~~~~~~
+
+Any nontrivial feature will require several patches to implement, and
+may get extra bugfixes or improvements during its lifetime.
+
+Committing everything directly on the integration branches leads to many
+problems: Bad commits cannot be undone, so they must be reverted one
+by one, which creates confusing histories and further error potential
+when you forget to revert part of a group of changes. Working in
+parallel mixes up the changes, creating further confusion.
+
+Use of "topic branches" solves these problems. The name is pretty
+self explanatory, with a caveat that comes from the "merge upwards"
+rule above:
+
+.Topic branches
+[caption="Rule: "]
+=====================================
+Make a side branch for every topic (feature, bugfix, ...). Fork it off
+at the oldest integration branch that you will eventually want to merge it
+into.
+=====================================
+
+Many things can then be done very naturally:
+
+* To get the feature/bugfix into an integration branch, simply merge
+ it. If the topic has evolved further in the meantime, merge again.
+ (Note that you do not necessarily have to merge it to the oldest
+ integration branch first. For example, you can first merge a bugfix
+ to 'next', give it some testing time, and merge to 'maint' when you
+ know it is stable.)
+
+* If you find you need new features from the branch 'other' to continue
+ working on your topic, merge 'other' to 'topic'. (However, do not
+ do this "just habitually", see below.)
+
+* If you find you forked off the wrong branch and want to move it
+ "back in time", use linkgit:git-rebase[1].
+
+Note that the last point clashes with the other two: a topic that has
+been merged elsewhere should not be rebased. See the section on
+RECOVERING FROM UPSTREAM REBASE in linkgit:git-rebase[1].
+
+We should point out that "habitually" (regularly for no real reason)
+merging an integration branch into your topics -- and by extension,
+merging anything upstream into anything downstream on a regular basis
+-- is frowned upon:
+
+.Merge to downstream only at well-defined points
+[caption="Rule: "]
+=====================================
+Do not merge to downstream except with a good reason: upstream API
+changes affect your branch; your branch no longer merges to upstream
+cleanly; etc.
+=====================================
+
+Otherwise, the topic that was merged to suddenly contains more than a
+single (well-separated) change. The many resulting small merges will
+greatly clutter up history. Anyone who later investigates the history
+of a file will have to find out whether that merge affected the topic
+in development. An upstream might even inadvertently be merged into a
+"more stable" branch. And so on.
+
+
+Throw-away integration
+~~~~~~~~~~~~~~~~~~~~~~
+
+If you followed the last paragraph, you will now have many small topic
+branches, and occasionally wonder how they interact. Perhaps the
+result of merging them does not even work? But on the other hand, we
+want to avoid merging them anywhere "stable" because such merges
+cannot easily be undone.
+
+The solution, of course, is to make a merge that we can undo: merge
+into a throw-away branch.
+
+.Throw-away integration branches
+[caption="Rule: "]
+=====================================
+To test the interaction of several topics, merge them into a
+throw-away branch. You must never base any work on such a branch!
+=====================================
+
+If you make it (very) clear that this branch is going to be deleted
+right after the testing, you can even publish this branch, for example
+to give the testers a chance to work with it, or other developers a
+chance to see if their in-progress work will be compatible. `git.git`
+has such an official throw-away integration branch called 'pu'.
+
+
+DISTRIBUTED WORKFLOWS
+---------------------
+
+After the last section, you should know how to manage topics. In
+general, you will not be the only person working on the project, so
+you will have to share your work.
+
+Roughly speaking, there are two important workflows: merge and patch.
+The important difference is that the merge workflow can propagate full
+history, including merges, while patches cannot. Both workflows can
+be used in parallel: in `git.git`, only subsystem maintainers use
+the merge workflow, while everyone else sends patches.
+
+Note that the maintainer(s) may impose restrictions, such as
+"Signed-off-by" requirements, that all commits/patches submitted for
+inclusion must adhere to. Consult your project's documentation for
+more information.
+
+
+Merge workflow
+~~~~~~~~~~~~~~
+
+The merge workflow works by copying branches between upstream and
+downstream. Upstream can merge contributions into the official
+history; downstream base their work on the official history.
+
+There are three main tools that can be used for this:
+
+* linkgit:git-push[1] copies your branches to a remote repository,
+ usually to one that can be read by all involved parties;
+
+* linkgit:git-fetch[1] that copies remote branches to your repository;
+ and
+
+* linkgit:git-pull[1] that does fetch and merge in one go.
+
+Note the last point. Do 'not' use 'git-pull' unless you actually want
+to merge the remote branch.
+
+Getting changes out is easy:
+
+.Push/pull: Publishing branches/topics
+[caption="Recipe: "]
+=====================================
+`git push <remote> <branch>` and tell everyone where they can fetch
+from.
+=====================================
+
+You will still have to tell people by other means, such as mail. (Git
+provides the linkgit:git-request-pull[1] to send preformatted pull
+requests to upstream maintainers to simplify this task.)
+
+If you just want to get the newest copies of the integration branches,
+staying up to date is easy too:
+
+.Push/pull: Staying up to date
+[caption="Recipe: "]
+=====================================
+Use `git fetch <remote>` or `git remote update` to stay up to date.
+=====================================
+
+Then simply fork your topic branches from the stable remotes as
+explained earlier.
+
+If you are a maintainer and would like to merge other people's topic
+branches to the integration branches, they will typically send a
+request to do so by mail. Such a request looks like
+
+-------------------------------------
+Please pull from
+ <url> <branch>
+-------------------------------------
+
+In that case, 'git-pull' can do the fetch and merge in one go, as
+follows.
+
+.Push/pull: Merging remote topics
+[caption="Recipe: "]
+=====================================
+`git pull <url> <branch>`
+=====================================
+
+Occasionally, the maintainer may get merge conflicts when he tries to
+pull changes from downstream. In this case, he can ask downstream to
+do the merge and resolve the conflicts themselves (perhaps they will
+know better how to resolve them). It is one of the rare cases where
+downstream 'should' merge from upstream.
+
+
+Patch workflow
+~~~~~~~~~~~~~~
+
+If you are a contributor that sends changes upstream in the form of
+emails, you should use topic branches as usual (see above). Then use
+linkgit:git-format-patch[1] to generate the corresponding emails
+(highly recommended over manually formatting them because it makes the
+maintainer's life easier).
+
+.format-patch/am: Publishing branches/topics
+[caption="Recipe: "]
+=====================================
+* `git format-patch -M upstream..topic` to turn them into preformatted
+ patch files
+* `git send-email --to=<recipient> <patches>`
+=====================================
+
+See the linkgit:git-format-patch[1] and linkgit:git-send-email[1]
+manpages for further usage notes.
+
+If the maintainer tells you that your patch no longer applies to the
+current upstream, you will have to rebase your topic (you cannot use a
+merge because you cannot format-patch merges):
+
+.format-patch/am: Keeping topics up to date
+[caption="Recipe: "]
+=====================================
+`git pull --rebase <url> <branch>`
+=====================================
+
+You can then fix the conflicts during the rebase. Presumably you have
+not published your topic other than by mail, so rebasing it is not a
+problem.
+
+If you receive such a patch series (as maintainer, or perhaps as a
+reader of the mailing list it was sent to), save the mails to files,
+create a new topic branch and use 'git-am' to import the commits:
+
+.format-patch/am: Importing patches
+[caption="Recipe: "]
+=====================================
+`git am < patch`
+=====================================
+
+One feature worth pointing out is the three-way merge, which can help
+if you get conflicts: `git am -3` will use index information contained
+in patches to figure out the merge base. See linkgit:git-am[1] for
+other options.
+
+
+SEE ALSO
+--------
+linkgit:gittutorial[7],
+linkgit:git-push[1],
+linkgit:git-pull[1],
+linkgit:git-merge[1],
+linkgit:git-rebase[1],
+linkgit:git-format-patch[1],
+linkgit:git-send-email[1],
+linkgit:git-am[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite.
diff --git a/Documentation/howto/rebase-and-edit.txt b/Documentation/howto/rebase-and-edit.txt
deleted file mode 100644
index 554909f..0000000
--- a/Documentation/howto/rebase-and-edit.txt
+++ /dev/null
@@ -1,79 +0,0 @@
-Date: Sat, 13 Aug 2005 22:16:02 -0700 (PDT)
-From: Linus Torvalds <torvalds@osdl.org>
-To: Steve French <smfrench@austin.rr.com>
-cc: git@vger.kernel.org
-Subject: Re: sending changesets from the middle of a git tree
-Abstract: In this article, Linus demonstrates how a broken commit
- in a sequence of commits can be removed by rewinding the head and
- reapplying selected changes.
-
-On Sat, 13 Aug 2005, Linus Torvalds wrote:
-
-> That's correct. Same things apply: you can move a patch over, and create a
-> new one with a modified comment, but basically the _old_ commit will be
-> immutable.
-
-Let me clarify.
-
-You can entirely _drop_ old branches, so commits may be immutable, but
-nothing forces you to keep them. Of course, when you drop a commit, you'll
-always end up dropping all the commits that depended on it, and if you
-actually got somebody else to pull that commit you can't drop it from
-_their_ repository, but undoing things is not impossible.
-
-For example, let's say that you've made a mess of things: you've committed
-three commits "old->a->b->c", and you notice that "a" was broken, but you
-want to save "b" and "c". What you can do is
-
- # Create a branch "broken" that is the current code
- # for reference
- git branch broken
-
- # Reset the main branch to three parents back: this
- # effectively undoes the three top commits
- git reset HEAD^^^
- git checkout -f
-
- # Check the result visually to make sure you know what's
- # going on
- gitk --all
-
- # Re-apply the two top ones from "broken"
- #
- # First "parent of broken" (aka b):
- git-diff-tree -p broken^ | git-apply --index
- git commit --reedit=broken^
-
- # Then "top of broken" (aka c):
- git-diff-tree -p broken | git-apply --index
- git commit --reedit=broken
-
-and you've now re-applied (and possibly edited the comments) the two
-commits b/c, and commit "a" is basically gone (it still exists in the
-"broken" branch, of course).
-
-Finally, check out the end result again:
-
- # Look at the new commit history
- gitk --all
-
-to see that everything looks sensible.
-
-And then, you can just remove the broken branch if you decide you really
-don't want it:
-
- # remove 'broken' branch
- git branch -d broken
-
- # Prune old objects if you're really really sure
- git prune
-
-And yeah, I'm sure there are other ways of doing this. And as usual, the
-above is totally untested, and I just wrote it down in this email, so if
-I've done something wrong, you'll have to figure it out on your own ;)
-
- Linus
--
-To unsubscribe from this list: send the line "unsubscribe git" in
-the body of a message to majordomo@vger.kernel.org
-More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 388d492..f18d33e 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -116,6 +116,7 @@ The placeholders are:
- '%cr': committer date, relative
- '%ct': committer date, UNIX timestamp
- '%ci': committer date, ISO 8601 format
+- '%d': ref names, like the --decorate option of linkgit:git-log[1]
- '%e': encoding
- '%s': subject
- '%b': body
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 735cf07..0ce916a 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -409,6 +409,48 @@ Note that without '\--full-history', this still simplifies merges: if
one of the parents is TREESAME, we follow only that one, so the other
sides of the merge are never walked.
+Finally, there is a fourth simplification mode available:
+
+--simplify-merges::
+
+ First, build a history graph in the same way that
+ '\--full-history' with parent rewriting does (see above).
++
+Then simplify each commit `C` to its replacement `C'` in the final
+history according to the following rules:
++
+--
+* Set `C'` to `C`.
++
+* Replace each parent `P` of `C'` with its simplification `P'`. In
+ the process, drop parents that are ancestors of other parents, and
+ remove duplicates.
++
+* If after this parent rewriting, `C'` is a root or merge commit (has
+ zero or >1 parents), a boundary commit, or !TREESAME, it remains.
+ Otherwise, it is replaced with its only parent.
+--
++
+The effect of this is best shown by way of comparing to
+'\--full-history' with parent rewriting. The example turns into:
++
+-----------------------------------------------------------------------
+ .-A---M---N---O
+ / / /
+ I B D
+ \ / /
+ `---------'
+-----------------------------------------------------------------------
++
+Note the major differences in `N` and `P` over '\--full-history':
++
+--
+* `N`'s parent list had `I` removed, because it is an ancestor of the
+ other parent `M`. Still, `N` remained because it is !TREESAME.
++
+* `P`'s parent list similarly had `I` removed. `P` was then
+ removed completely, because it had one parent and is TREESAME.
+--
ifdef::git-rev-list[]
Bisection Helpers
diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt
index 75aa5d4..82e9e83 100644
--- a/Documentation/technical/api-run-command.txt
+++ b/Documentation/technical/api-run-command.txt
@@ -30,7 +30,7 @@ Functions
start_command() followed by finish_command(). Takes a pointer
to a `struct child_process` that specifies the details.
-`run_command_v_opt`, `run_command_v_opt_cd`, `run_command_v_opt_cd_env`::
+`run_command_v_opt`, `run_command_v_opt_cd_env`::
Convenience functions that encapsulate a sequence of
start_command() followed by finish_command(). The argument argv
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index 08d1310..645d752 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -4356,7 +4356,9 @@ $ git remote show example # get details
* remote example
URL: git://example.com/project.git
Tracked remote branches
- master next ...
+ master
+ next
+ ...
$ git fetch example # update branches from example
$ git branch -r # list all remote branches
-----------------------------------------------
diff --git a/INSTALL b/INSTALL
index 2bae53f..a4fd862 100644
--- a/INSTALL
+++ b/INSTALL
@@ -6,7 +6,7 @@ will install the git programs in your own ~/bin/ directory. If you want
to do a global install, you can do
$ make prefix=/usr all doc info ;# as yourself
- # make prefix=/usr install install-doc install-info ;# as root
+ # make prefix=/usr install install-doc install-html install-info ;# as root
(or prefix=/usr/local, of course). Just like any program suite
that uses $prefix, the built results have some paths encoded,
@@ -19,7 +19,7 @@ set up install paths (via config.mak.autogen), so you can write instead
$ make configure ;# as yourself
$ ./configure --prefix=/usr ;# as yourself
$ make all doc ;# as yourself
- # make install install-doc ;# as root
+ # make install install-doc install-html;# as root
Issues of note:
@@ -89,13 +89,22 @@ Issues of note:
inclined to install the tools, the default build target
("make all") does _not_ build them.
+ "make doc" builds documentation in man and html formats; there are
+ also "make man", "make html" and "make info". Note that "make html"
+ requires asciidoc, but not xmlto. "make man" (and thus make doc)
+ requires both.
+
+ "make install-doc" installs documentation in man format only; there
+ are also "make install-man", "make install-html" and "make
+ install-info".
+
Building and installing the info file additionally requires
makeinfo and docbook2X. Version 0.8.3 is known to work.
The documentation is written for AsciiDoc 7, but "make
ASCIIDOC8=YesPlease doc" will let you format with AsciiDoc 8.
- Alternatively, pre-formatted documentation are available in
+ Alternatively, pre-formatted documentation is available in
"html" and "man" branches of the git repository itself. For
example, you could:
@@ -117,6 +126,12 @@ Issues of note:
http://www.kernel.org/pub/software/scm/git/docs/
+ There are also "make quick-install-doc" and "make quick-install-html"
+ which install preformatted man pages and html documentation.
+ This does not require asciidoc/xmlto, but it only works from within
+ a cloned checkout of git.git with these two extra branches, and will
+ not work for the maintainer for obvious chicken-and-egg reasons.
+
It has been reported that docbook-xsl version 1.72 and 1.73 are
buggy; 1.72 misformats manual pages for callouts, and 1.73 needs
the patch in contrib/patches/docbook-xsl-manpages-charmap.patch
diff --git a/Makefile b/Makefile
index 0d40f0e..d6f3695 100644
--- a/Makefile
+++ b/Makefile
@@ -124,6 +124,9 @@ all::
# Define USE_STDEV below if you want git to care about the underlying device
# change being considered an inode change from the update-index perspective.
#
+# Define NO_ST_BLOCKS_IN_STRUCT_STAT if your platform does not have st_blocks
+# field that counts the on-disk footprint in 512-byte blocks.
+#
# Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8
#
# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72.
@@ -291,8 +294,8 @@ PROGRAMS += git-mktag$X
PROGRAMS += git-mktree$X
PROGRAMS += git-pack-redundant$X
PROGRAMS += git-patch-id$X
-PROGRAMS += git-receive-pack$X
PROGRAMS += git-send-pack$X
+PROGRAMS += git-shell$X
PROGRAMS += git-show-index$X
PROGRAMS += git-unpack-file$X
PROGRAMS += git-update-server-info$X
@@ -343,6 +346,7 @@ LIB_H += cache.h
LIB_H += cache-tree.h
LIB_H += commit.h
LIB_H += compat/mingw.h
+LIB_H += compat/cygwin.h
LIB_H += csum-file.h
LIB_H += decorate.h
LIB_H += delta.h
@@ -354,6 +358,8 @@ LIB_H += git-compat-util.h
LIB_H += graph.h
LIB_H += grep.h
LIB_H += hash.h
+LIB_H += help.h
+LIB_H += levenshtein.h
LIB_H += list-objects.h
LIB_H += ll-merge.h
LIB_H += log-tree.h
@@ -383,6 +389,7 @@ LIB_H += transport.h
LIB_H += tree.h
LIB_H += tree-walk.h
LIB_H += unpack-trees.h
+LIB_H += userdiff.h
LIB_H += utf8.h
LIB_H += wt-status.h
@@ -430,6 +437,7 @@ LIB_OBJS += hash.o
LIB_OBJS += help.o
LIB_OBJS += ident.o
LIB_OBJS += interpolate.o
+LIB_OBJS += levenshtein.o
LIB_OBJS += list-objects.o
LIB_OBJS += ll-merge.o
LIB_OBJS += lockfile.o
@@ -437,6 +445,7 @@ LIB_OBJS += log-tree.o
LIB_OBJS += mailmap.o
LIB_OBJS += match-trees.o
LIB_OBJS += merge-file.o
+LIB_OBJS += merge-recursive.o
LIB_OBJS += name-hash.o
LIB_OBJS += object.o
LIB_OBJS += pack-check.o
@@ -477,6 +486,7 @@ LIB_OBJS += tree-diff.o
LIB_OBJS += tree.o
LIB_OBJS += tree-walk.o
LIB_OBJS += unpack-trees.o
+LIB_OBJS += userdiff.o
LIB_OBJS += usage.o
LIB_OBJS += utf8.o
LIB_OBJS += walker.o
@@ -518,6 +528,7 @@ BUILTIN_OBJS += builtin-for-each-ref.o
BUILTIN_OBJS += builtin-fsck.o
BUILTIN_OBJS += builtin-gc.o
BUILTIN_OBJS += builtin-grep.o
+BUILTIN_OBJS += builtin-help.o
BUILTIN_OBJS += builtin-init-db.o
BUILTIN_OBJS += builtin-log.o
BUILTIN_OBJS += builtin-ls-files.o
@@ -538,6 +549,7 @@ BUILTIN_OBJS += builtin-prune-packed.o
BUILTIN_OBJS += builtin-prune.o
BUILTIN_OBJS += builtin-push.o
BUILTIN_OBJS += builtin-read-tree.o
+BUILTIN_OBJS += builtin-receive-pack.o
BUILTIN_OBJS += builtin-reflog.o
BUILTIN_OBJS += builtin-remote.o
BUILTIN_OBJS += builtin-rerere.o
@@ -575,9 +587,11 @@ EXTLIBS =
ifeq ($(uname_S),Linux)
NO_STRLCPY = YesPlease
+ THREADED_DELTA_SEARCH = YesPlease
endif
ifeq ($(uname_S),GNU/kFreeBSD)
NO_STRLCPY = YesPlease
+ THREADED_DELTA_SEARCH = YesPlease
endif
ifeq ($(uname_S),UnixWare)
CC = cc
@@ -677,6 +691,7 @@ ifeq ($(uname_S),FreeBSD)
BASIC_CFLAGS += -I/usr/local/include
BASIC_LDFLAGS += -L/usr/local/lib
DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
+ THREADED_DELTA_SEARCH = YesPlease
COMPAT_CFLAGS += -Icompat/regex
COMPAT_OBJS += compat/regex/regex.o
endif
@@ -686,14 +701,15 @@ ifeq ($(uname_S),OpenBSD)
NEEDS_LIBICONV = YesPlease
BASIC_CFLAGS += -I/usr/local/include
BASIC_LDFLAGS += -L/usr/local/lib
+ THREADED_DELTA_SEARCH = YesPlease
endif
ifeq ($(uname_S),NetBSD)
ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
NEEDS_LIBICONV = YesPlease
endif
BASIC_CFLAGS += -I/usr/pkg/include
- BASIC_LDFLAGS += -L/usr/pkg/lib
- ALL_LDFLAGS += -Wl,-rpath,/usr/pkg/lib
+ BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib
+ THREADED_DELTA_SEARCH = YesPlease
endif
ifeq ($(uname_S),AIX)
NO_STRCASESTR=YesPlease
@@ -735,6 +751,9 @@ ifeq ($(uname_S),HP-UX)
NO_SYS_SELECT_H = YesPlease
SNPRINTF_RETURNS_BOGUS = YesPlease
endif
+ifneq (,$(findstring CYGWIN,$(uname_S)))
+ COMPAT_OBJS += compat/cygwin.o
+endif
ifneq (,$(findstring MINGW,$(uname_S)))
NO_MMAP = YesPlease
NO_PREAD = YesPlease
@@ -756,6 +775,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
NO_SVN_TESTS = YesPlease
NO_PERL_MAKEMAKER = YesPlease
NO_POSIX_ONLY_PROGRAMS = YesPlease
+ NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/regex -Icompat/fnmatch
COMPAT_CFLAGS += -DSNPRINTF_SIZE_CORR=1
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
@@ -788,12 +808,14 @@ ifeq ($(uname_S),Darwin)
endif
endif
-ifdef NO_R_TO_GCC_LINKER
- # Some gcc does not accept and pass -R to the linker to specify
- # the runtime dynamic library path.
- CC_LD_DYNPATH = -Wl,-rpath=
-else
- CC_LD_DYNPATH = -R
+ifndef CC_LD_DYNPATH
+ ifdef NO_R_TO_GCC_LINKER
+ # Some gcc does not accept and pass -R to the linker to specify
+ # the runtime dynamic library path.
+ CC_LD_DYNPATH = -Wl,-rpath,
+ else
+ CC_LD_DYNPATH = -R
+ endif
endif
ifdef NO_CURL
@@ -829,7 +851,6 @@ EXTLIBS += -lz
ifndef NO_POSIX_ONLY_PROGRAMS
PROGRAMS += git-daemon$X
PROGRAMS += git-imap-send$X
- PROGRAMS += git-shell$X
endif
ifndef NO_OPENSSL
OPENSSL_LIBSSL = -lssl
@@ -870,6 +891,9 @@ endif
ifdef NO_D_INO_IN_DIRENT
BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT
endif
+ifdef NO_ST_BLOCKS_IN_STRUCT_STAT
+ BASIC_CFLAGS += -DNO_ST_BLOCKS_IN_STRUCT_STAT
+endif
ifdef NO_C99_FORMAT
BASIC_CFLAGS += -DNO_C99_FORMAT
endif
@@ -1098,7 +1122,7 @@ git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
$(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
-help.o: help.c common-cmds.h GIT-CFLAGS
+builtin-help.o: builtin-help.c common-cmds.h GIT-CFLAGS
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
'-DGIT_HTML_PATH="$(htmldir_SQ)"' \
'-DGIT_MAN_PATH="$(mandir_SQ)"' \
@@ -1225,7 +1249,9 @@ endif
git-%$X: %.o $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
-git-imap-send$X: imap-send.o $(LIB_FILE)
+git-imap-send$X: imap-send.o $(GITLIBS)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+ $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL)
http.o http-walker.o http-push.o transport.o: http.h
@@ -1252,6 +1278,12 @@ $(XDIFF_LIB): $(XDIFF_OBJS)
doc:
$(MAKE) -C Documentation all
+man:
+ $(MAKE) -C Documentation man
+
+html:
+ $(MAKE) -C Documentation html
+
info:
$(MAKE) -C Documentation info
@@ -1355,7 +1387,7 @@ install: all
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) git$X git-upload-pack$X git-receive-pack$X git-upload-archive$X '$(DESTDIR_SQ)$(bindir_SQ)'
+ $(INSTALL) git$X git-upload-pack$X git-receive-pack$X git-upload-archive$X git-shell$X git-cvsserver '$(DESTDIR_SQ)$(bindir_SQ)'
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
ifndef NO_TCLTK
@@ -1388,6 +1420,9 @@ install-info:
quick-install-doc:
$(MAKE) -C Documentation quick-install
+quick-install-html:
+ $(MAKE) -C Documentation quick-install-html
+
### Maintainer's dist rules
diff --git a/RelNotes b/RelNotes
index a677737..3d42084 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.6.0.3.txt \ No newline at end of file
+Documentation/RelNotes-1.6.1.txt \ No newline at end of file
diff --git a/abspath.c b/abspath.c
index 0d56124..8194ce1 100644
--- a/abspath.c
+++ b/abspath.c
@@ -1,5 +1,16 @@
#include "cache.h"
+/*
+ * Do not use this for inspecting *tracked* content. When path is a
+ * symlink to a directory, we do not want to say it is a directory when
+ * dealing with tracked content in the working tree.
+ */
+int is_directory(const char *path)
+{
+ struct stat st;
+ return (!stat(path, &st) && S_ISDIR(st.st_mode));
+}
+
/* We allow "recursive" symbolic links. Only within reason, though. */
#define MAXDEPTH 5
@@ -17,7 +28,7 @@ const char *make_absolute_path(const char *path)
die ("Too long path: %.*s", 60, path);
while (depth--) {
- if (stat(buf, &st) || !S_ISDIR(st.st_mode)) {
+ if (!is_directory(buf)) {
char *last_slash = strrchr(buf, '/');
if (last_slash) {
*last_slash = '\0';
diff --git a/archive-tar.c b/archive-tar.c
index 1302961..ba890eb 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -124,11 +124,10 @@ static int write_tar_entry(struct archiver_args *args,
unsigned int mode, void *buffer, unsigned long size)
{
struct ustar_header header;
- struct strbuf ext_header;
+ struct strbuf ext_header = STRBUF_INIT;
int err = 0;
memset(&header, 0, sizeof(header));
- strbuf_init(&ext_header, 0);
if (!sha1) {
*header.typeflag = TYPEFLAG_GLOBAL_HEADER;
@@ -211,10 +210,9 @@ static int write_tar_entry(struct archiver_args *args,
static int write_global_extended_header(struct archiver_args *args)
{
const unsigned char *sha1 = args->commit_sha1;
- struct strbuf ext_header;
+ struct strbuf ext_header = STRBUF_INIT;
int err;
- strbuf_init(&ext_header, 0);
strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40);
err = write_tar_entry(args, NULL, NULL, 0, 0, ext_header.buf,
ext_header.len);
diff --git a/archive.c b/archive.c
index 45d242b..9ac455d 100644
--- a/archive.c
+++ b/archive.c
@@ -15,7 +15,7 @@ static char const * const archive_usage[] = {
#define USES_ZLIB_COMPRESSION 1
-const struct archiver {
+static const struct archiver {
const char *name;
write_archive_fn_t write_archive;
unsigned int flags;
@@ -29,11 +29,10 @@ static void format_subst(const struct commit *commit,
struct strbuf *buf)
{
char *to_free = NULL;
- struct strbuf fmt;
+ struct strbuf fmt = STRBUF_INIT;
if (src == buf->buf)
to_free = strbuf_detach(buf, NULL);
- strbuf_init(&fmt, 0);
for (;;) {
const char *b, *c;
@@ -65,10 +64,9 @@ static void *sha1_file_to_archive(const char *path, const unsigned char *sha1,
buffer = read_sha1_file(sha1, type, sizep);
if (buffer && S_ISREG(mode)) {
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
size_t size = 0;
- strbuf_init(&buf, 0);
strbuf_attach(&buf, buffer, *sizep, *sizep + 1);
convert_to_working_tree(path, buf.buf, buf.len, &buf);
if (commit)
diff --git a/arm/sha1.c b/arm/sha1.c
index 9e3ae03..c61ad4a 100644
--- a/arm/sha1.c
+++ b/arm/sha1.c
@@ -8,9 +8,9 @@
#include <string.h>
#include "sha1.h"
-extern void sha_transform(uint32_t *hash, const unsigned char *data, uint32_t *W);
+extern void arm_sha_transform(uint32_t *hash, const unsigned char *data, uint32_t *W);
-void SHA1_Init(SHA_CTX *c)
+void arm_SHA1_Init(arm_SHA_CTX *c)
{
c->len = 0;
c->hash[0] = 0x67452301;
@@ -20,7 +20,7 @@ void SHA1_Init(SHA_CTX *c)
c->hash[4] = 0xc3d2e1f0;
}
-void SHA1_Update(SHA_CTX *c, const void *p, unsigned long n)
+void arm_SHA1_Update(arm_SHA_CTX *c, const void *p, unsigned long n)
{
uint32_t workspace[80];
unsigned int partial;
@@ -32,12 +32,12 @@ void SHA1_Update(SHA_CTX *c, const void *p, unsigned long n)
if (partial) {
done = 64 - partial;
memcpy(c->buffer + partial, p, done);
- sha_transform(c->hash, c->buffer, workspace);
+ arm_sha_transform(c->hash, c->buffer, workspace);
partial = 0;
} else
done = 0;
while (n >= done + 64) {
- sha_transform(c->hash, p + done, workspace);
+ arm_sha_transform(c->hash, p + done, workspace);
done += 64;
}
} else
@@ -46,7 +46,7 @@ void SHA1_Update(SHA_CTX *c, const void *p, unsigned long n)
memcpy(c->buffer + partial, p + done, n - done);
}
-void SHA1_Final(unsigned char *hash, SHA_CTX *c)
+void arm_SHA1_Final(unsigned char *hash, arm_SHA_CTX *c)
{
uint64_t bitlen;
uint32_t bitlen_hi, bitlen_lo;
@@ -57,7 +57,7 @@ void SHA1_Final(unsigned char *hash, SHA_CTX *c)
bitlen = c->len << 3;
offset = c->len & 0x3f;
padlen = ((offset < 56) ? 56 : (64 + 56)) - offset;
- SHA1_Update(c, padding, padlen);
+ arm_SHA1_Update(c, padding, padlen);
bitlen_hi = bitlen >> 32;
bitlen_lo = bitlen & 0xffffffff;
@@ -69,7 +69,7 @@ void SHA1_Final(unsigned char *hash, SHA_CTX *c)
bits[5] = bitlen_lo >> 16;
bits[6] = bitlen_lo >> 8;
bits[7] = bitlen_lo;
- SHA1_Update(c, bits, 8);
+ arm_SHA1_Update(c, bits, 8);
for (i = 0; i < 5; i++) {
uint32_t v = c->hash[i];
diff --git a/arm/sha1.h b/arm/sha1.h
index 3952646..b61b618 100644
--- a/arm/sha1.h
+++ b/arm/sha1.h
@@ -7,12 +7,17 @@
#include <stdint.h>
-typedef struct sha_context {
+typedef struct {
uint64_t len;
uint32_t hash[5];
unsigned char buffer[64];
-} SHA_CTX;
+} arm_SHA_CTX;
-void SHA1_Init(SHA_CTX *c);
-void SHA1_Update(SHA_CTX *c, const void *p, unsigned long n);
-void SHA1_Final(unsigned char *hash, SHA_CTX *c);
+void arm_SHA1_Init(arm_SHA_CTX *c);
+void arm_SHA1_Update(arm_SHA_CTX *c, const void *p, unsigned long n);
+void arm_SHA1_Final(unsigned char *hash, arm_SHA_CTX *c);
+
+#define git_SHA_CTX arm_SHA_CTX
+#define git_SHA1_Init arm_SHA1_Init
+#define git_SHA1_Update arm_SHA1_Update
+#define git_SHA1_Final arm_SHA1_Final
diff --git a/arm/sha1_arm.S b/arm/sha1_arm.S
index 8c1cb99..41e9263 100644
--- a/arm/sha1_arm.S
+++ b/arm/sha1_arm.S
@@ -10,7 +10,7 @@
*/
.text
- .globl sha_transform
+ .globl arm_sha_transform
/*
* void sha_transform(uint32_t *hash, const unsigned char *data, uint32_t *W);
@@ -18,7 +18,7 @@
* note: the "data" pointer may be unaligned.
*/
-sha_transform:
+arm_sha_transform:
stmfd sp!, {r4 - r8, lr}
diff --git a/branch.c b/branch.c
index b1e59f2..b1ac837 100644
--- a/branch.c
+++ b/branch.c
@@ -129,7 +129,9 @@ void create_branch(const char *head,
die("Cannot setup tracking information; starting point is not a branch.");
break;
case 1:
- /* Unique completion -- good */
+ /* Unique completion -- good, only if it is a real ref */
+ if (track == BRANCH_TRACK_EXPLICIT && !strcmp(real_ref, "HEAD"))
+ die("Cannot setup tracking information; starting point is not a branch.");
break;
default:
die("Ambiguous object name: '%s'.", start_name);
@@ -168,5 +170,6 @@ void remove_branch_state(void)
unlink(git_path("MERGE_HEAD"));
unlink(git_path("MERGE_RR"));
unlink(git_path("MERGE_MSG"));
+ unlink(git_path("MERGE_MODE"));
unlink(git_path("SQUASH_MSG"));
}
diff --git a/builtin-add.c b/builtin-add.c
index fc3f96e..ea4e771 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -8,10 +8,6 @@
#include "dir.h"
#include "exec_cmd.h"
#include "cache-tree.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "commit.h"
-#include "revision.h"
#include "run-command.h"
#include "parse-options.h"
@@ -22,6 +18,27 @@ static const char * const builtin_add_usage[] = {
static int patch_interactive = 0, add_interactive = 0;
static int take_worktree_changes;
+static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
+{
+ int num_unmatched = 0, i;
+
+ /*
+ * Since we are walking the index as if we are warlking the directory,
+ * we have to mark the matched pathspec as seen; otherwise we will
+ * mistakenly think that the user gave a pathspec that did not match
+ * anything.
+ */
+ for (i = 0; i < specs; i++)
+ if (!seen[i])
+ num_unmatched++;
+ if (!num_unmatched)
+ return;
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
+ }
+}
+
static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
{
char *seen;
@@ -41,6 +58,7 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p
*dst++ = entry;
}
dir->nr = dst - dir->entries;
+ fill_pathspec_matches(pathspec, seen, specs);
for (i = 0; i < specs; i++) {
if (!seen[i] && !file_exists(pathspec[i]))
@@ -79,59 +97,6 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec,
prune_directory(dir, pathspec, baselen);
}
-struct update_callback_data
-{
- int flags;
- int add_errors;
-};
-
-static void update_callback(struct diff_queue_struct *q,
- struct diff_options *opt, void *cbdata)
-{
- int i;
- struct update_callback_data *data = cbdata;
-
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- const char *path = p->one->path;
- switch (p->status) {
- default:
- die("unexpected diff status %c", p->status);
- case DIFF_STATUS_UNMERGED:
- case DIFF_STATUS_MODIFIED:
- case DIFF_STATUS_TYPE_CHANGED:
- if (add_file_to_cache(path, data->flags)) {
- if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
- die("updating files failed");
- data->add_errors++;
- }
- break;
- case DIFF_STATUS_DELETED:
- if (!(data->flags & ADD_CACHE_PRETEND))
- remove_file_from_cache(path);
- if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
- printf("remove '%s'\n", path);
- break;
- }
- }
-}
-
-int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
-{
- struct update_callback_data data;
- struct rev_info rev;
- init_revisions(&rev, prefix);
- setup_revisions(0, NULL, &rev, NULL);
- rev.prune_data = pathspec;
- rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
- rev.diffopt.format_callback = update_callback;
- data.flags = flags;
- data.add_errors = 0;
- rev.diffopt.format_callback_data = &data;
- run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
- return !!data.add_errors;
-}
-
static void refresh(int verbose, const char **pathspec)
{
char *seen;
@@ -153,6 +118,16 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p
{
const char **pathspec = get_pathspec(prefix, argv);
+ if (pathspec) {
+ const char **p;
+ for (p = pathspec; *p; p++) {
+ if (has_symlink_leading_path(strlen(*p), *p)) {
+ int len = prefix ? strlen(prefix) : 0;
+ die("'%s' is beyond a symbolic link", *p + len);
+ }
+ }
+ }
+
return pathspec;
}
@@ -191,7 +166,7 @@ static const char ignore_error[] =
"The following paths are ignored by one of your .gitignore files:\n";
static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
-static int ignore_add_errors, addremove;
+static int ignore_add_errors, addremove, intent_to_add;
static struct option builtin_add_options[] = {
OPT__DRY_RUN(&show_only),
@@ -201,6 +176,7 @@ static struct option builtin_add_options[] = {
OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"),
OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"),
OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"),
+ OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"),
OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"),
OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"),
@@ -258,7 +234,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (addremove && take_worktree_changes)
die("-A and -u are mutually incompatible");
- if (addremove && !argc) {
+ if ((addremove || take_worktree_changes) && !argc) {
static const char *here[2] = { ".", NULL };
argc = 1;
argv = here;
@@ -271,33 +247,31 @@ int cmd_add(int argc, const char **argv, const char *prefix)
flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
(show_only ? ADD_CACHE_PRETEND : 0) |
- (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0));
+ (intent_to_add ? ADD_CACHE_INTENT : 0) |
+ (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
+ (!(addremove || take_worktree_changes)
+ ? ADD_CACHE_IGNORE_REMOVAL : 0));
if (require_pathspec && argc == 0) {
fprintf(stderr, "Nothing specified, nothing added.\n");
fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
return 0;
}
- pathspec = get_pathspec(prefix, argv);
-
- /*
- * If we are adding new files, we need to scan the working
- * tree to find the ones that match pathspecs; this needs
- * to be done before we read the index.
- */
- if (add_new_files)
- fill_directory(&dir, pathspec, ignored_too);
+ pathspec = validate_pathspec(argc, argv, prefix);
if (read_cache() < 0)
die("index file corrupt");
+ if (add_new_files)
+ /* This picks up the paths that are not tracked */
+ fill_directory(&dir, pathspec, ignored_too);
+
if (refresh_only) {
refresh(verbose, pathspec);
goto finish;
}
- if (take_worktree_changes || addremove)
- exit_status |= add_files_to_cache(prefix, pathspec, flags);
+ exit_status |= add_files_to_cache(prefix, pathspec, flags);
if (add_new_files)
exit_status |= add_files(&dir, flags);
diff --git a/builtin-apply.c b/builtin-apply.c
index 50b623e..4c4d1e1 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -321,13 +321,12 @@ static char *find_name(const char *line, char *def, int p_value, int terminate)
const char *start = line;
if (*line == '"') {
- struct strbuf name;
+ struct strbuf name = STRBUF_INIT;
/*
* Proposed "new-style" GNU patch/diff format; see
* http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
*/
- strbuf_init(&name, 0);
if (!unquote_c_style(&name, line, NULL)) {
char *cp;
@@ -675,11 +674,8 @@ static char *git_header_name(char *line, int llen)
if (*line == '"') {
const char *cp;
- struct strbuf first;
- struct strbuf sp;
-
- strbuf_init(&first, 0);
- strbuf_init(&sp, 0);
+ struct strbuf first = STRBUF_INIT;
+ struct strbuf sp = STRBUF_INIT;
if (unquote_c_style(&first, line, &second))
goto free_and_fail1;
@@ -741,10 +737,9 @@ static char *git_header_name(char *line, int llen)
*/
for (second = name; second < line + llen; second++) {
if (*second == '"') {
- struct strbuf sp;
+ struct strbuf sp = STRBUF_INIT;
const char *np;
- strbuf_init(&sp, 0);
if (unquote_c_style(&sp, second, NULL))
goto free_and_fail2;
@@ -1515,11 +1510,10 @@ static const char minuses[]=
static void show_stats(struct patch *patch)
{
- struct strbuf qname;
+ struct strbuf qname = STRBUF_INIT;
char *cp = patch->new_name ? patch->new_name : patch->old_name;
int max, add, del;
- strbuf_init(&qname, 0);
quote_c_style(cp, &qname, NULL, 0);
/*
@@ -2299,14 +2293,12 @@ static void add_to_fn_table(struct patch *patch)
static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
{
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
struct image image;
size_t len;
char *img;
struct patch *tpatch;
- strbuf_init(&buf, 0);
-
if (!(patch->is_copy || patch->is_rename) &&
((tpatch = in_fn_table(patch->old_name)) != NULL)) {
if (tpatch == (struct patch *) -1) {
@@ -2786,7 +2778,7 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
{
int fd;
- struct strbuf nbuf;
+ struct strbuf nbuf = STRBUF_INIT;
if (S_ISGITLINK(mode)) {
struct stat st;
@@ -2805,7 +2797,6 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf,
if (fd < 0)
return -1;
- strbuf_init(&nbuf, 0);
if (convert_to_working_tree(path, buf, size, &nbuf)) {
size = nbuf.len;
buf = nbuf.buf;
@@ -2996,29 +2987,45 @@ static int write_out_results(struct patch *list, int skipped_patch)
static struct lock_file lock_file;
-static struct excludes {
- struct excludes *next;
- const char *path;
-} *excludes;
+static struct string_list limit_by_name;
+static int has_include;
+static void add_name_limit(const char *name, int exclude)
+{
+ struct string_list_item *it;
+
+ it = string_list_append(name, &limit_by_name);
+ it->util = exclude ? NULL : (void *) 1;
+}
static int use_patch(struct patch *p)
{
const char *pathname = p->new_name ? p->new_name : p->old_name;
- struct excludes *x = excludes;
- while (x) {
- if (fnmatch(x->path, pathname, 0) == 0)
- return 0;
- x = x->next;
- }
+ int i;
+
+ /* Paths outside are not touched regardless of "--include" */
if (0 < prefix_length) {
int pathlen = strlen(pathname);
if (pathlen <= prefix_length ||
memcmp(prefix, pathname, prefix_length))
return 0;
}
- return 1;
+
+ /* See if it matches any of exclude/include rule */
+ for (i = 0; i < limit_by_name.nr; i++) {
+ struct string_list_item *it = &limit_by_name.items[i];
+ if (!fnmatch(it->string, pathname, 0))
+ return (it->util != NULL);
+ }
+
+ /*
+ * If we had any include, a path that does not match any rule is
+ * not used. Otherwise, we saw bunch of exclude rules (or none)
+ * and such a path is used.
+ */
+ return !has_include;
}
+
static void prefix_one(char **name)
{
char *old_name = *name;
@@ -3051,13 +3058,12 @@ static void prefix_patches(struct patch *p)
static int apply_patch(int fd, const char *filename, int options)
{
size_t offset;
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
struct patch *list = NULL, **listp = &list;
int skipped_patch = 0;
/* FIXME - memory leak when using multiple patch files as inputs */
memset(&fn_table, 0, sizeof(struct string_list));
- strbuf_init(&buf, 0);
patch_input_file = filename;
read_patch_file(&buf, fd);
offset = 0;
@@ -3159,10 +3165,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
continue;
}
if (!prefixcmp(arg, "--exclude=")) {
- struct excludes *x = xmalloc(sizeof(*x));
- x->path = arg + 10;
- x->next = excludes;
- excludes = x;
+ add_name_limit(arg + 10, 1);
+ continue;
+ }
+ if (!prefixcmp(arg, "--include=")) {
+ add_name_limit(arg + 10, 0);
+ has_include = 1;
continue;
}
if (!prefixcmp(arg, "-p")) {
diff --git a/builtin-blame.c b/builtin-blame.c
index 101c416..48cc0c1 100644
--- a/builtin-blame.c
+++ b/builtin-blame.c
@@ -464,7 +464,6 @@ struct patch {
};
struct blame_diff_state {
- struct xdiff_emit_state xm;
struct patch *ret;
unsigned hunk_post_context;
unsigned hunk_in_pre_context : 1;
@@ -527,15 +526,12 @@ static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o,
xpp.flags = xdl_opts;
memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = context;
- ecb.outf = xdiff_outf;
- ecb.priv = &state;
memset(&state, 0, sizeof(state));
- state.xm.consume = process_u_diff;
state.ret = xmalloc(sizeof(struct patch));
state.ret->chunks = NULL;
state.ret->num = 0;
- xdi_diff(file_p, file_o, &xpp, &xecfg, &ecb);
+ xdi_diff_outf(file_p, file_o, process_u_diff, &state, &xpp, &xecfg, &ecb);
if (state.ret->num) {
struct chunk *chunk;
@@ -2066,7 +2062,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
struct commit *commit;
struct origin *origin;
unsigned char head_sha1[20];
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
const char *ident;
time_t now;
int size, len;
@@ -2086,7 +2082,6 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
origin = make_origin(commit, path);
- strbuf_init(&buf, 0);
if (!contents_from || strcmp("-", contents_from)) {
struct stat st;
const char *read_from;
diff --git a/builtin-branch.c b/builtin-branch.c
index b1a2ad7..8d634ff 100644
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@ -334,11 +334,10 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
}
if (verbose) {
- struct strbuf subject;
+ struct strbuf subject = STRBUF_INIT;
const char *sub = " **** invalid ref ****";
char stat[128];
- strbuf_init(&subject, 0);
stat[0] = '\0';
commit = item->commit;
diff --git a/builtin-cat-file.c b/builtin-cat-file.c
index 3fba6b9..30d00a6 100644
--- a/builtin-cat-file.c
+++ b/builtin-cat-file.c
@@ -189,9 +189,8 @@ static int batch_one_object(const char *obj_name, int print_contents)
static int batch_objects(int print_contents)
{
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
- strbuf_init(&buf, 0);
while (strbuf_getline(&buf, stdin, '\n') != EOF) {
int error = batch_one_object(buf.buf, print_contents);
if (error)
diff --git a/builtin-check-attr.c b/builtin-check-attr.c
index cb783fc..4921341 100644
--- a/builtin-check-attr.c
+++ b/builtin-check-attr.c
@@ -2,21 +2,84 @@
#include "cache.h"
#include "attr.h"
#include "quote.h"
+#include "parse-options.h"
-static const char check_attr_usage[] =
-"git check-attr attr... [--] pathname...";
+static int stdin_paths;
+static const char * const check_attr_usage[] = {
+"git check-attr attr... [--] pathname...",
+"git check-attr --stdin attr... < <list-of-paths>",
+NULL
+};
+
+static int null_term_line;
+
+static const struct option check_attr_options[] = {
+ OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"),
+ OPT_BOOLEAN('z', NULL, &null_term_line,
+ "input paths are terminated by a null character"),
+ OPT_END()
+};
+
+static void check_attr(int cnt, struct git_attr_check *check,
+ const char** name, const char *file)
+{
+ int j;
+ if (git_checkattr(file, cnt, check))
+ die("git_checkattr died");
+ for (j = 0; j < cnt; j++) {
+ const char *value = check[j].value;
+
+ if (ATTR_TRUE(value))
+ value = "set";
+ else if (ATTR_FALSE(value))
+ value = "unset";
+ else if (ATTR_UNSET(value))
+ value = "unspecified";
+
+ quote_c_style(file, NULL, stdout, 0);
+ printf(": %s: %s\n", name[j], value);
+ }
+}
+
+static void check_attr_stdin_paths(int cnt, struct git_attr_check *check,
+ const char** name)
+{
+ struct strbuf buf, nbuf;
+ int line_termination = null_term_line ? 0 : '\n';
+
+ strbuf_init(&buf, 0);
+ strbuf_init(&nbuf, 0);
+ while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+ if (line_termination && buf.buf[0] == '"') {
+ strbuf_reset(&nbuf);
+ if (unquote_c_style(&nbuf, buf.buf, NULL))
+ die("line is badly quoted");
+ strbuf_swap(&buf, &nbuf);
+ }
+ check_attr(cnt, check, name, buf.buf);
+ maybe_flush_or_die(stdout, "attribute to stdout");
+ }
+ strbuf_release(&buf);
+ strbuf_release(&nbuf);
+}
int cmd_check_attr(int argc, const char **argv, const char *prefix)
{
struct git_attr_check *check;
int cnt, i, doubledash;
+ const char *errstr = NULL;
+
+ argc = parse_options(argc, argv, check_attr_options, check_attr_usage,
+ PARSE_OPT_KEEP_DASHDASH);
+ if (!argc)
+ usage_with_options(check_attr_usage, check_attr_options);
if (read_cache() < 0) {
die("invalid cache");
}
doubledash = -1;
- for (i = 1; doubledash < 0 && i < argc; i++) {
+ for (i = 0; doubledash < 0 && i < argc; i++) {
if (!strcmp(argv[i], "--"))
doubledash = i;
}
@@ -24,41 +87,37 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
/* If there is no double dash, we handle only one attribute */
if (doubledash < 0) {
cnt = 1;
- doubledash = 1;
+ doubledash = 0;
} else
- cnt = doubledash - 1;
+ cnt = doubledash;
doubledash++;
- if (cnt <= 0 || argc < doubledash)
- usage(check_attr_usage);
+ if (cnt <= 0)
+ errstr = "No attribute specified";
+ else if (stdin_paths && doubledash < argc)
+ errstr = "Can't specify files with --stdin";
+ if (errstr) {
+ error (errstr);
+ usage_with_options(check_attr_usage, check_attr_options);
+ }
+
check = xcalloc(cnt, sizeof(*check));
for (i = 0; i < cnt; i++) {
const char *name;
struct git_attr *a;
- name = argv[i + 1];
+ name = argv[i];
a = git_attr(name, strlen(name));
if (!a)
return error("%s: not a valid attribute name", name);
check[i].attr = a;
}
- for (i = doubledash; i < argc; i++) {
- int j;
- if (git_checkattr(argv[i], cnt, check))
- die("git_checkattr died");
- for (j = 0; j < cnt; j++) {
- const char *value = check[j].value;
-
- if (ATTR_TRUE(value))
- value = "set";
- else if (ATTR_FALSE(value))
- value = "unset";
- else if (ATTR_UNSET(value))
- value = "unspecified";
-
- quote_c_style(argv[i], NULL, stdout, 0);
- printf(": %s: %s\n", argv[j+1], value);
- }
+ if (stdin_paths)
+ check_attr_stdin_paths(cnt, check, argv);
+ else {
+ for (i = doubledash; i < argc; i++)
+ check_attr(cnt, check, argv, argv[i]);
+ maybe_flush_or_die(stdout, "attribute to stdout");
}
return 0;
}
diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c
index 55b7aaf..4ba2702 100644
--- a/builtin-checkout-index.c
+++ b/builtin-checkout-index.c
@@ -268,13 +268,11 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
}
if (read_from_stdin) {
- struct strbuf buf, nbuf;
+ struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
if (all)
die("git checkout-index: don't mix '--all' and '--stdin'");
- strbuf_init(&buf, 0);
- strbuf_init(&nbuf, 0);
while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
const char *p;
if (line_termination && buf.buf[0] == '"') {
diff --git a/builtin-checkout.c b/builtin-checkout.c
index 1deda92..57b94d2 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -13,6 +13,9 @@
#include "diff.h"
#include "revision.h"
#include "remote.h"
+#include "blob.h"
+#include "xdiff-interface.h"
+#include "ll-merge.h"
static const char * const checkout_usage[] = {
"git checkout [options] <branch>",
@@ -20,6 +23,18 @@ static const char * const checkout_usage[] = {
NULL,
};
+struct checkout_opts {
+ int quiet;
+ int merge;
+ int force;
+ int writeout_stage;
+ int writeout_error;
+
+ const char *new_branch;
+ int new_branch_log;
+ enum branch_track track;
+};
+
static int post_checkout_hook(struct commit *old, struct commit *new,
int changed)
{
@@ -84,8 +99,121 @@ static int skip_same_name(struct cache_entry *ce, int pos)
return pos;
}
+static int check_stage(int stage, struct cache_entry *ce, int pos)
+{
+ while (pos < active_nr &&
+ !strcmp(active_cache[pos]->name, ce->name)) {
+ if (ce_stage(active_cache[pos]) == stage)
+ return 0;
+ pos++;
+ }
+ return error("path '%s' does not have %s version",
+ ce->name,
+ (stage == 2) ? "our" : "their");
+}
+
+static int check_all_stages(struct cache_entry *ce, int pos)
+{
+ if (ce_stage(ce) != 1 ||
+ active_nr <= pos + 2 ||
+ strcmp(active_cache[pos+1]->name, ce->name) ||
+ ce_stage(active_cache[pos+1]) != 2 ||
+ strcmp(active_cache[pos+2]->name, ce->name) ||
+ ce_stage(active_cache[pos+2]) != 3)
+ return error("path '%s' does not have all three versions",
+ ce->name);
+ return 0;
+}
+
+static int checkout_stage(int stage, struct cache_entry *ce, int pos,
+ struct checkout *state)
+{
+ while (pos < active_nr &&
+ !strcmp(active_cache[pos]->name, ce->name)) {
+ if (ce_stage(active_cache[pos]) == stage)
+ return checkout_entry(active_cache[pos], state, NULL);
+ pos++;
+ }
+ return error("path '%s' does not have %s version",
+ ce->name,
+ (stage == 2) ? "our" : "their");
+}
+
+/* NEEDSWORK: share with merge-recursive */
+static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
+{
+ unsigned long size;
+ enum object_type type;
+
+ if (!hashcmp(sha1, null_sha1)) {
+ mm->ptr = xstrdup("");
+ mm->size = 0;
+ return;
+ }
+
+ mm->ptr = read_sha1_file(sha1, &type, &size);
+ if (!mm->ptr || type != OBJ_BLOB)
+ die("unable to read blob object %s", sha1_to_hex(sha1));
+ mm->size = size;
+}
+
+static int checkout_merged(int pos, struct checkout *state)
+{
+ struct cache_entry *ce = active_cache[pos];
+ const char *path = ce->name;
+ mmfile_t ancestor, ours, theirs;
+ int status;
+ unsigned char sha1[20];
+ mmbuffer_t result_buf;
+
+ if (ce_stage(ce) != 1 ||
+ active_nr <= pos + 2 ||
+ strcmp(active_cache[pos+1]->name, path) ||
+ ce_stage(active_cache[pos+1]) != 2 ||
+ strcmp(active_cache[pos+2]->name, path) ||
+ ce_stage(active_cache[pos+2]) != 3)
+ return error("path '%s' does not have all 3 versions", path);
+
+ fill_mm(active_cache[pos]->sha1, &ancestor);
+ fill_mm(active_cache[pos+1]->sha1, &ours);
+ fill_mm(active_cache[pos+2]->sha1, &theirs);
+
+ status = ll_merge(&result_buf, path, &ancestor,
+ &ours, "ours", &theirs, "theirs", 1);
+ free(ancestor.ptr);
+ free(ours.ptr);
+ free(theirs.ptr);
+ if (status < 0 || !result_buf.ptr) {
+ free(result_buf.ptr);
+ return error("path '%s': cannot merge", path);
+ }
+
+ /*
+ * NEEDSWORK:
+ * There is absolutely no reason to write this as a blob object
+ * and create a phoney cache entry just to leak. This hack is
+ * primarily to get to the write_entry() machinery that massages
+ * the contents to work-tree format and writes out which only
+ * allows it for a cache entry. The code in write_entry() needs
+ * to be refactored to allow us to feed a <buffer, size, mode>
+ * instead of a cache entry. Such a refactoring would help
+ * merge_recursive as well (it also writes the merge result to the
+ * object database even when it may contain conflicts).
+ */
+ if (write_sha1_file(result_buf.ptr, result_buf.size,
+ blob_type, sha1))
+ die("Unable to add merge result for '%s'", path);
+ ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode),
+ sha1,
+ path, 2, 0);
+ if (!ce)
+ die("make_cache_entry failed for path '%s'", path);
+ status = checkout_entry(ce, state, NULL);
+ return status;
+}
-static int checkout_paths(struct tree *source_tree, const char **pathspec)
+static int checkout_paths(struct tree *source_tree, const char **pathspec,
+ struct checkout_opts *opts)
{
int pos;
struct checkout state;
@@ -94,7 +222,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
int flag;
struct commit *head;
int errs = 0;
-
+ int stage = opts->writeout_stage;
+ int merge = opts->merge;
int newfd;
struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
@@ -122,8 +251,16 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
if (pathspec_match(pathspec, NULL, ce->name, 0)) {
if (!ce_stage(ce))
continue;
- errs = 1;
- error("path '%s' is unmerged", ce->name);
+ if (opts->force) {
+ warning("path '%s' is unmerged", ce->name);
+ } else if (stage) {
+ errs |= check_stage(stage, ce, pos);
+ } else if (opts->merge) {
+ errs |= check_all_stages(ce, pos);
+ } else {
+ errs = 1;
+ error("path '%s' is unmerged", ce->name);
+ }
pos = skip_same_name(ce, pos) - 1;
}
}
@@ -141,6 +278,10 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
errs |= checkout_entry(ce, &state, NULL);
continue;
}
+ if (stage)
+ errs |= checkout_stage(stage, ce, pos, &state);
+ else if (merge)
+ errs |= checkout_merged(pos, &state);
pos = skip_same_name(ce, pos) - 1;
}
}
@@ -169,8 +310,7 @@ static void show_local_changes(struct object *head)
static void describe_detached_head(char *msg, struct commit *commit)
{
- struct strbuf sb;
- strbuf_init(&sb, 0);
+ struct strbuf sb = STRBUF_INIT;
parse_commit(commit);
pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, NULL, NULL, 0, 0);
fprintf(stderr, "%s %s... %s\n", msg,
@@ -178,17 +318,6 @@ static void describe_detached_head(char *msg, struct commit *commit)
strbuf_release(&sb);
}
-struct checkout_opts {
- int quiet;
- int merge;
- int force;
- int writeout_error;
-
- char *new_branch;
- int new_branch_log;
- enum branch_track track;
-};
-
static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
{
struct unpack_trees_options opts;
@@ -230,8 +359,7 @@ struct branch_info {
static void setup_branch_path(struct branch_info *branch)
{
- struct strbuf buf;
- strbuf_init(&buf, 0);
+ struct strbuf buf = STRBUF_INIT;
strbuf_addstr(&buf, "refs/heads/");
strbuf_addstr(&buf, branch->name);
branch->path = strbuf_detach(&buf, NULL);
@@ -293,6 +421,7 @@ static int merge_working_tree(struct checkout_opts *opts,
*/
struct tree *result;
struct tree *work;
+ struct merge_options o;
if (!opts->merge)
return 1;
parse_commit(old->commit);
@@ -311,13 +440,17 @@ static int merge_working_tree(struct checkout_opts *opts,
*/
add_files_to_cache(NULL, NULL, 0);
- work = write_tree_from_memory();
+ init_merge_options(&o);
+ o.verbosity = 0;
+ work = write_tree_from_memory(&o);
ret = reset_tree(new->commit->tree, opts, 1);
if (ret)
return ret;
- merge_trees(new->commit->tree, work, old->commit->tree,
- new->name, "local", &result);
+ o.branch1 = new->name;
+ o.branch2 = "local";
+ merge_trees(&o, new->commit->tree, work,
+ old->commit->tree, &result);
ret = reset_tree(new->commit->tree, opts, 0);
if (ret)
return ret;
@@ -349,7 +482,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
struct branch_info *old,
struct branch_info *new)
{
- struct strbuf msg;
+ struct strbuf msg = STRBUF_INIT;
const char *old_desc;
if (opts->new_branch) {
create_branch(old->name, opts->new_branch, new->name, 0,
@@ -358,7 +491,6 @@ static void update_refs_for_switch(struct checkout_opts *opts,
setup_branch_path(new);
}
- strbuf_init(&msg, 0);
old_desc = old->name;
if (!old_desc)
old_desc = sha1_to_hex(old->commit->object.sha1);
@@ -440,6 +572,11 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
return ret || opts->writeout_error;
}
+static int git_checkout_config(const char *var, const char *value, void *cb)
+{
+ return git_xmerge_config(var, value, cb);
+}
+
int cmd_checkout(int argc, const char **argv, const char *prefix)
{
struct checkout_opts opts;
@@ -447,14 +584,21 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
const char *arg;
struct branch_info new;
struct tree *source_tree = NULL;
+ char *conflict_style = NULL;
struct option options[] = {
OPT__QUIET(&opts.quiet),
OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
OPT_SET_INT('t', "track", &opts.track, "track",
BRANCH_TRACK_EXPLICIT),
+ OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
+ 2),
+ OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
+ 3),
OPT_BOOLEAN('f', NULL, &opts.force, "force"),
- OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
+ OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
+ OPT_STRING(0, "conflict", &conflict_style, "style",
+ "conflict style (merge or diff3)"),
OPT_END(),
};
int has_dash_dash;
@@ -462,15 +606,34 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
memset(&opts, 0, sizeof(opts));
memset(&new, 0, sizeof(new));
- git_config(git_default_config, NULL);
+ git_config(git_checkout_config, NULL);
- opts.track = git_branch_track;
+ opts.track = BRANCH_TRACK_UNSPECIFIED;
argc = parse_options(argc, argv, options, checkout_usage,
PARSE_OPT_KEEP_DASHDASH);
- if (!opts.new_branch && (opts.track != git_branch_track))
- die("git checkout: --track and --no-track require -b");
+ /* --track without -b should DWIM */
+ if (0 < opts.track && !opts.new_branch) {
+ const char *argv0 = argv[0];
+ if (!argc || !strcmp(argv0, "--"))
+ die ("--track needs a branch name");
+ if (!prefixcmp(argv0, "refs/"))
+ argv0 += 5;
+ if (!prefixcmp(argv0, "remotes/"))
+ argv0 += 8;
+ argv0 = strchr(argv0, '/');
+ if (!argv0 || !argv0[1])
+ die ("Missing branch name; try -b");
+ opts.new_branch = argv0 + 1;
+ }
+
+ if (opts.track == BRANCH_TRACK_UNSPECIFIED)
+ opts.track = git_branch_track;
+ if (conflict_style) {
+ opts.merge = 1; /* implied */
+ git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+ }
if (opts.force && opts.merge)
die("git checkout: -f and -m are incompatible");
@@ -554,20 +717,22 @@ no_reference:
die("invalid path specification");
/* Checkout paths */
- if (opts.new_branch || opts.force || opts.merge) {
+ if (opts.new_branch) {
if (argc == 1) {
- die("git checkout: updating paths is incompatible with switching branches/forcing\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
+ die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
} else {
- die("git checkout: updating paths is incompatible with switching branches/forcing");
+ die("git checkout: updating paths is incompatible with switching branches.");
}
}
- return checkout_paths(source_tree, pathspec);
+ if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+ die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
+
+ return checkout_paths(source_tree, pathspec, &opts);
}
if (opts.new_branch) {
- struct strbuf buf;
- strbuf_init(&buf, 0);
+ struct strbuf buf = STRBUF_INIT;
strbuf_addstr(&buf, "refs/heads/");
strbuf_addstr(&buf, opts.new_branch);
if (!get_sha1(buf.buf, rev))
@@ -580,6 +745,8 @@ no_reference:
if (new.name && !new.commit) {
die("Cannot switch branch to a non-commit.");
}
+ if (opts.writeout_stage)
+ die("--ours/--theirs is incompatible with switching branches.");
return switch_branches(&opts, &new);
}
diff --git a/builtin-clean.c b/builtin-clean.c
index 48bf29f..f78c2fb 100644
--- a/builtin-clean.c
+++ b/builtin-clean.c
@@ -31,11 +31,11 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
int i;
int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
int ignored_only = 0, baselen = 0, config_set = 0, errors = 0;
- struct strbuf directory;
+ struct strbuf directory = STRBUF_INIT;
struct dir_struct dir;
const char *path, *base;
static const char **pathspec;
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
const char *qname;
char *seen = NULL;
struct option options[] = {
@@ -58,7 +58,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, options, builtin_clean_usage, 0);
- strbuf_init(&buf, 0);
memset(&dir, 0, sizeof(dir));
if (ignored_only)
dir.show_ignored = 1;
@@ -88,7 +87,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (baselen)
path = base = xmemdupz(*pathspec, baselen);
read_directory(&dir, path, base, baselen, pathspec);
- strbuf_init(&directory, 0);
if (pathspec)
seen = xmalloc(argc > 0 ? argc : 1);
diff --git a/builtin-clone.c b/builtin-clone.c
index 5b40e07..8e1a1d3 100644
--- a/builtin-clone.c
+++ b/builtin-clone.c
@@ -38,9 +38,11 @@ static int option_local, option_no_hardlinks, option_shared;
static char *option_template, *option_reference, *option_depth;
static char *option_origin = NULL;
static char *option_upload_pack = "git-upload-pack";
+static int option_verbose;
static struct option builtin_clone_options[] = {
OPT__QUIET(&option_quiet),
+ OPT__VERBOSE(&option_verbose),
OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
"don't create a checkout"),
OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
@@ -77,7 +79,7 @@ static char *get_repo_path(const char *repo, int *is_bundle)
for (i = 0; i < ARRAY_SIZE(suffix); i++) {
const char *path;
path = mkpath("%s%s", repo, suffix[i]);
- if (!stat(path, &st) && S_ISDIR(st.st_mode)) {
+ if (is_directory(path)) {
*is_bundle = 0;
return xstrdup(make_nonrelative_path(path));
}
@@ -140,13 +142,6 @@ static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
return xstrndup(start, end - start);
}
-static int is_directory(const char *path)
-{
- struct stat buf;
-
- return !stat(path, &buf) && S_ISDIR(buf.st_mode);
-}
-
static void strip_trailing_slashes(char *dir)
{
char *end = dir + strlen(dir);
@@ -271,10 +266,9 @@ pid_t junk_pid;
static void remove_junk(void)
{
- struct strbuf sb;
+ struct strbuf sb = STRBUF_INIT;
if (getpid() != junk_pid)
return;
- strbuf_init(&sb, 0);
if (junk_git_dir) {
strbuf_addstr(&sb, junk_git_dir);
remove_dir_recursively(&sb, 0);
@@ -361,7 +355,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
char *path, *dir;
const struct ref *refs, *head_points_at, *remote_head, *mapped_refs;
char branch_top[256], key[256], value[256];
- struct strbuf reflog_msg;
+ struct strbuf reflog_msg = STRBUF_INIT;
struct transport *transport = NULL;
char *src_ref_prefix = "refs/heads/";
@@ -411,7 +405,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (!stat(dir, &buf))
die("destination directory '%s' already exists.", dir);
- strbuf_init(&reflog_msg, 0);
strbuf_addf(&reflog_msg, "clone: from %s", repo);
if (option_bare)
@@ -513,6 +506,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_quiet)
transport->verbose = -1;
+ else if (option_verbose)
+ transport->progress = 1;
if (option_upload_pack)
transport_set_option(transport, TRANS_OPT_UPLOADPACK,
@@ -533,7 +528,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
create_symref("HEAD", head_points_at->name, NULL);
if (!option_bare) {
- struct strbuf head_ref;
+ struct strbuf head_ref = STRBUF_INIT;
const char *head = head_points_at->name;
if (!prefixcmp(head, "refs/heads/"))
@@ -546,7 +541,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
head_points_at->old_sha1,
NULL, 0, DIE_ON_ERR);
- strbuf_init(&head_ref, 0);
strbuf_addstr(&head_ref, branch_top);
strbuf_addstr(&head_ref, "HEAD");
diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
index 9b84c48..0453425 100644
--- a/builtin-commit-tree.c
+++ b/builtin-commit-tree.c
@@ -46,8 +46,10 @@ static const char commit_utf8_warn[] =
"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)
+ struct commit_list *parents, unsigned char *ret,
+ const char *author)
{
+ int result;
int encoding_is_utf8;
struct strbuf buffer;
@@ -73,7 +75,9 @@ int commit_tree(const char *msg, unsigned char *tree,
}
/* Person/date information */
- strbuf_addf(&buffer, "author %s\n", git_author_info(IDENT_ERROR_ON_NO_NAME));
+ if (!author)
+ author = git_author_info(IDENT_ERROR_ON_NO_NAME);
+ strbuf_addf(&buffer, "author %s\n", author);
strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
if (!encoding_is_utf8)
strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
@@ -86,7 +90,9 @@ int commit_tree(const char *msg, unsigned char *tree,
if (encoding_is_utf8 && !is_utf8(buffer.buf))
fprintf(stderr, commit_utf8_warn);
- return write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
+ result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
+ strbuf_release(&buffer);
+ return result;
}
int cmd_commit_tree(int argc, const char **argv, const char *prefix)
@@ -120,7 +126,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
if (strbuf_read(&buffer, 0, 0) < 0)
die("git commit-tree: read returned %s", strerror(errno));
- if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1)) {
+ if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
printf("%s\n", sha1_to_hex(commit_sha1));
return 0;
}
diff --git a/builtin-commit.c b/builtin-commit.c
index e2a7e48..93ca496 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -320,7 +320,9 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
die("unable to write new_index file");
fd = hold_lock_file_for_update(&false_lock,
- git_path("next-index-%d", getpid()), 1);
+ git_path("next-index-%"PRIuMAX,
+ (uintmax_t) getpid()),
+ LOCK_DIE_ON_ERROR);
create_base_index();
add_remove_files(&partial);
@@ -448,7 +450,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
{
struct stat statbuf;
int commitable, saved_color_setting;
- struct strbuf sb;
+ struct strbuf sb = STRBUF_INIT;
char *buffer;
FILE *fp;
const char *hook_arg1 = NULL;
@@ -458,7 +460,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
if (!no_verify && run_hook(index_file, "pre-commit", NULL))
return 0;
- strbuf_init(&sb, 0);
if (message.len) {
strbuf_addbuf(&sb, &message);
hook_arg1 = "message";
@@ -511,10 +512,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
stripspace(&sb, 0);
if (signoff) {
- struct strbuf sob;
+ struct strbuf sob = STRBUF_INIT;
int i;
- strbuf_init(&sob, 0);
strbuf_addstr(&sob, sign_off_header);
strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
getenv("GIT_COMMITTER_EMAIL")));
@@ -667,20 +667,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
}
/*
- * Find out if the message starting at position 'start' in the strbuf
- * contains only whitespace and Signed-off-by lines.
+ * Find out if the message in the strbuf contains only whitespace and
+ * Signed-off-by lines.
*/
-static int message_is_empty(struct strbuf *sb, int start)
+static int message_is_empty(struct strbuf *sb)
{
- struct strbuf tmpl;
+ struct strbuf tmpl = STRBUF_INIT;
const char *nl;
- int eol, i;
+ int eol, i, start = 0;
if (cleanup_mode == CLEANUP_NONE && sb->len)
return 0;
/* See if the template is just a prefix of the message. */
- strbuf_init(&tmpl, 0);
if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
if (start + tmpl.len <= sb->len &&
@@ -710,6 +709,31 @@ static int message_is_empty(struct strbuf *sb, int start)
return 1;
}
+static const char *find_author_by_nickname(const char *name)
+{
+ struct rev_info revs;
+ struct commit *commit;
+ struct strbuf buf = STRBUF_INIT;
+ const char *av[20];
+ int ac = 0;
+
+ init_revisions(&revs, NULL);
+ strbuf_addf(&buf, "--author=%s", name);
+ av[++ac] = "--all";
+ av[++ac] = "-i";
+ av[++ac] = buf.buf;
+ av[++ac] = NULL;
+ setup_revisions(ac, av, &revs, NULL);
+ prepare_revision_walk(&revs);
+ commit = get_revision(&revs);
+ if (commit) {
+ strbuf_release(&buf);
+ format_commit_message(commit, "%an <%ae>", &buf, DATE_NORMAL);
+ return strbuf_detach(&buf, NULL);
+ }
+ die("No existing author found with '%s'", name);
+}
+
static int parse_and_validate_options(int argc, const char *argv[],
const char * const usage[],
const char *prefix)
@@ -720,6 +744,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
logfile = parse_options_fix_filename(prefix, logfile);
template_file = parse_options_fix_filename(prefix, template_file);
+ if (force_author && !strchr(force_author, '>'))
+ force_author = find_author_by_nickname(force_author);
+
if (logfile || message.len || use_message)
use_editor = 0;
if (edit_flag)
@@ -854,6 +881,9 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
{
struct rev_info rev;
struct commit *commit;
+ static const char *format = "format:%h: \"%s\"";
+ unsigned char junk_sha1[20];
+ const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
commit = lookup_commit(sha1);
if (!commit)
@@ -871,18 +901,24 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
rev.verbose_header = 1;
rev.show_root_diff = 1;
- get_commit_format("format:%h: %s", &rev);
+ get_commit_format(format, &rev);
rev.always_show_header = 0;
rev.diffopt.detect_rename = 1;
rev.diffopt.rename_limit = 100;
rev.diffopt.break_opt = 0;
diff_setup_done(&rev.diffopt);
- printf("Created %scommit ", initial_commit ? "initial " : "");
+ printf("[%s%s]: created ",
+ !prefixcmp(head, "refs/heads/") ?
+ head + 11 :
+ !strcmp(head, "HEAD") ?
+ "detached HEAD" :
+ head,
+ initial_commit ? " (root-commit)" : "");
if (!log_tree_commit(&rev, commit)) {
struct strbuf buf = STRBUF_INIT;
- format_commit_message(commit, "%h: %s", &buf, DATE_NORMAL);
+ format_commit_message(commit, format + 7, &buf, DATE_NORMAL);
printf("%s\n", buf.buf);
strbuf_release(&buf);
}
@@ -901,34 +937,16 @@ static const char commit_utf8_warn[] =
"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";
-static void add_parent(struct strbuf *sb, const unsigned char *sha1)
-{
- struct object *obj = parse_object(sha1);
- const char *parent = sha1_to_hex(sha1);
- const char *cp;
-
- if (!obj)
- die("Unable to find commit parent %s", parent);
- if (obj->type != OBJ_COMMIT)
- die("Parent %s isn't a proper commit", parent);
-
- for (cp = sb->buf; cp && (cp = strstr(cp, "\nparent ")); cp += 8) {
- if (!memcmp(cp + 8, parent, 40) && cp[48] == '\n') {
- error("duplicate parent %s ignored", parent);
- return;
- }
- }
- strbuf_addf(sb, "parent %s\n", parent);
-}
-
int cmd_commit(int argc, const char **argv, const char *prefix)
{
- int header_len;
- struct strbuf sb;
+ struct strbuf sb = STRBUF_INIT;
const char *index_file, *reflog_msg;
char *nl, *p;
unsigned char commit_sha1[20];
struct ref_lock *ref_lock;
+ struct commit_list *parents = NULL, **pptr = &parents;
+ struct stat statbuf;
+ int allow_fast_forward = 1;
git_config(git_commit_config, NULL);
@@ -943,13 +961,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
return 1;
}
- /*
- * The commit object
- */
- strbuf_init(&sb, 0);
- strbuf_addf(&sb, "tree %s\n",
- sha1_to_hex(active_cache_tree->sha1));
-
/* Determine parents */
if (initial_commit) {
reflog_msg = "commit (initial)";
@@ -963,14 +974,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
die("could not parse HEAD commit");
for (c = commit->parents; c; c = c->next)
- add_parent(&sb, c->item->object.sha1);
+ pptr = &commit_list_insert(c->item, pptr)->next;
} else if (in_merge) {
- struct strbuf m;
+ struct strbuf m = STRBUF_INIT;
FILE *fp;
reflog_msg = "commit (merge)";
- add_parent(&sb, head_sha1);
- strbuf_init(&m, 0);
+ pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
fp = fopen(git_path("MERGE_HEAD"), "r");
if (fp == NULL)
die("could not open %s for reading: %s",
@@ -979,24 +989,26 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
unsigned char sha1[20];
if (get_sha1_hex(m.buf, sha1) < 0)
die("Corrupt MERGE_HEAD file (%s)", m.buf);
- add_parent(&sb, sha1);
+ pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next;
}
fclose(fp);
strbuf_release(&m);
+ if (!stat(git_path("MERGE_MODE"), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0)
+ die("could not read MERGE_MODE: %s",
+ strerror(errno));
+ if (!strcmp(sb.buf, "no-ff"))
+ allow_fast_forward = 0;
+ }
+ if (allow_fast_forward)
+ parents = reduce_heads(parents);
} else {
reflog_msg = "commit";
- strbuf_addf(&sb, "parent %s\n", sha1_to_hex(head_sha1));
+ pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
}
- strbuf_addf(&sb, "author %s\n",
- fmt_ident(author_name, author_email, author_date, IDENT_ERROR_ON_NO_NAME));
- strbuf_addf(&sb, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
- if (!is_encoding_utf8(git_commit_encoding))
- strbuf_addf(&sb, "encoding %s\n", git_commit_encoding);
- strbuf_addch(&sb, '\n');
-
/* Finally, get the commit message */
- header_len = sb.len;
+ strbuf_reset(&sb);
if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
rollback_index_files();
die("could not read commit message");
@@ -1009,16 +1021,15 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (cleanup_mode != CLEANUP_NONE)
stripspace(&sb, cleanup_mode == CLEANUP_ALL);
- if (sb.len < header_len || message_is_empty(&sb, header_len)) {
+ if (message_is_empty(&sb)) {
rollback_index_files();
fprintf(stderr, "Aborting commit due to empty commit message.\n");
exit(1);
}
- strbuf_addch(&sb, '\0');
- if (is_encoding_utf8(git_commit_encoding) && !is_utf8(sb.buf))
- fprintf(stderr, commit_utf8_warn);
- if (write_sha1_file(sb.buf, sb.len - 1, commit_type, commit_sha1)) {
+ if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1,
+ fmt_ident(author_name, author_email, author_date,
+ IDENT_ERROR_ON_NO_NAME))) {
rollback_index_files();
die("failed to write commit object");
}
@@ -1027,12 +1038,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
initial_commit ? NULL : head_sha1,
0);
- nl = strchr(sb.buf + header_len, '\n');
+ nl = strchr(sb.buf, '\n');
if (nl)
strbuf_setlen(&sb, nl + 1 - sb.buf);
else
strbuf_addch(&sb, '\n');
- strbuf_remove(&sb, 0, header_len);
strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
@@ -1047,6 +1057,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
unlink(git_path("MERGE_HEAD"));
unlink(git_path("MERGE_MSG"));
+ unlink(git_path("MERGE_MODE"));
unlink(git_path("SQUASH_MSG"));
if (commit_index_files())
diff --git a/builtin-count-objects.c b/builtin-count-objects.c
index 91b5487..ab35b65 100644
--- a/builtin-count-objects.c
+++ b/builtin-count-objects.c
@@ -43,7 +43,7 @@ static void count_objects(DIR *d, char *path, int len, int verbose,
if (lstat(path, &st) || !S_ISREG(st.st_mode))
bad = 1;
else
- (*loose_size) += xsize_t(st.st_blocks);
+ (*loose_size) += xsize_t(on_disk_bytes(st));
}
if (bad) {
if (verbose) {
@@ -104,6 +104,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
if (verbose) {
struct packed_git *p;
unsigned long num_pack = 0;
+ unsigned long size_pack = 0;
if (!packed_git)
prepare_packed_git();
for (p = packed_git; p; p = p->next) {
@@ -112,17 +113,19 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
if (open_pack_index(p))
continue;
packed += p->num_objects;
+ size_pack += p->pack_size + p->index_size;
num_pack++;
}
printf("count: %lu\n", loose);
- printf("size: %lu\n", loose_size / 2);
+ printf("size: %lu\n", loose_size / 1024);
printf("in-pack: %lu\n", packed);
printf("packs: %lu\n", num_pack);
+ printf("size-pack: %lu\n", size_pack / 1024);
printf("prune-packable: %lu\n", packed_loose);
printf("garbage: %lu\n", garbage);
}
else
printf("%lu objects, %lu kilobytes\n",
- loose, loose_size / 2);
+ loose, loose_size / 1024);
return 0;
}
diff --git a/builtin-describe.c b/builtin-describe.c
index ec404c8..d2cfb1b 100644
--- a/builtin-describe.c
+++ b/builtin-describe.c
@@ -15,8 +15,8 @@ static const char * const describe_usage[] = {
};
static int debug; /* Display lots of verbose info */
-static int all; /* Default to annotated tags only */
-static int tags; /* But allow any tags if --tags is specified */
+static int all; /* Any valid ref can be used */
+static int tags; /* Allow lightweight tags */
static int longformat;
static int abbrev = DEFAULT_ABBREV;
static int max_candidates = 10;
@@ -112,8 +112,6 @@ static int compare_pt(const void *a_, const void *b_)
{
struct possible_tag *a = (struct possible_tag *)a_;
struct possible_tag *b = (struct possible_tag *)b_;
- if (a->name->prio != b->name->prio)
- return b->name->prio - a->name->prio;
if (a->depth != b->depth)
return a->depth - b->depth;
if (a->found_order != b->found_order)
diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c
index 415cb16..8ecefd4 100644
--- a/builtin-diff-tree.c
+++ b/builtin-diff-tree.c
@@ -14,20 +14,10 @@ static int diff_tree_commit_sha1(const unsigned char *sha1)
return log_tree_commit(&log_tree_opt, commit);
}
-static int diff_tree_stdin(char *line)
+/* Diff one or more commits. */
+static int stdin_diff_commit(struct commit *commit, char *line, int len)
{
- int len = strlen(line);
unsigned char sha1[20];
- struct commit *commit;
-
- if (!len || line[len-1] != '\n')
- return -1;
- line[len-1] = 0;
- if (get_sha1_hex(line, sha1))
- return -1;
- commit = lookup_commit(sha1);
- if (!commit || parse_commit(commit))
- return -1;
if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) {
/* Graft the fake parents locally to the commit */
int pos = 41;
@@ -52,6 +42,49 @@ static int diff_tree_stdin(char *line)
return log_tree_commit(&log_tree_opt, commit);
}
+/* Diff two trees. */
+static int stdin_diff_trees(struct tree *tree1, char *line, int len)
+{
+ unsigned char sha1[20];
+ struct tree *tree2;
+ if (len != 82 || !isspace(line[40]) || get_sha1_hex(line + 41, sha1))
+ return error("Need exactly two trees, separated by a space");
+ tree2 = lookup_tree(sha1);
+ if (!tree2 || parse_tree(tree2))
+ return -1;
+ printf("%s %s\n", sha1_to_hex(tree1->object.sha1),
+ sha1_to_hex(tree2->object.sha1));
+ diff_tree_sha1(tree1->object.sha1, tree2->object.sha1,
+ "", &log_tree_opt.diffopt);
+ log_tree_diff_flush(&log_tree_opt);
+ return 0;
+}
+
+static int diff_tree_stdin(char *line)
+{
+ int len = strlen(line);
+ unsigned char sha1[20];
+ struct object *obj;
+
+ if (!len || line[len-1] != '\n')
+ return -1;
+ line[len-1] = 0;
+ if (get_sha1_hex(line, sha1))
+ return -1;
+ obj = lookup_unknown_object(sha1);
+ if (!obj || !obj->parsed)
+ obj = parse_object(sha1);
+ if (!obj)
+ return -1;
+ if (obj->type == OBJ_COMMIT)
+ return stdin_diff_commit((struct commit *)obj, line, len);
+ if (obj->type == OBJ_TREE)
+ return stdin_diff_trees((struct tree *)obj, line, len);
+ error("Object %s is a %s, not a commit or tree",
+ sha1_to_hex(sha1), typename(obj->type));
+ return -1;
+}
+
static const char diff_tree_usage[] =
"git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n"
diff --git a/builtin-diff.c b/builtin-diff.c
index d5fe775..9c8c295 100644
--- a/builtin-diff.c
+++ b/builtin-diff.c
@@ -74,6 +74,8 @@ static int builtin_diff_b_f(struct rev_info *revs,
if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
die("'%s': not a regular file or symlink", path);
+ diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/");
+
if (blob[0].mode == S_IFINVALID)
blob[0].mode = canon_mode(st.st_mode);
@@ -175,10 +177,8 @@ 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));
- /* Again, the revs are all reverse */
for (i = 0; i < ents; i++)
- hashcpy((unsigned char *)(parent + i),
- ent[ents - 1 - i].item->sha1);
+ hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
diff_tree_combined(parent[0], parent + 1, ents - 1,
revs->dense_combined_merges, revs);
return 0;
diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c
index 7460ab7..469b07e 100644
--- a/builtin-fetch--tool.c
+++ b/builtin-fetch--tool.c
@@ -5,8 +5,7 @@
static char *get_stdin(void)
{
- struct strbuf buf;
- strbuf_init(&buf, 0);
+ struct strbuf buf = STRBUF_INIT;
if (strbuf_read(&buf, 0, 1024) < 0) {
die("error reading standard input: %s", strerror(errno));
}
diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c
index 85509f5..372bfa2 100644
--- a/builtin-fetch-pack.c
+++ b/builtin-fetch-pack.c
@@ -540,7 +540,7 @@ static int get_pack(int xd[2], char **pack_lockfile)
*av++ = "--fix-thin";
if (args.lock_pack || unpack_limit) {
int s = sprintf(keep_arg,
- "--keep=fetch-pack %d on ", getpid());
+ "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid());
if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
strcpy(keep_arg + s, "localhost");
*av++ = keep_arg;
@@ -735,7 +735,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
conn = git_connect(fd, (char *)dest, args.uploadpack,
args.verbose ? CONNECT_VERBOSE : 0);
if (conn) {
- get_remote_heads(fd[0], &ref, 0, NULL, 0);
+ get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
ref = fetch_pack(&args, fd, conn, ref, dest, nr_heads, heads, NULL);
close(fd[0]);
@@ -813,7 +813,8 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
)
die("shallow file was changed during fetch");
- fd = hold_lock_file_for_update(&lock, shallow, 1);
+ fd = hold_lock_file_for_update(&lock, shallow,
+ LOCK_DIE_ON_ERROR);
if (!write_shallow_commits(fd, 0)) {
unlink(shallow);
rollback_lock_file(&lock);
diff --git a/builtin-fetch.c b/builtin-fetch.c
index ee93d3a..f151cfa 100644
--- a/builtin-fetch.c
+++ b/builtin-fetch.c
@@ -521,8 +521,8 @@ static void find_non_local_tags(struct transport *transport,
will_fetch(head, ref->old_sha1))) {
string_list_insert(ref_name, &new_refs);
- rm = alloc_ref_from_str(ref_name);
- rm->peer_ref = alloc_ref_from_str(ref_name);
+ rm = alloc_ref(ref_name);
+ rm->peer_ref = alloc_ref(ref_name);
hashcpy(rm->old_sha1, ref_sha1);
**tail = rm;
@@ -534,6 +534,19 @@ static void find_non_local_tags(struct transport *transport,
string_list_clear(&new_refs, 0);
}
+static void check_not_current_branch(struct ref *ref_map)
+{
+ struct branch *current_branch = branch_get(NULL);
+
+ if (is_bare_repository() || !current_branch)
+ return;
+
+ for (; ref_map; ref_map = ref_map->next)
+ if (ref_map->peer_ref && !strcmp(current_branch->refname,
+ ref_map->peer_ref->name))
+ die("Refusing to fetch into current branch");
+}
+
static int do_fetch(struct transport *transport,
struct refspec *refs, int ref_count)
{
@@ -558,6 +571,8 @@ static int do_fetch(struct transport *transport,
}
ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
+ if (!update_head_ok)
+ check_not_current_branch(ref_map);
for (rm = ref_map; rm; rm = rm->next) {
if (rm->peer_ref)
diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c
index df02ba7..df18f40 100644
--- a/builtin-fmt-merge-msg.c
+++ b/builtin-fmt-merge-msg.c
@@ -5,8 +5,10 @@
#include "revision.h"
#include "tag.h"
-static const char *fmt_merge_msg_usage =
- "git fmt-merge-msg [--log] [--no-log] [--file <file>]";
+static const char * const fmt_merge_msg_usage[] = {
+ "git fmt-merge-msg [--log|--no-log] [--file <file>]",
+ NULL
+};
static int merge_summary;
@@ -347,46 +349,35 @@ int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
{
+ const char *inpath = NULL;
+ struct option options[] = {
+ OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"),
+ OPT_BOOLEAN(0, "summary", &merge_summary, "alias for --log"),
+ OPT_STRING('F', "file", &inpath, "file", "file to read from"),
+ OPT_END()
+ };
+
FILE *in = stdin;
- struct strbuf input, output;
+ struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
int ret;
git_config(fmt_merge_msg_config, NULL);
-
- while (argc > 1) {
- if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary"))
- merge_summary = 1;
- else if (!strcmp(argv[1], "--no-log")
- || !strcmp(argv[1], "--no-summary"))
- merge_summary = 0;
- else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
- if (argc < 3)
- die ("Which file?");
- if (!strcmp(argv[2], "-"))
- in = stdin;
- else {
- fclose(in);
- in = fopen(argv[2], "r");
- if (!in)
- die("cannot open %s", argv[2]);
- }
- argc--; argv++;
- } else
- break;
- argc--; argv++;
+ argc = parse_options(argc, argv, options, fmt_merge_msg_usage, 0);
+ if (argc > 0)
+ usage_with_options(fmt_merge_msg_usage, options);
+
+ if (inpath && strcmp(inpath, "-")) {
+ in = fopen(inpath, "r");
+ if (!in)
+ die("cannot open %s", inpath);
}
- if (argc > 1)
- usage(fmt_merge_msg_usage);
-
- strbuf_init(&input, 0);
if (strbuf_read(&input, fileno(in), 0) < 0)
die("could not read input file %s", strerror(errno));
- strbuf_init(&output, 0);
ret = fmt_merge_msg(merge_summary, &input, &output);
if (ret)
return ret;
- printf("%s", output.buf);
+ write_in_full(STDOUT_FILENO, output.buf, output.len);
return 0;
}
diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c
index 72c0878..fa6c1ed 100644
--- a/builtin-for-each-ref.c
+++ b/builtin-for-each-ref.c
@@ -544,6 +544,107 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, v
}
/*
+ * generate a format suitable for scanf from a ref_rev_parse_rules
+ * rule, that is replace the "%.*s" spec with a "%s" spec
+ */
+static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
+{
+ char *spec;
+
+ spec = strstr(rule, "%.*s");
+ if (!spec || strstr(spec + 4, "%.*s"))
+ die("invalid rule in ref_rev_parse_rules: %s", rule);
+
+ /* copy all until spec */
+ strncpy(scanf_fmt, rule, spec - rule);
+ scanf_fmt[spec - rule] = '\0';
+ /* copy new spec */
+ strcat(scanf_fmt, "%s");
+ /* copy remaining rule */
+ strcat(scanf_fmt, spec + 4);
+
+ return;
+}
+
+/*
+ * Shorten the refname to an non-ambiguous form
+ */
+static char *get_short_ref(struct refinfo *ref)
+{
+ int i;
+ static char **scanf_fmts;
+ static int nr_rules;
+ char *short_name;
+
+ /* pre generate scanf formats from ref_rev_parse_rules[] */
+ if (!nr_rules) {
+ size_t total_len = 0;
+
+ /* the rule list is NULL terminated, count them first */
+ for (; ref_rev_parse_rules[nr_rules]; nr_rules++)
+ /* no +1 because strlen("%s") < strlen("%.*s") */
+ total_len += strlen(ref_rev_parse_rules[nr_rules]);
+
+ scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
+
+ total_len = 0;
+ for (i = 0; i < nr_rules; i++) {
+ scanf_fmts[i] = (char *)&scanf_fmts[nr_rules]
+ + total_len;
+ gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]);
+ total_len += strlen(ref_rev_parse_rules[i]);
+ }
+ }
+
+ /* bail out if there are no rules */
+ if (!nr_rules)
+ return ref->refname;
+
+ /* buffer for scanf result, at most ref->refname must fit */
+ short_name = xstrdup(ref->refname);
+
+ /* skip first rule, it will always match */
+ for (i = nr_rules - 1; i > 0 ; --i) {
+ int j;
+ int short_name_len;
+
+ if (1 != sscanf(ref->refname, scanf_fmts[i], short_name))
+ continue;
+
+ short_name_len = strlen(short_name);
+
+ /*
+ * check if the short name resolves to a valid ref,
+ * but use only rules prior to the matched one
+ */
+ for (j = 0; j < i; j++) {
+ const char *rule = ref_rev_parse_rules[j];
+ unsigned char short_objectname[20];
+
+ /*
+ * the short name is ambiguous, if it resolves
+ * (with this previous rule) to a valid ref
+ * read_ref() returns 0 on success
+ */
+ if (!read_ref(mkpath(rule, short_name_len, short_name),
+ short_objectname))
+ break;
+ }
+
+ /*
+ * short name is non-ambiguous if all previous rules
+ * haven't resolved to a valid ref
+ */
+ if (j == i)
+ return short_name;
+ }
+
+ free(short_name);
+ return ref->refname;
+}
+
+
+/*
* Parse the object referred by ref, and grab needed value.
*/
static void populate_value(struct refinfo *ref)
@@ -568,13 +669,33 @@ static void populate_value(struct refinfo *ref)
for (i = 0; i < used_atom_cnt; i++) {
const char *name = used_atom[i];
struct atom_value *v = &ref->value[i];
- if (!strcmp(name, "refname"))
- v->s = ref->refname;
- else if (!strcmp(name, "*refname")) {
- int len = strlen(ref->refname);
- char *s = xmalloc(len + 4);
- sprintf(s, "%s^{}", ref->refname);
- v->s = s;
+ int deref = 0;
+ if (*name == '*') {
+ deref = 1;
+ name++;
+ }
+ if (!prefixcmp(name, "refname")) {
+ const char *formatp = strchr(name, ':');
+ const char *refname = ref->refname;
+
+ /* look for "short" refname format */
+ if (formatp) {
+ formatp++;
+ if (!strcmp(formatp, "short"))
+ refname = get_short_ref(ref);
+ else
+ die("unknown refname format %s",
+ formatp);
+ }
+
+ if (!deref)
+ v->s = refname;
+ else {
+ int len = strlen(refname);
+ char *s = xmalloc(len + 4);
+ sprintf(s, "%s^{}", refname);
+ v->s = s;
+ }
}
}
diff --git a/builtin-gc.c b/builtin-gc.c
index fac200e..7af65bb 100644
--- a/builtin-gc.c
+++ b/builtin-gc.c
@@ -26,7 +26,7 @@ static int pack_refs = 1;
static int aggressive_window = -1;
static int gc_auto_threshold = 6700;
static int gc_auto_pack_limit = 50;
-static char *prune_expire = "2.weeks.ago";
+static const char *prune_expire = "2.weeks.ago";
#define MAX_ADD 10
static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL};
@@ -57,15 +57,12 @@ static int gc_config(const char *var, const char *value, void *cb)
return 0;
}
if (!strcmp(var, "gc.pruneexpire")) {
- if (!value)
- return config_error_nonbool(var);
- if (strcmp(value, "now")) {
+ if (value && strcmp(value, "now")) {
unsigned long now = approxidate("now");
if (approxidate(value) >= now)
return error("Invalid %s: '%s'", var, value);
}
- prune_expire = xstrdup(value);
- return 0;
+ return git_config_string(&prune_expire, var, value);
}
return git_default_config(var, value, cb);
}
diff --git a/builtin-grep.c b/builtin-grep.c
index 3a51662..624f86e 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -295,6 +295,9 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
push_arg("-l");
if (opt->unmatch_name_only)
push_arg("-L");
+ if (opt->null_following_name)
+ /* in GNU grep git's "-z" translates to "-Z" */
+ push_arg("-Z");
if (opt->count)
push_arg("-c");
if (opt->post_context || opt->pre_context) {
@@ -599,6 +602,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
opt.unmatch_name_only = 1;
continue;
}
+ if (!strcmp("-z", arg) ||
+ !strcmp("--null", arg)) {
+ opt.null_following_name = 1;
+ continue;
+ }
if (!strcmp("-c", arg) ||
!strcmp("--count", arg)) {
opt.count = 1;
diff --git a/builtin-help.c b/builtin-help.c
new file mode 100644
index 0000000..f076efa
--- /dev/null
+++ b/builtin-help.c
@@ -0,0 +1,463 @@
+/*
+ * builtin-help.c
+ *
+ * Builtin help command
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+#include "common-cmds.h"
+#include "parse-options.h"
+#include "run-command.h"
+#include "help.h"
+
+static struct man_viewer_list {
+ struct man_viewer_list *next;
+ char name[FLEX_ARRAY];
+} *man_viewer_list;
+
+static struct man_viewer_info_list {
+ struct man_viewer_info_list *next;
+ const char *info;
+ char name[FLEX_ARRAY];
+} *man_viewer_info_list;
+
+enum help_format {
+ HELP_FORMAT_MAN,
+ HELP_FORMAT_INFO,
+ HELP_FORMAT_WEB,
+};
+
+static int show_all = 0;
+static enum help_format help_format = HELP_FORMAT_MAN;
+static struct option builtin_help_options[] = {
+ OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
+ OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
+ OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
+ HELP_FORMAT_WEB),
+ OPT_SET_INT('i', "info", &help_format, "show info page",
+ HELP_FORMAT_INFO),
+ OPT_END(),
+};
+
+static const char * const builtin_help_usage[] = {
+ "git help [--all] [--man|--web|--info] [command]",
+ NULL
+};
+
+static enum help_format parse_help_format(const char *format)
+{
+ if (!strcmp(format, "man"))
+ return HELP_FORMAT_MAN;
+ if (!strcmp(format, "info"))
+ return HELP_FORMAT_INFO;
+ if (!strcmp(format, "web") || !strcmp(format, "html"))
+ return HELP_FORMAT_WEB;
+ die("unrecognized help format '%s'", format);
+}
+
+static const char *get_man_viewer_info(const char *name)
+{
+ struct man_viewer_info_list *viewer;
+
+ for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
+ {
+ if (!strcasecmp(name, viewer->name))
+ return viewer->info;
+ }
+ return NULL;
+}
+
+static int check_emacsclient_version(void)
+{
+ struct strbuf buffer = STRBUF_INIT;
+ struct child_process ec_process;
+ const char *argv_ec[] = { "emacsclient", "--version", NULL };
+ int version;
+
+ /* emacsclient prints its version number on stderr */
+ memset(&ec_process, 0, sizeof(ec_process));
+ ec_process.argv = argv_ec;
+ ec_process.err = -1;
+ ec_process.stdout_to_stderr = 1;
+ if (start_command(&ec_process)) {
+ fprintf(stderr, "Failed to start emacsclient.\n");
+ return -1;
+ }
+ strbuf_read(&buffer, ec_process.err, 20);
+ close(ec_process.err);
+
+ /*
+ * Don't bother checking return value, because "emacsclient --version"
+ * seems to always exits with code 1.
+ */
+ finish_command(&ec_process);
+
+ if (prefixcmp(buffer.buf, "emacsclient")) {
+ fprintf(stderr, "Failed to parse emacsclient version.\n");
+ strbuf_release(&buffer);
+ return -1;
+ }
+
+ strbuf_remove(&buffer, 0, strlen("emacsclient"));
+ version = atoi(buffer.buf);
+
+ if (version < 22) {
+ fprintf(stderr,
+ "emacsclient version '%d' too old (< 22).\n",
+ version);
+ strbuf_release(&buffer);
+ return -1;
+ }
+
+ strbuf_release(&buffer);
+ return 0;
+}
+
+static void exec_woman_emacs(const char* path, const char *page)
+{
+ if (!check_emacsclient_version()) {
+ /* This works only with emacsclient version >= 22. */
+ struct strbuf man_page = STRBUF_INIT;
+
+ if (!path)
+ path = "emacsclient";
+ strbuf_addf(&man_page, "(woman \"%s\")", page);
+ execlp(path, "emacsclient", "-e", man_page.buf, NULL);
+ warning("failed to exec '%s': %s", path, strerror(errno));
+ }
+}
+
+static void exec_man_konqueror(const char* path, const char *page)
+{
+ const char *display = getenv("DISPLAY");
+ if (display && *display) {
+ struct strbuf man_page = STRBUF_INIT;
+ const char *filename = "kfmclient";
+
+ /* It's simpler to launch konqueror using kfmclient. */
+ if (path) {
+ const char *file = strrchr(path, '/');
+ if (file && !strcmp(file + 1, "konqueror")) {
+ char *new = xstrdup(path);
+ char *dest = strrchr(new, '/');
+
+ /* strlen("konqueror") == strlen("kfmclient") */
+ strcpy(dest + 1, "kfmclient");
+ path = new;
+ }
+ if (file)
+ filename = file;
+ } else
+ path = "kfmclient";
+ strbuf_addf(&man_page, "man:%s(1)", page);
+ execlp(path, filename, "newTab", man_page.buf, NULL);
+ warning("failed to exec '%s': %s", path, strerror(errno));
+ }
+}
+
+static void exec_man_man(const char* path, const char *page)
+{
+ if (!path)
+ path = "man";
+ execlp(path, "man", page, NULL);
+ warning("failed to exec '%s': %s", path, strerror(errno));
+}
+
+static void exec_man_cmd(const char *cmd, const char *page)
+{
+ struct strbuf shell_cmd = STRBUF_INIT;
+ strbuf_addf(&shell_cmd, "%s %s", cmd, page);
+ execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL);
+ warning("failed to exec '%s': %s", cmd, strerror(errno));
+}
+
+static void add_man_viewer(const char *name)
+{
+ struct man_viewer_list **p = &man_viewer_list;
+ size_t len = strlen(name);
+
+ while (*p)
+ p = &((*p)->next);
+ *p = xcalloc(1, (sizeof(**p) + len + 1));
+ strncpy((*p)->name, name, len);
+}
+
+static int supported_man_viewer(const char *name, size_t len)
+{
+ return (!strncasecmp("man", name, len) ||
+ !strncasecmp("woman", name, len) ||
+ !strncasecmp("konqueror", name, len));
+}
+
+static void do_add_man_viewer_info(const char *name,
+ size_t len,
+ const char *value)
+{
+ struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1);
+
+ strncpy(new->name, name, len);
+ new->info = xstrdup(value);
+ new->next = man_viewer_info_list;
+ man_viewer_info_list = new;
+}
+
+static int add_man_viewer_path(const char *name,
+ size_t len,
+ const char *value)
+{
+ if (supported_man_viewer(name, len))
+ do_add_man_viewer_info(name, len, value);
+ else
+ warning("'%s': path for unsupported man viewer.\n"
+ "Please consider using 'man.<tool>.cmd' instead.",
+ name);
+
+ return 0;
+}
+
+static int add_man_viewer_cmd(const char *name,
+ size_t len,
+ const char *value)
+{
+ if (supported_man_viewer(name, len))
+ warning("'%s': cmd for supported man viewer.\n"
+ "Please consider using 'man.<tool>.path' instead.",
+ name);
+ else
+ do_add_man_viewer_info(name, len, value);
+
+ return 0;
+}
+
+static int add_man_viewer_info(const char *var, const char *value)
+{
+ const char *name = var + 4;
+ const char *subkey = strrchr(name, '.');
+
+ if (!subkey)
+ return error("Config with no key for man viewer: %s", name);
+
+ if (!strcmp(subkey, ".path")) {
+ if (!value)
+ return config_error_nonbool(var);
+ return add_man_viewer_path(name, subkey - name, value);
+ }
+ if (!strcmp(subkey, ".cmd")) {
+ if (!value)
+ return config_error_nonbool(var);
+ return add_man_viewer_cmd(name, subkey - name, value);
+ }
+
+ warning("'%s': unsupported man viewer sub key.", subkey);
+ return 0;
+}
+
+static int git_help_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "help.format")) {
+ if (!value)
+ return config_error_nonbool(var);
+ help_format = parse_help_format(value);
+ return 0;
+ }
+ if (!strcmp(var, "man.viewer")) {
+ if (!value)
+ return config_error_nonbool(var);
+ add_man_viewer(value);
+ return 0;
+ }
+ if (!prefixcmp(var, "man."))
+ return add_man_viewer_info(var, value);
+
+ return git_default_config(var, value, cb);
+}
+
+static struct cmdnames main_cmds, other_cmds;
+
+void list_common_cmds_help(void)
+{
+ int i, longest = 0;
+
+ for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+ if (longest < strlen(common_cmds[i].name))
+ longest = strlen(common_cmds[i].name);
+ }
+
+ puts("The most commonly used git commands are:");
+ for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+ printf(" %s ", common_cmds[i].name);
+ mput_char(' ', longest - strlen(common_cmds[i].name));
+ puts(common_cmds[i].help);
+ }
+}
+
+static int is_git_command(const char *s)
+{
+ return is_in_cmdlist(&main_cmds, s) ||
+ is_in_cmdlist(&other_cmds, s);
+}
+
+static const char *prepend(const char *prefix, const char *cmd)
+{
+ size_t pre_len = strlen(prefix);
+ size_t cmd_len = strlen(cmd);
+ char *p = xmalloc(pre_len + cmd_len + 1);
+ memcpy(p, prefix, pre_len);
+ strcpy(p + pre_len, cmd);
+ return p;
+}
+
+static const char *cmd_to_page(const char *git_cmd)
+{
+ if (!git_cmd)
+ return "git";
+ else if (!prefixcmp(git_cmd, "git"))
+ return git_cmd;
+ else if (is_git_command(git_cmd))
+ return prepend("git-", git_cmd);
+ else
+ return prepend("git", git_cmd);
+}
+
+static void setup_man_path(void)
+{
+ struct strbuf new_path = STRBUF_INIT;
+ const char *old_path = getenv("MANPATH");
+
+ /* We should always put ':' after our path. If there is no
+ * old_path, the ':' at the end will let 'man' to try
+ * system-wide paths after ours to find the manual page. If
+ * there is old_path, we need ':' as delimiter. */
+ strbuf_addstr(&new_path, GIT_MAN_PATH);
+ strbuf_addch(&new_path, ':');
+ if (old_path)
+ strbuf_addstr(&new_path, old_path);
+
+ setenv("MANPATH", new_path.buf, 1);
+
+ strbuf_release(&new_path);
+}
+
+static void exec_viewer(const char *name, const char *page)
+{
+ const char *info = get_man_viewer_info(name);
+
+ if (!strcasecmp(name, "man"))
+ exec_man_man(info, page);
+ else if (!strcasecmp(name, "woman"))
+ exec_woman_emacs(info, page);
+ else if (!strcasecmp(name, "konqueror"))
+ exec_man_konqueror(info, page);
+ else if (info)
+ exec_man_cmd(info, page);
+ else
+ warning("'%s': unknown man viewer.", name);
+}
+
+static void show_man_page(const char *git_cmd)
+{
+ struct man_viewer_list *viewer;
+ const char *page = cmd_to_page(git_cmd);
+ const char *fallback = getenv("GIT_MAN_VIEWER");
+
+ setup_man_path();
+ for (viewer = man_viewer_list; viewer; viewer = viewer->next)
+ {
+ exec_viewer(viewer->name, page); /* will return when unable */
+ }
+ if (fallback)
+ exec_viewer(fallback, page);
+ exec_viewer("man", page);
+ die("no man viewer handled the request");
+}
+
+static void show_info_page(const char *git_cmd)
+{
+ const char *page = cmd_to_page(git_cmd);
+ setenv("INFOPATH", GIT_INFO_PATH, 1);
+ execlp("info", "info", "gitman", page, NULL);
+}
+
+static void get_html_page_path(struct strbuf *page_path, const char *page)
+{
+ struct stat st;
+ const char *html_path = system_path(GIT_HTML_PATH);
+
+ /* Check that we have a git documentation directory. */
+ if (stat(mkpath("%s/git.html", html_path), &st)
+ || !S_ISREG(st.st_mode))
+ die("'%s': not a documentation directory.", html_path);
+
+ strbuf_init(page_path, 0);
+ strbuf_addf(page_path, "%s/%s.html", html_path, page);
+}
+
+/*
+ * If open_html is not defined in a platform-specific way (see for
+ * example compat/mingw.h), we use the script web--browse to display
+ * HTML.
+ */
+#ifndef open_html
+void open_html(const char *path)
+{
+ execl_git_cmd("web--browse", "-c", "help.browser", path, NULL);
+}
+#endif
+
+static void show_html_page(const char *git_cmd)
+{
+ const char *page = cmd_to_page(git_cmd);
+ struct strbuf page_path; /* it leaks but we exec bellow */
+
+ get_html_page_path(&page_path, page);
+
+ open_html(page_path.buf);
+}
+
+int cmd_help(int argc, const char **argv, const char *prefix)
+{
+ int nongit;
+ const char *alias;
+ load_command_list("git-", &main_cmds, &other_cmds);
+
+ setup_git_directory_gently(&nongit);
+ git_config(git_help_config, NULL);
+
+ argc = parse_options(argc, argv, builtin_help_options,
+ builtin_help_usage, 0);
+
+ if (show_all) {
+ printf("usage: %s\n\n", git_usage_string);
+ list_commands("git commands", &main_cmds, &other_cmds);
+ printf("%s\n", git_more_info_string);
+ return 0;
+ }
+
+ if (!argv[0]) {
+ printf("usage: %s\n\n", git_usage_string);
+ list_common_cmds_help();
+ printf("\n%s\n", git_more_info_string);
+ return 0;
+ }
+
+ alias = alias_lookup(argv[0]);
+ if (alias && !is_git_command(argv[0])) {
+ printf("`git %s' is aliased to `%s'\n", argv[0], alias);
+ return 0;
+ }
+
+ switch (help_format) {
+ case HELP_FORMAT_MAN:
+ show_man_page(argv[0]);
+ break;
+ case HELP_FORMAT_INFO:
+ show_info_page(argv[0]);
+ break;
+ case HELP_FORMAT_WEB:
+ show_html_page(argv[0]);
+ break;
+ }
+
+ return 0;
+}
diff --git a/builtin-http-fetch.c b/builtin-http-fetch.c
index 03f34d7..f3e63d7 100644
--- a/builtin-http-fetch.c
+++ b/builtin-http-fetch.c
@@ -53,7 +53,7 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix)
}
url = argv[arg];
if (url && url[strlen(url)-1] != '/') {
- rewritten_url = malloc(strlen(url)+2);
+ rewritten_url = xmalloc(strlen(url)+2);
strcpy(rewritten_url, url);
strcat(rewritten_url, "/");
url = rewritten_url;
diff --git a/builtin-log.c b/builtin-log.c
index 2efe593..a0944f7 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -14,7 +14,6 @@
#include "tag.h"
#include "reflog-walk.h"
#include "patch-ids.h"
-#include "refs.h"
#include "run-command.h"
#include "shortlog.h"
@@ -25,31 +24,6 @@ static int default_show_root = 1;
static const char *fmt_patch_subject_prefix = "PATCH";
static const char *fmt_pretty;
-static void add_name_decoration(const char *prefix, const char *name, struct object *obj)
-{
- int plen = strlen(prefix);
- int nlen = strlen(name);
- struct name_decoration *res = xmalloc(sizeof(struct name_decoration) + plen + nlen);
- memcpy(res->name, prefix, plen);
- memcpy(res->name + plen, name, nlen + 1);
- res->next = add_decoration(&name_decoration, obj, res);
-}
-
-static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
-{
- struct object *obj = parse_object(sha1);
- if (!obj)
- return 0;
- add_name_decoration("", refname, obj);
- while (obj->type == OBJ_TAG) {
- obj = ((struct tag *)obj)->tagged;
- if (!obj)
- break;
- add_name_decoration("tag: ", refname, obj);
- }
- return 0;
-}
-
static void cmd_log_init(int argc, const char **argv, const char *prefix,
struct rev_info *rev)
{
@@ -80,8 +54,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--decorate")) {
- if (!decorate)
- for_each_ref(add_ref_decoration, NULL);
+ load_ref_decorations();
decorate = 1;
} else
die("unrecognized argument: %s", arg);
@@ -217,6 +190,11 @@ static int cmd_log_walk(struct rev_info *rev)
if (rev->early_output)
finish_early_output(rev);
+ /*
+ * For --check and --exit-code, the exit code is based on CHECK_FAILED
+ * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to
+ * retain that state information if replacing rev->diffopt in this loop
+ */
while ((commit = get_revision(rev)) != NULL) {
log_tree_commit(rev, commit);
if (!rev->reflog_info) {
@@ -227,7 +205,11 @@ static int cmd_log_walk(struct rev_info *rev)
free_commit_list(commit->parents);
commit->parents = NULL;
}
- return 0;
+ if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
+ DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) {
+ return 02;
+ }
+ return diff_result_code(&rev->diffopt, 0);
}
static int git_log_config(const char *var, const char *value, void *cb)
@@ -444,7 +426,7 @@ static int istitlechar(char c)
static const char *fmt_patch_suffix = ".patch";
static int numbered = 0;
-static int auto_number = 0;
+static int auto_number = 1;
static char **extra_hdr;
static int extra_hdr_nr;
@@ -503,6 +485,7 @@ static int git_format_config(const char *var, const char *value, void *cb)
return 0;
}
numbered = git_config_bool(var, value);
+ auto_number = auto_number && numbered;
return 0;
}
@@ -646,10 +629,9 @@ static void gen_message_id(struct rev_info *info, char *base)
const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
const char *email_start = strrchr(committer, '<');
const char *email_end = strrchr(committer, '>');
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
if (!email_start || !email_end || email_start > email_end - 1)
die("Could not extract email from committer identity.");
- strbuf_init(&buf, 0);
strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
(unsigned long) time(NULL),
(int)(email_end - email_start - 1), email_start + 1);
@@ -668,7 +650,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
const char *msg;
const char *extra_headers = rev->extra_headers;
struct shortlog log;
- struct strbuf sb;
+ struct strbuf sb = STRBUF_INIT;
int i;
const char *encoding = "utf-8";
struct diff_options opts;
@@ -689,7 +671,6 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
committer = git_committer_info(0);
msg = body;
- strbuf_init(&sb, 0);
pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
encoding);
pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers,
@@ -771,7 +752,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
const char *in_reply_to = NULL;
struct patch_ids ids;
char *add_signoff = NULL;
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
git_config(git_format_config, NULL);
init_revisions(&rev, prefix);
@@ -879,8 +860,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
}
argc = j;
- strbuf_init(&buf, 0);
-
for (i = 0; i < extra_hdr_nr; i++) {
strbuf_addstr(&buf, extra_hdr[i]);
strbuf_addch(&buf, '\n');
@@ -923,7 +902,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (argc > 1)
die ("unrecognized argument: %s", argv[1]);
- if (!rev.diffopt.output_format)
+ if (!rev.diffopt.output_format
+ || rev.diffopt.output_format == DIFF_FORMAT_PATCH)
rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH;
if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff)
@@ -1156,8 +1136,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
sign = '-';
if (verbose) {
- struct strbuf buf;
- strbuf_init(&buf, 0);
+ struct strbuf buf = STRBUF_INIT;
pretty_print_commit(CMIT_FMT_ONELINE, commit,
&buf, 0, NULL, NULL, 0, 0);
printf("%c %s %s\n", sign,
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index 068f424..b48327d 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -91,39 +91,10 @@ static void show_other_files(struct dir_struct *dir)
{
int i;
-
- /*
- * Skip matching and unmerged entries for the paths,
- * since we want just "others".
- *
- * (Matching entries are normally pruned during
- * the directory tree walk, but will show up for
- * gitlinks because we don't necessarily have
- * dir->show_other_directories set to suppress
- * them).
- */
for (i = 0; i < dir->nr; i++) {
struct dir_entry *ent = dir->entries[i];
- int len, pos;
- struct cache_entry *ce;
-
- /*
- * Remove the '/' at the end that directory
- * walking adds for directory entries.
- */
- len = ent->len;
- if (len && ent->name[len-1] == '/')
- len--;
- pos = cache_name_pos(ent->name, len);
- if (0 <= pos)
- continue; /* exact match */
- pos = -pos - 1;
- if (pos < active_nr) {
- ce = active_cache[pos];
- if (ce_namelen(ce) == len &&
- !memcmp(ce->name, ent->name, len))
- continue; /* Yup, this one exists unmerged */
- }
+ if (!cache_name_is_other(ent->name, ent->len))
+ continue;
show_dir_entry(tag_other, ent);
}
}
diff --git a/builtin-merge-base.c b/builtin-merge-base.c
index 3382b13..03fc1c2 100644
--- a/builtin-merge-base.c
+++ b/builtin-merge-base.c
@@ -1,10 +1,13 @@
#include "builtin.h"
#include "cache.h"
#include "commit.h"
+#include "parse-options.h"
-static int show_merge_base(struct commit *rev1, struct commit *rev2, int show_all)
+static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
{
- struct commit_list *result = get_merge_bases(rev1, rev2, 0);
+ struct commit_list *result;
+
+ result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0);
if (!result)
return 1;
@@ -19,8 +22,10 @@ static int show_merge_base(struct commit *rev1, struct commit *rev2, int show_al
return 0;
}
-static const char merge_base_usage[] =
-"git merge-base [--all] <commit-id> <commit-id>";
+static const char * const merge_base_usage[] = {
+ "git merge-base [--all] <commit-id> <commit-id>...",
+ NULL
+};
static struct commit *get_commit_reference(const char *arg)
{
@@ -38,23 +43,21 @@ static struct commit *get_commit_reference(const char *arg)
int cmd_merge_base(int argc, const char **argv, const char *prefix)
{
- struct commit *rev1, *rev2;
+ struct commit **rev;
+ int rev_nr = 0;
int show_all = 0;
- git_config(git_default_config, NULL);
+ struct option options[] = {
+ OPT_BOOLEAN('a', "all", &show_all, "outputs all common ancestors"),
+ OPT_END()
+ };
- while (1 < argc && argv[1][0] == '-') {
- const char *arg = argv[1];
- if (!strcmp(arg, "-a") || !strcmp(arg, "--all"))
- show_all = 1;
- else
- usage(merge_base_usage);
- argc--; argv++;
- }
- if (argc != 3)
- usage(merge_base_usage);
- rev1 = get_commit_reference(argv[1]);
- rev2 = get_commit_reference(argv[2]);
-
- return show_merge_base(rev1, rev2, show_all);
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, options, merge_base_usage, 0);
+ if (argc < 2)
+ usage_with_options(merge_base_usage, options);
+ rev = xmalloc(argc * sizeof(*rev));
+ while (argc-- > 0)
+ rev[rev_nr++] = get_commit_reference(*argv++);
+ return show_merge_base(rev, rev_nr, show_all);
}
diff --git a/builtin-merge-file.c b/builtin-merge-file.c
index 3605960..9d4e874 100644
--- a/builtin-merge-file.c
+++ b/builtin-merge-file.c
@@ -2,57 +2,76 @@
#include "cache.h"
#include "xdiff/xdiff.h"
#include "xdiff-interface.h"
+#include "parse-options.h"
-static const char merge_file_usage[] =
-"git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2";
+static const char *const merge_file_usage[] = {
+ "git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file file2",
+ NULL
+};
+
+static int label_cb(const struct option *opt, const char *arg, int unset)
+{
+ static int label_count = 0;
+ const char **names = (const char **)opt->value;
+
+ if (label_count >= 3)
+ return error("too many labels on the command line");
+ names[label_count++] = arg;
+ return 0;
+}
int cmd_merge_file(int argc, const char **argv, const char *prefix)
{
- const char *names[3];
+ const char *names[3] = { NULL, NULL, NULL };
mmfile_t mmfs[3];
mmbuffer_t result = {NULL, 0};
xpparam_t xpp = {XDF_NEED_MINIMAL};
int ret = 0, i = 0, to_stdout = 0;
+ int merge_level = XDL_MERGE_ZEALOUS_ALNUM;
+ int merge_style = 0, quiet = 0;
+ int nongit;
- while (argc > 4) {
- if (!strcmp(argv[1], "-L") && i < 3) {
- names[i++] = argv[2];
- argc--;
- argv++;
- } else if (!strcmp(argv[1], "-p") ||
- !strcmp(argv[1], "--stdout"))
- to_stdout = 1;
- else if (!strcmp(argv[1], "-q") ||
- !strcmp(argv[1], "--quiet"))
- freopen("/dev/null", "w", stderr);
- else
- usage(merge_file_usage);
- argc--;
- argv++;
- }
+ struct option options[] = {
+ OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"),
+ OPT_SET_INT(0, "diff3", &merge_style, "use a diff3 based merge", XDL_MERGE_DIFF3),
+ OPT__QUIET(&quiet),
+ OPT_CALLBACK('L', NULL, names, "name",
+ "set labels for file1/orig_file/file2", &label_cb),
+ OPT_END(),
+ };
- if (argc != 4)
- usage(merge_file_usage);
+ prefix = setup_git_directory_gently(&nongit);
+ if (!nongit) {
+ /* Read the configuration file */
+ git_config(git_xmerge_config, NULL);
+ if (0 <= git_xmerge_style)
+ merge_style = git_xmerge_style;
+ }
- for (; i < 3; i++)
- names[i] = argv[i + 1];
+ argc = parse_options(argc, argv, options, merge_file_usage, 0);
+ if (argc != 3)
+ usage_with_options(merge_file_usage, options);
+ if (quiet)
+ freopen("/dev/null", "w", stderr);
for (i = 0; i < 3; i++) {
- if (read_mmfile(mmfs + i, argv[i + 1]))
+ if (!names[i])
+ names[i] = argv[i];
+ if (read_mmfile(mmfs + i, argv[i]))
return -1;
if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
return error("Cannot merge binary files: %s\n",
- argv[i + 1]);
+ argv[i]);
}
ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
- &xpp, XDL_MERGE_ZEALOUS_ALNUM, &result);
+ &xpp, merge_level | merge_style, &result);
for (i = 0; i < 3; i++)
free(mmfs[i].ptr);
if (ret >= 0) {
- const char *filename = argv[1];
+ const char *filename = argv[0];
FILE *f = to_stdout ? stdout : fopen(filename, "wb");
if (!f)
diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c
index b973865..6b534c1 100644
--- a/builtin-merge-recursive.c
+++ b/builtin-merge-recursive.c
@@ -1,1288 +1,8 @@
-/*
- * Recursive Merge algorithm stolen from git-merge-recursive.py by
- * Fredrik Kuivinen.
- * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006
- */
#include "cache.h"
-#include "cache-tree.h"
#include "commit.h"
-#include "blob.h"
-#include "builtin.h"
-#include "tree-walk.h"
-#include "diff.h"
-#include "diffcore.h"
#include "tag.h"
-#include "unpack-trees.h"
-#include "string-list.h"
-#include "xdiff-interface.h"
-#include "ll-merge.h"
-#include "interpolate.h"
-#include "attr.h"
-#include "dir.h"
#include "merge-recursive.h"
-static int subtree_merge;
-
-static struct tree *shift_tree_object(struct tree *one, struct tree *two)
-{
- unsigned char shifted[20];
-
- /*
- * NEEDSWORK: this limits the recursion depth to hardcoded
- * value '2' to avoid excessive overhead.
- */
- shift_tree(one->object.sha1, two->object.sha1, shifted, 2);
- if (!hashcmp(two->object.sha1, shifted))
- return two;
- return lookup_tree(shifted);
-}
-
-/*
- * A virtual commit has
- * - (const char *)commit->util set to the name, and
- * - *(int *)commit->object.sha1 set to the virtual id.
- */
-
-static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
-{
- struct commit *commit = xcalloc(1, sizeof(struct commit));
- static unsigned virtual_id = 1;
- commit->tree = tree;
- commit->util = (void*)comment;
- *(int*)commit->object.sha1 = virtual_id++;
- /* avoid warnings */
- commit->object.parsed = 1;
- return commit;
-}
-
-/*
- * Since we use get_tree_entry(), which does not put the read object into
- * the object pool, we cannot rely on a == b.
- */
-static int sha_eq(const unsigned char *a, const unsigned char *b)
-{
- if (!a && !b)
- return 2;
- return a && b && hashcmp(a, b) == 0;
-}
-
-/*
- * Since we want to write the index eventually, we cannot reuse the index
- * for these (temporary) data.
- */
-struct stage_data
-{
- struct
- {
- unsigned mode;
- unsigned char sha[20];
- } stages[4];
- unsigned processed:1;
-};
-
-static struct string_list current_file_set = {NULL, 0, 0, 1};
-static struct string_list current_directory_set = {NULL, 0, 0, 1};
-
-static int call_depth = 0;
-static int verbosity = 2;
-static int diff_rename_limit = -1;
-static int merge_rename_limit = -1;
-static int buffer_output = 1;
-static struct strbuf obuf = STRBUF_INIT;
-
-static int show(int v)
-{
- return (!call_depth && verbosity >= v) || verbosity >= 5;
-}
-
-static void flush_output(void)
-{
- if (obuf.len) {
- fputs(obuf.buf, stdout);
- strbuf_reset(&obuf);
- }
-}
-
-static void output(int v, const char *fmt, ...)
-{
- int len;
- va_list ap;
-
- if (!show(v))
- return;
-
- strbuf_grow(&obuf, call_depth * 2 + 2);
- memset(obuf.buf + obuf.len, ' ', call_depth * 2);
- strbuf_setlen(&obuf, obuf.len + call_depth * 2);
-
- va_start(ap, fmt);
- len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap);
- va_end(ap);
-
- if (len < 0)
- len = 0;
- if (len >= strbuf_avail(&obuf)) {
- strbuf_grow(&obuf, len + 2);
- va_start(ap, fmt);
- len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap);
- va_end(ap);
- if (len >= strbuf_avail(&obuf)) {
- die("this should not happen, your snprintf is broken");
- }
- }
- strbuf_setlen(&obuf, obuf.len + len);
- strbuf_add(&obuf, "\n", 1);
- if (!buffer_output)
- flush_output();
-}
-
-static void output_commit_title(struct commit *commit)
-{
- int i;
- flush_output();
- for (i = call_depth; i--;)
- fputs(" ", stdout);
- if (commit->util)
- printf("virtual %s\n", (char *)commit->util);
- else {
- printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
- if (parse_commit(commit) != 0)
- printf("(bad commit)\n");
- else {
- const char *s;
- int len;
- for (s = commit->buffer; *s; s++)
- if (*s == '\n' && s[1] == '\n') {
- s += 2;
- break;
- }
- for (len = 0; s[len] && '\n' != s[len]; len++)
- ; /* do nothing */
- printf("%.*s\n", len, s);
- }
- }
-}
-
-static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
- const char *path, int stage, int refresh, int options)
-{
- struct cache_entry *ce;
- ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh);
- if (!ce)
- return error("addinfo_cache failed for path '%s'", path);
- return add_cache_entry(ce, options);
-}
-
-/*
- * This is a global variable which is used in a number of places but
- * only written to in the 'merge' function.
- *
- * index_only == 1 => Don't leave any non-stage 0 entries in the cache and
- * don't update the working directory.
- * 0 => Leave unmerged entries in the cache and update
- * the working directory.
- */
-static int index_only = 0;
-
-static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)
-{
- parse_tree(tree);
- init_tree_desc(desc, tree->buffer, tree->size);
-}
-
-static int git_merge_trees(int index_only,
- struct tree *common,
- struct tree *head,
- struct tree *merge)
-{
- int rc;
- struct tree_desc t[3];
- struct unpack_trees_options opts;
-
- memset(&opts, 0, sizeof(opts));
- if (index_only)
- opts.index_only = 1;
- else
- opts.update = 1;
- opts.merge = 1;
- opts.head_idx = 2;
- opts.fn = threeway_merge;
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
-
- init_tree_desc_from_tree(t+0, common);
- init_tree_desc_from_tree(t+1, head);
- init_tree_desc_from_tree(t+2, merge);
-
- rc = unpack_trees(3, t, &opts);
- cache_tree_free(&active_cache_tree);
- return rc;
-}
-
-struct tree *write_tree_from_memory(void)
-{
- struct tree *result = NULL;
-
- if (unmerged_cache()) {
- int i;
- output(0, "There are unmerged index entries:");
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- if (ce_stage(ce))
- output(0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name);
- }
- return NULL;
- }
-
- if (!active_cache_tree)
- active_cache_tree = cache_tree();
-
- if (!cache_tree_fully_valid(active_cache_tree) &&
- cache_tree_update(active_cache_tree,
- active_cache, active_nr, 0, 0) < 0)
- die("error building trees");
-
- result = lookup_tree(active_cache_tree->sha1);
-
- return result;
-}
-
-static int save_files_dirs(const unsigned char *sha1,
- const char *base, int baselen, const char *path,
- unsigned int mode, int stage, void *context)
-{
- int len = strlen(path);
- char *newpath = xmalloc(baselen + len + 1);
- memcpy(newpath, base, baselen);
- memcpy(newpath + baselen, path, len);
- newpath[baselen + len] = '\0';
-
- if (S_ISDIR(mode))
- string_list_insert(newpath, &current_directory_set);
- else
- string_list_insert(newpath, &current_file_set);
- free(newpath);
-
- return READ_TREE_RECURSIVE;
-}
-
-static int get_files_dirs(struct tree *tree)
-{
- int n;
- if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, NULL))
- return 0;
- n = current_file_set.nr + current_directory_set.nr;
- return n;
-}
-
-/*
- * Returns an index_entry instance which doesn't have to correspond to
- * a real cache entry in Git's index.
- */
-static struct stage_data *insert_stage_data(const char *path,
- struct tree *o, struct tree *a, struct tree *b,
- struct string_list *entries)
-{
- struct string_list_item *item;
- struct stage_data *e = xcalloc(1, sizeof(struct stage_data));
- get_tree_entry(o->object.sha1, path,
- e->stages[1].sha, &e->stages[1].mode);
- get_tree_entry(a->object.sha1, path,
- e->stages[2].sha, &e->stages[2].mode);
- get_tree_entry(b->object.sha1, path,
- e->stages[3].sha, &e->stages[3].mode);
- item = string_list_insert(path, entries);
- item->util = e;
- return e;
-}
-
-/*
- * Create a dictionary mapping file names to stage_data objects. The
- * dictionary contains one entry for every path with a non-zero stage entry.
- */
-static struct string_list *get_unmerged(void)
-{
- struct string_list *unmerged = xcalloc(1, sizeof(struct string_list));
- int i;
-
- unmerged->strdup_strings = 1;
-
- for (i = 0; i < active_nr; i++) {
- struct string_list_item *item;
- struct stage_data *e;
- struct cache_entry *ce = active_cache[i];
- if (!ce_stage(ce))
- continue;
-
- item = string_list_lookup(ce->name, unmerged);
- if (!item) {
- item = string_list_insert(ce->name, unmerged);
- item->util = xcalloc(1, sizeof(struct stage_data));
- }
- e = item->util;
- e->stages[ce_stage(ce)].mode = ce->ce_mode;
- hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1);
- }
-
- return unmerged;
-}
-
-struct rename
-{
- struct diff_filepair *pair;
- struct stage_data *src_entry;
- struct stage_data *dst_entry;
- unsigned processed:1;
-};
-
-/*
- * Get information of all renames which occurred between 'o_tree' and
- * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and
- * 'b_tree') to be able to associate the correct cache entries with
- * the rename information. 'tree' is always equal to either a_tree or b_tree.
- */
-static struct string_list *get_renames(struct tree *tree,
- struct tree *o_tree,
- struct tree *a_tree,
- struct tree *b_tree,
- struct string_list *entries)
-{
- int i;
- struct string_list *renames;
- struct diff_options opts;
-
- renames = xcalloc(1, sizeof(struct string_list));
- diff_setup(&opts);
- DIFF_OPT_SET(&opts, RECURSIVE);
- opts.detect_rename = DIFF_DETECT_RENAME;
- opts.rename_limit = merge_rename_limit >= 0 ? merge_rename_limit :
- diff_rename_limit >= 0 ? diff_rename_limit :
- 500;
- opts.warn_on_too_large_rename = 1;
- opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- if (diff_setup_done(&opts) < 0)
- die("diff setup failed");
- diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts);
- diffcore_std(&opts);
- for (i = 0; i < diff_queued_diff.nr; ++i) {
- struct string_list_item *item;
- struct rename *re;
- struct diff_filepair *pair = diff_queued_diff.queue[i];
- if (pair->status != 'R') {
- diff_free_filepair(pair);
- continue;
- }
- re = xmalloc(sizeof(*re));
- re->processed = 0;
- re->pair = pair;
- item = string_list_lookup(re->pair->one->path, entries);
- if (!item)
- re->src_entry = insert_stage_data(re->pair->one->path,
- o_tree, a_tree, b_tree, entries);
- else
- re->src_entry = item->util;
-
- item = string_list_lookup(re->pair->two->path, entries);
- if (!item)
- re->dst_entry = insert_stage_data(re->pair->two->path,
- o_tree, a_tree, b_tree, entries);
- else
- re->dst_entry = item->util;
- item = string_list_insert(pair->one->path, renames);
- item->util = re;
- }
- opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- diff_queued_diff.nr = 0;
- diff_flush(&opts);
- return renames;
-}
-
-static int update_stages(const char *path, struct diff_filespec *o,
- struct diff_filespec *a, struct diff_filespec *b,
- int clear)
-{
- int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
- if (clear)
- if (remove_file_from_cache(path))
- return -1;
- if (o)
- if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))
- return -1;
- if (a)
- if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options))
- return -1;
- if (b)
- if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options))
- return -1;
- return 0;
-}
-
-static int remove_file(int clean, const char *path, int no_wd)
-{
- int update_cache = index_only || clean;
- int update_working_directory = !index_only && !no_wd;
-
- if (update_cache) {
- if (remove_file_from_cache(path))
- return -1;
- }
- if (update_working_directory) {
- if (remove_path(path))
- return -1;
- }
- return 0;
-}
-
-static char *unique_path(const char *path, const char *branch)
-{
- char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1);
- int suffix = 0;
- struct stat st;
- char *p = newpath + strlen(path);
- strcpy(newpath, path);
- *(p++) = '~';
- strcpy(p, branch);
- for (; *p; ++p)
- if ('/' == *p)
- *p = '_';
- while (string_list_has_string(&current_file_set, newpath) ||
- string_list_has_string(&current_directory_set, newpath) ||
- lstat(newpath, &st) == 0)
- sprintf(p, "_%d", suffix++);
-
- string_list_insert(newpath, &current_file_set);
- return newpath;
-}
-
-static void flush_buffer(int fd, const char *buf, unsigned long size)
-{
- while (size > 0) {
- long ret = write_in_full(fd, buf, size);
- if (ret < 0) {
- /* Ignore epipe */
- if (errno == EPIPE)
- break;
- die("merge-recursive: %s", strerror(errno));
- } else if (!ret) {
- die("merge-recursive: disk full?");
- }
- size -= ret;
- buf += ret;
- }
-}
-
-static int make_room_for_path(const char *path)
-{
- int status;
- const char *msg = "failed to create path '%s'%s";
-
- status = safe_create_leading_directories_const(path);
- if (status) {
- if (status == -3) {
- /* something else exists */
- error(msg, path, ": perhaps a D/F conflict?");
- return -1;
- }
- die(msg, path, "");
- }
-
- /* Successful unlink is good.. */
- if (!unlink(path))
- return 0;
- /* .. and so is no existing file */
- if (errno == ENOENT)
- return 0;
- /* .. but not some other error (who really cares what?) */
- return error(msg, path, ": perhaps a D/F conflict?");
-}
-
-static void update_file_flags(const unsigned char *sha,
- unsigned mode,
- const char *path,
- int update_cache,
- int update_wd)
-{
- if (index_only)
- update_wd = 0;
-
- if (update_wd) {
- enum object_type type;
- void *buf;
- unsigned long size;
-
- if (S_ISGITLINK(mode))
- die("cannot read object %s '%s': It is a submodule!",
- sha1_to_hex(sha), path);
-
- buf = read_sha1_file(sha, &type, &size);
- if (!buf)
- die("cannot read object %s '%s'", sha1_to_hex(sha), path);
- if (type != OBJ_BLOB)
- die("blob expected for %s '%s'", sha1_to_hex(sha), path);
- if (S_ISREG(mode)) {
- struct strbuf strbuf;
- strbuf_init(&strbuf, 0);
- if (convert_to_working_tree(path, buf, size, &strbuf)) {
- free(buf);
- size = strbuf.len;
- buf = strbuf_detach(&strbuf, NULL);
- }
- }
-
- if (make_room_for_path(path) < 0) {
- update_wd = 0;
- free(buf);
- goto update_index;
- }
- if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
- int fd;
- if (mode & 0100)
- mode = 0777;
- else
- mode = 0666;
- fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
- if (fd < 0)
- die("failed to open %s: %s", path, strerror(errno));
- flush_buffer(fd, buf, size);
- close(fd);
- } else if (S_ISLNK(mode)) {
- char *lnk = xmemdupz(buf, size);
- safe_create_leading_directories_const(path);
- unlink(path);
- symlink(lnk, path);
- free(lnk);
- } else
- die("do not know what to do with %06o %s '%s'",
- mode, sha1_to_hex(sha), path);
- free(buf);
- }
- update_index:
- if (update_cache)
- add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
-}
-
-static void update_file(int clean,
- const unsigned char *sha,
- unsigned mode,
- const char *path)
-{
- update_file_flags(sha, mode, path, index_only || clean, !index_only);
-}
-
-/* Low level file merging, update and removal */
-
-struct merge_file_info
-{
- unsigned char sha[20];
- unsigned mode;
- unsigned clean:1,
- merge:1;
-};
-
-static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
-{
- unsigned long size;
- enum object_type type;
-
- if (!hashcmp(sha1, null_sha1)) {
- mm->ptr = xstrdup("");
- mm->size = 0;
- return;
- }
-
- mm->ptr = read_sha1_file(sha1, &type, &size);
- if (!mm->ptr || type != OBJ_BLOB)
- die("unable to read blob object %s", sha1_to_hex(sha1));
- mm->size = size;
-}
-
-static int merge_3way(mmbuffer_t *result_buf,
- struct diff_filespec *o,
- struct diff_filespec *a,
- struct diff_filespec *b,
- const char *branch1,
- const char *branch2)
-{
- mmfile_t orig, src1, src2;
- char *name1, *name2;
- int merge_status;
-
- name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
- name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
-
- fill_mm(o->sha1, &orig);
- fill_mm(a->sha1, &src1);
- fill_mm(b->sha1, &src2);
-
- merge_status = ll_merge(result_buf, a->path, &orig,
- &src1, name1, &src2, name2,
- index_only);
-
- free(name1);
- free(name2);
- free(orig.ptr);
- free(src1.ptr);
- free(src2.ptr);
- return merge_status;
-}
-
-static struct merge_file_info merge_file(struct diff_filespec *o,
- struct diff_filespec *a, struct diff_filespec *b,
- const char *branch1, const char *branch2)
-{
- struct merge_file_info result;
- result.merge = 0;
- result.clean = 1;
-
- if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
- result.clean = 0;
- if (S_ISREG(a->mode)) {
- result.mode = a->mode;
- hashcpy(result.sha, a->sha1);
- } else {
- result.mode = b->mode;
- hashcpy(result.sha, b->sha1);
- }
- } else {
- if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1))
- result.merge = 1;
-
- /*
- * Merge modes
- */
- if (a->mode == b->mode || a->mode == o->mode)
- result.mode = b->mode;
- else {
- result.mode = a->mode;
- if (b->mode != o->mode) {
- result.clean = 0;
- result.merge = 1;
- }
- }
-
- if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, o->sha1))
- hashcpy(result.sha, b->sha1);
- else if (sha_eq(b->sha1, o->sha1))
- hashcpy(result.sha, a->sha1);
- else if (S_ISREG(a->mode)) {
- mmbuffer_t result_buf;
- int merge_status;
-
- merge_status = merge_3way(&result_buf, o, a, b,
- branch1, branch2);
-
- if ((merge_status < 0) || !result_buf.ptr)
- die("Failed to execute internal merge");
-
- if (write_sha1_file(result_buf.ptr, result_buf.size,
- blob_type, result.sha))
- die("Unable to add %s to database",
- a->path);
-
- free(result_buf.ptr);
- result.clean = (merge_status == 0);
- } else if (S_ISGITLINK(a->mode)) {
- result.clean = 0;
- hashcpy(result.sha, a->sha1);
- } else if (S_ISLNK(a->mode)) {
- hashcpy(result.sha, a->sha1);
-
- if (!sha_eq(a->sha1, b->sha1))
- result.clean = 0;
- } else {
- die("unsupported object type in the tree");
- }
- }
-
- return result;
-}
-
-static void conflict_rename_rename(struct rename *ren1,
- const char *branch1,
- struct rename *ren2,
- const char *branch2)
-{
- char *del[2];
- int delp = 0;
- const char *ren1_dst = ren1->pair->two->path;
- const char *ren2_dst = ren2->pair->two->path;
- const char *dst_name1 = ren1_dst;
- const char *dst_name2 = ren2_dst;
- if (string_list_has_string(&current_directory_set, ren1_dst)) {
- dst_name1 = del[delp++] = unique_path(ren1_dst, branch1);
- output(1, "%s is a directory in %s added as %s instead",
- ren1_dst, branch2, dst_name1);
- remove_file(0, ren1_dst, 0);
- }
- if (string_list_has_string(&current_directory_set, ren2_dst)) {
- dst_name2 = del[delp++] = unique_path(ren2_dst, branch2);
- output(1, "%s is a directory in %s added as %s instead",
- ren2_dst, branch1, dst_name2);
- remove_file(0, ren2_dst, 0);
- }
- if (index_only) {
- remove_file_from_cache(dst_name1);
- remove_file_from_cache(dst_name2);
- /*
- * Uncomment to leave the conflicting names in the resulting tree
- *
- * update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1);
- * update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2);
- */
- } else {
- update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
- update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
- }
- while (delp--)
- free(del[delp]);
-}
-
-static void conflict_rename_dir(struct rename *ren1,
- const char *branch1)
-{
- char *new_path = unique_path(ren1->pair->two->path, branch1);
- output(1, "Renamed %s to %s instead", ren1->pair->one->path, new_path);
- remove_file(0, ren1->pair->two->path, 0);
- update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
- free(new_path);
-}
-
-static void conflict_rename_rename_2(struct rename *ren1,
- const char *branch1,
- struct rename *ren2,
- const char *branch2)
-{
- char *new_path1 = unique_path(ren1->pair->two->path, branch1);
- char *new_path2 = unique_path(ren2->pair->two->path, branch2);
- output(1, "Renamed %s to %s and %s to %s instead",
- ren1->pair->one->path, new_path1,
- ren2->pair->one->path, new_path2);
- remove_file(0, ren1->pair->two->path, 0);
- update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
- update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
- free(new_path2);
- free(new_path1);
-}
-
-static int process_renames(struct string_list *a_renames,
- struct string_list *b_renames,
- const char *a_branch,
- const char *b_branch)
-{
- int clean_merge = 1, i, j;
- struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};
- const struct rename *sre;
-
- for (i = 0; i < a_renames->nr; i++) {
- sre = a_renames->items[i].util;
- string_list_insert(sre->pair->two->path, &a_by_dst)->util
- = sre->dst_entry;
- }
- for (i = 0; i < b_renames->nr; i++) {
- sre = b_renames->items[i].util;
- string_list_insert(sre->pair->two->path, &b_by_dst)->util
- = sre->dst_entry;
- }
-
- for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {
- int compare;
- char *src;
- struct string_list *renames1, *renames2, *renames2Dst;
- struct rename *ren1 = NULL, *ren2 = NULL;
- const char *branch1, *branch2;
- const char *ren1_src, *ren1_dst;
-
- if (i >= a_renames->nr) {
- compare = 1;
- ren2 = b_renames->items[j++].util;
- } else if (j >= b_renames->nr) {
- compare = -1;
- ren1 = a_renames->items[i++].util;
- } else {
- compare = strcmp(a_renames->items[i].string,
- b_renames->items[j].string);
- if (compare <= 0)
- ren1 = a_renames->items[i++].util;
- if (compare >= 0)
- ren2 = b_renames->items[j++].util;
- }
-
- /* TODO: refactor, so that 1/2 are not needed */
- if (ren1) {
- renames1 = a_renames;
- renames2 = b_renames;
- renames2Dst = &b_by_dst;
- branch1 = a_branch;
- branch2 = b_branch;
- } else {
- struct rename *tmp;
- renames1 = b_renames;
- renames2 = a_renames;
- renames2Dst = &a_by_dst;
- branch1 = b_branch;
- branch2 = a_branch;
- tmp = ren2;
- ren2 = ren1;
- ren1 = tmp;
- }
- src = ren1->pair->one->path;
-
- ren1->dst_entry->processed = 1;
- ren1->src_entry->processed = 1;
-
- if (ren1->processed)
- continue;
- ren1->processed = 1;
-
- ren1_src = ren1->pair->one->path;
- ren1_dst = ren1->pair->two->path;
-
- if (ren2) {
- const char *ren2_src = ren2->pair->one->path;
- const char *ren2_dst = ren2->pair->two->path;
- /* Renamed in 1 and renamed in 2 */
- if (strcmp(ren1_src, ren2_src) != 0)
- die("ren1.src != ren2.src");
- ren2->dst_entry->processed = 1;
- ren2->processed = 1;
- if (strcmp(ren1_dst, ren2_dst) != 0) {
- clean_merge = 0;
- output(1, "CONFLICT (rename/rename): "
- "Rename \"%s\"->\"%s\" in branch \"%s\" "
- "rename \"%s\"->\"%s\" in \"%s\"%s",
- src, ren1_dst, branch1,
- src, ren2_dst, branch2,
- index_only ? " (left unresolved)": "");
- if (index_only) {
- remove_file_from_cache(src);
- update_file(0, ren1->pair->one->sha1,
- ren1->pair->one->mode, src);
- }
- conflict_rename_rename(ren1, branch1, ren2, branch2);
- } else {
- struct merge_file_info mfi;
- remove_file(1, ren1_src, 1);
- mfi = merge_file(ren1->pair->one,
- ren1->pair->two,
- ren2->pair->two,
- branch1,
- branch2);
- if (mfi.merge || !mfi.clean)
- output(1, "Renamed %s->%s", src, ren1_dst);
-
- if (mfi.merge)
- output(2, "Auto-merged %s", ren1_dst);
-
- if (!mfi.clean) {
- output(1, "CONFLICT (content): merge conflict in %s",
- ren1_dst);
- clean_merge = 0;
-
- if (!index_only)
- update_stages(ren1_dst,
- ren1->pair->one,
- ren1->pair->two,
- ren2->pair->two,
- 1 /* clear */);
- }
- update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
- }
- } else {
- /* Renamed in 1, maybe changed in 2 */
- struct string_list_item *item;
- /* we only use sha1 and mode of these */
- struct diff_filespec src_other, dst_other;
- int try_merge, stage = a_renames == renames1 ? 3: 2;
-
- remove_file(1, ren1_src, index_only || stage == 3);
-
- hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
- src_other.mode = ren1->src_entry->stages[stage].mode;
- hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha);
- dst_other.mode = ren1->dst_entry->stages[stage].mode;
-
- try_merge = 0;
-
- if (string_list_has_string(&current_directory_set, ren1_dst)) {
- clean_merge = 0;
- output(1, "CONFLICT (rename/directory): Renamed %s->%s in %s "
- " directory %s added in %s",
- ren1_src, ren1_dst, branch1,
- ren1_dst, branch2);
- conflict_rename_dir(ren1, branch1);
- } else if (sha_eq(src_other.sha1, null_sha1)) {
- clean_merge = 0;
- output(1, "CONFLICT (rename/delete): Renamed %s->%s in %s "
- "and deleted in %s",
- ren1_src, ren1_dst, branch1,
- branch2);
- update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
- } else if (!sha_eq(dst_other.sha1, null_sha1)) {
- const char *new_path;
- clean_merge = 0;
- try_merge = 1;
- output(1, "CONFLICT (rename/add): Renamed %s->%s in %s. "
- "%s added in %s",
- ren1_src, ren1_dst, branch1,
- ren1_dst, branch2);
- new_path = unique_path(ren1_dst, branch2);
- output(1, "Added as %s instead", new_path);
- update_file(0, dst_other.sha1, dst_other.mode, new_path);
- } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) {
- ren2 = item->util;
- clean_merge = 0;
- ren2->processed = 1;
- output(1, "CONFLICT (rename/rename): Renamed %s->%s in %s. "
- "Renamed %s->%s in %s",
- ren1_src, ren1_dst, branch1,
- ren2->pair->one->path, ren2->pair->two->path, branch2);
- conflict_rename_rename_2(ren1, branch1, ren2, branch2);
- } else
- try_merge = 1;
-
- if (try_merge) {
- struct diff_filespec *o, *a, *b;
- struct merge_file_info mfi;
- src_other.path = (char *)ren1_src;
-
- o = ren1->pair->one;
- if (a_renames == renames1) {
- a = ren1->pair->two;
- b = &src_other;
- } else {
- b = ren1->pair->two;
- a = &src_other;
- }
- mfi = merge_file(o, a, b,
- a_branch, b_branch);
-
- if (mfi.clean &&
- sha_eq(mfi.sha, ren1->pair->two->sha1) &&
- mfi.mode == ren1->pair->two->mode)
- /*
- * This messaged is part of
- * t6022 test. If you change
- * it update the test too.
- */
- output(3, "Skipped %s (merged same as existing)", ren1_dst);
- else {
- if (mfi.merge || !mfi.clean)
- output(1, "Renamed %s => %s", ren1_src, ren1_dst);
- if (mfi.merge)
- output(2, "Auto-merged %s", ren1_dst);
- if (!mfi.clean) {
- output(1, "CONFLICT (rename/modify): Merge conflict in %s",
- ren1_dst);
- clean_merge = 0;
-
- if (!index_only)
- update_stages(ren1_dst,
- o, a, b, 1);
- }
- update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
- }
- }
- }
- }
- string_list_clear(&a_by_dst, 0);
- string_list_clear(&b_by_dst, 0);
-
- return clean_merge;
-}
-
-static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)
-{
- return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
-}
-
-/* Per entry merge function */
-static int process_entry(const char *path, struct stage_data *entry,
- const char *branch1,
- const char *branch2)
-{
- /*
- printf("processing entry, clean cache: %s\n", index_only ? "yes": "no");
- print_index_entry("\tpath: ", entry);
- */
- int clean_merge = 1;
- unsigned o_mode = entry->stages[1].mode;
- unsigned a_mode = entry->stages[2].mode;
- unsigned b_mode = entry->stages[3].mode;
- unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
- unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
- unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
-
- if (o_sha && (!a_sha || !b_sha)) {
- /* Case A: Deleted in one */
- if ((!a_sha && !b_sha) ||
- (sha_eq(a_sha, o_sha) && !b_sha) ||
- (!a_sha && sha_eq(b_sha, o_sha))) {
- /* Deleted in both or deleted in one and
- * unchanged in the other */
- if (a_sha)
- output(2, "Removed %s", path);
- /* do not touch working file if it did not exist */
- remove_file(1, path, !a_sha);
- } else {
- /* Deleted in one and changed in the other */
- clean_merge = 0;
- if (!a_sha) {
- output(1, "CONFLICT (delete/modify): %s deleted in %s "
- "and modified in %s. Version %s of %s left in tree.",
- path, branch1,
- branch2, branch2, path);
- update_file(0, b_sha, b_mode, path);
- } else {
- output(1, "CONFLICT (delete/modify): %s deleted in %s "
- "and modified in %s. Version %s of %s left in tree.",
- path, branch2,
- branch1, branch1, path);
- update_file(0, a_sha, a_mode, path);
- }
- }
-
- } else if ((!o_sha && a_sha && !b_sha) ||
- (!o_sha && !a_sha && b_sha)) {
- /* Case B: Added in one. */
- const char *add_branch;
- const char *other_branch;
- unsigned mode;
- const unsigned char *sha;
- const char *conf;
-
- if (a_sha) {
- add_branch = branch1;
- other_branch = branch2;
- mode = a_mode;
- sha = a_sha;
- conf = "file/directory";
- } else {
- add_branch = branch2;
- other_branch = branch1;
- mode = b_mode;
- sha = b_sha;
- conf = "directory/file";
- }
- if (string_list_has_string(&current_directory_set, path)) {
- const char *new_path = unique_path(path, add_branch);
- clean_merge = 0;
- output(1, "CONFLICT (%s): There is a directory with name %s in %s. "
- "Added %s as %s",
- conf, path, other_branch, path, new_path);
- remove_file(0, path, 0);
- update_file(0, sha, mode, new_path);
- } else {
- output(2, "Added %s", path);
- update_file(1, sha, mode, path);
- }
- } else if (a_sha && b_sha) {
- /* Case C: Added in both (check for same permissions) and */
- /* case D: Modified in both, but differently. */
- const char *reason = "content";
- struct merge_file_info mfi;
- struct diff_filespec o, a, b;
-
- if (!o_sha) {
- reason = "add/add";
- o_sha = (unsigned char *)null_sha1;
- }
- output(2, "Auto-merged %s", path);
- o.path = a.path = b.path = (char *)path;
- hashcpy(o.sha1, o_sha);
- o.mode = o_mode;
- hashcpy(a.sha1, a_sha);
- a.mode = a_mode;
- hashcpy(b.sha1, b_sha);
- b.mode = b_mode;
-
- mfi = merge_file(&o, &a, &b,
- branch1, branch2);
-
- clean_merge = mfi.clean;
- if (mfi.clean)
- update_file(1, mfi.sha, mfi.mode, path);
- else if (S_ISGITLINK(mfi.mode))
- output(1, "CONFLICT (submodule): Merge conflict in %s "
- "- needs %s", path, sha1_to_hex(b.sha1));
- else {
- output(1, "CONFLICT (%s): Merge conflict in %s",
- reason, path);
-
- if (index_only)
- update_file(0, mfi.sha, mfi.mode, path);
- else
- update_file_flags(mfi.sha, mfi.mode, path,
- 0 /* update_cache */, 1 /* update_working_directory */);
- }
- } else if (!o_sha && !a_sha && !b_sha) {
- /*
- * this entry was deleted altogether. a_mode == 0 means
- * we had that path and want to actively remove it.
- */
- remove_file(1, path, !a_mode);
- } else
- die("Fatal merge failure, shouldn't happen.");
-
- return clean_merge;
-}
-
-int merge_trees(struct tree *head,
- struct tree *merge,
- struct tree *common,
- const char *branch1,
- const char *branch2,
- struct tree **result)
-{
- int code, clean;
-
- if (subtree_merge) {
- merge = shift_tree_object(head, merge);
- common = shift_tree_object(head, common);
- }
-
- if (sha_eq(common->object.sha1, merge->object.sha1)) {
- output(0, "Already uptodate!");
- *result = head;
- return 1;
- }
-
- code = git_merge_trees(index_only, common, head, merge);
-
- if (code != 0)
- die("merging of trees %s and %s failed",
- sha1_to_hex(head->object.sha1),
- sha1_to_hex(merge->object.sha1));
-
- if (unmerged_cache()) {
- struct string_list *entries, *re_head, *re_merge;
- int i;
- string_list_clear(&current_file_set, 1);
- string_list_clear(&current_directory_set, 1);
- get_files_dirs(head);
- get_files_dirs(merge);
-
- entries = get_unmerged();
- re_head = get_renames(head, common, head, merge, entries);
- re_merge = get_renames(merge, common, head, merge, entries);
- clean = process_renames(re_head, re_merge,
- branch1, branch2);
- for (i = 0; i < entries->nr; i++) {
- const char *path = entries->items[i].string;
- struct stage_data *e = entries->items[i].util;
- if (!e->processed
- && !process_entry(path, e, branch1, branch2))
- clean = 0;
- }
-
- string_list_clear(re_merge, 0);
- string_list_clear(re_head, 0);
- string_list_clear(entries, 1);
-
- }
- else
- clean = 1;
-
- if (index_only)
- *result = write_tree_from_memory();
-
- return clean;
-}
-
-static struct commit_list *reverse_commit_list(struct commit_list *list)
-{
- struct commit_list *next = NULL, *current, *backup;
- for (current = list; current; current = backup) {
- backup = current->next;
- current->next = next;
- next = current;
- }
- return next;
-}
-
-/*
- * Merge the commits h1 and h2, return the resulting virtual
- * commit object and a flag indicating the cleanness of the merge.
- */
-int merge_recursive(struct commit *h1,
- struct commit *h2,
- const char *branch1,
- const char *branch2,
- struct commit_list *ca,
- struct commit **result)
-{
- struct commit_list *iter;
- struct commit *merged_common_ancestors;
- struct tree *mrtree = mrtree;
- int clean;
-
- if (show(4)) {
- output(4, "Merging:");
- output_commit_title(h1);
- output_commit_title(h2);
- }
-
- if (!ca) {
- ca = get_merge_bases(h1, h2, 1);
- ca = reverse_commit_list(ca);
- }
-
- if (show(5)) {
- output(5, "found %u common ancestor(s):", commit_list_count(ca));
- for (iter = ca; iter; iter = iter->next)
- output_commit_title(iter->item);
- }
-
- merged_common_ancestors = pop_commit(&ca);
- if (merged_common_ancestors == NULL) {
- /* if there is no common ancestor, make an empty tree */
- struct tree *tree = xcalloc(1, sizeof(struct tree));
-
- tree->object.parsed = 1;
- tree->object.type = OBJ_TREE;
- pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
- merged_common_ancestors = make_virtual_commit(tree, "ancestor");
- }
-
- for (iter = ca; iter; iter = iter->next) {
- call_depth++;
- /*
- * When the merge fails, the result contains files
- * with conflict markers. The cleanness flag is
- * ignored, it was never actually used, as result of
- * merge_trees has always overwritten it: the committed
- * "conflicts" were already resolved.
- */
- discard_cache();
- merge_recursive(merged_common_ancestors, iter->item,
- "Temporary merge branch 1",
- "Temporary merge branch 2",
- NULL,
- &merged_common_ancestors);
- call_depth--;
-
- if (!merged_common_ancestors)
- die("merge returned no commit");
- }
-
- discard_cache();
- if (!call_depth) {
- read_cache();
- index_only = 0;
- } else
- index_only = 1;
-
- clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree,
- branch1, branch2, &mrtree);
-
- if (index_only) {
- *result = make_virtual_commit(mrtree, "merged tree");
- commit_list_insert(h1, &(*result)->parents);
- commit_list_insert(h2, &(*result)->parents->next);
- }
- flush_output();
- return clean;
-}
-
static const char *better_branch_name(const char *branch)
{
static char githead_env[8 + 40 + 1];
@@ -1295,103 +15,58 @@ static const char *better_branch_name(const char *branch)
return name ? name : branch;
}
-static struct commit *get_ref(const char *ref)
-{
- unsigned char sha1[20];
- struct object *object;
-
- if (get_sha1(ref, sha1))
- die("Could not resolve ref '%s'", ref);
- object = deref_tag(parse_object(sha1), ref, strlen(ref));
- if (!object)
- return NULL;
- if (object->type == OBJ_TREE)
- return make_virtual_commit((struct tree*)object,
- better_branch_name(ref));
- if (object->type != OBJ_COMMIT)
- return NULL;
- if (parse_commit((struct commit *)object))
- die("Could not parse commit '%s'", sha1_to_hex(object->sha1));
- return (struct commit *)object;
-}
-
-static int merge_config(const char *var, const char *value, void *cb)
-{
- if (!strcasecmp(var, "merge.verbosity")) {
- verbosity = git_config_int(var, value);
- return 0;
- }
- if (!strcasecmp(var, "diff.renamelimit")) {
- diff_rename_limit = git_config_int(var, value);
- return 0;
- }
- if (!strcasecmp(var, "merge.renamelimit")) {
- merge_rename_limit = git_config_int(var, value);
- return 0;
- }
- return git_default_config(var, value, cb);
-}
-
int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
{
- static const char *bases[20];
- static unsigned bases_count = 0;
- int i, clean;
- const char *branch1, *branch2;
- struct commit *result, *h1, *h2;
- struct commit_list *ca = NULL;
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
- int index_fd;
+ const unsigned char *bases[21];
+ unsigned bases_count = 0;
+ int i, failed;
+ unsigned char h1[20], h2[20];
+ struct merge_options o;
+ struct commit *result;
+ init_merge_options(&o);
if (argv[0]) {
int namelen = strlen(argv[0]);
if (8 < namelen &&
!strcmp(argv[0] + namelen - 8, "-subtree"))
- subtree_merge = 1;
+ o.subtree_merge = 1;
}
- git_config(merge_config, NULL);
- if (getenv("GIT_MERGE_VERBOSITY"))
- verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10);
-
if (argc < 4)
die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]);
for (i = 1; i < argc; ++i) {
if (!strcmp(argv[i], "--"))
break;
- if (bases_count < sizeof(bases)/sizeof(*bases))
- bases[bases_count++] = argv[i];
+ if (bases_count < ARRAY_SIZE(bases)-1) {
+ unsigned char *sha = xmalloc(20);
+ if (get_sha1(argv[i], sha))
+ die("Could not parse object '%s'", argv[i]);
+ bases[bases_count++] = sha;
+ }
+ else
+ warning("Cannot handle more than %zu bases. "
+ "Ignoring %s.", ARRAY_SIZE(bases)-1, argv[i]);
}
if (argc - i != 3) /* "--" "<head>" "<remote>" */
die("Not handling anything other than two heads merge.");
- if (verbosity >= 5)
- buffer_output = 0;
- branch1 = argv[++i];
- branch2 = argv[++i];
+ o.branch1 = argv[++i];
+ o.branch2 = argv[++i];
- h1 = get_ref(branch1);
- h2 = get_ref(branch2);
+ if (get_sha1(o.branch1, h1))
+ die("Could not resolve ref '%s'", o.branch1);
+ if (get_sha1(o.branch2, h2))
+ die("Could not resolve ref '%s'", o.branch2);
- branch1 = better_branch_name(branch1);
- branch2 = better_branch_name(branch2);
-
- if (show(3))
- printf("Merging %s with %s\n", branch1, branch2);
-
- index_fd = hold_locked_index(lock, 1);
-
- for (i = 0; i < bases_count; i++) {
- struct commit *ancestor = get_ref(bases[i]);
- ca = commit_list_insert(ancestor, &ca);
- }
- clean = merge_recursive(h1, h2, branch1, branch2, ca, &result);
+ o.branch1 = better_branch_name(o.branch1);
+ o.branch2 = better_branch_name(o.branch2);
- if (active_cache_changed &&
- (write_cache(index_fd, active_cache, active_nr) ||
- commit_locked_index(lock)))
- die ("unable to write %s", get_index_file());
+ if (o.verbosity >= 3)
+ printf("Merging %s with %s\n", o.branch1, o.branch2);
- return clean ? 0: 1;
+ failed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result);
+ if (failed < 0)
+ return 128; /* die() error code */
+ return failed;
}
diff --git a/builtin-merge.c b/builtin-merge.c
index d0bf1fc..5e7910b 100644
--- a/builtin-merge.c
+++ b/builtin-merge.c
@@ -22,6 +22,8 @@
#include "log-tree.h"
#include "color.h"
#include "rerere.h"
+#include "help.h"
+#include "merge-recursive.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
@@ -77,7 +79,9 @@ static int option_parse_message(const struct option *opt,
static struct strategy *get_strategy(const char *name)
{
int i;
- struct strbuf err;
+ struct strategy *ret;
+ static struct cmdnames main_cmds, other_cmds;
+ static int loaded;
if (!name)
return NULL;
@@ -86,12 +90,42 @@ static struct strategy *get_strategy(const char *name)
if (!strcmp(name, all_strategy[i].name))
return &all_strategy[i];
- strbuf_init(&err, 0);
- for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
- strbuf_addf(&err, " %s", all_strategy[i].name);
- fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
- fprintf(stderr, "Available strategies are:%s.\n", err.buf);
- exit(1);
+ if (!loaded) {
+ struct cmdnames not_strategies;
+ loaded = 1;
+
+ memset(&not_strategies, 0, sizeof(struct cmdnames));
+ load_command_list("git-merge-", &main_cmds, &other_cmds);
+ for (i = 0; i < main_cmds.cnt; i++) {
+ int j, found = 0;
+ struct cmdname *ent = main_cmds.names[i];
+ for (j = 0; j < ARRAY_SIZE(all_strategy); j++)
+ if (!strncmp(ent->name, all_strategy[j].name, ent->len)
+ && !all_strategy[j].name[ent->len])
+ found = 1;
+ if (!found)
+ add_cmdname(&not_strategies, ent->name, ent->len);
+ exclude_cmds(&main_cmds, &not_strategies);
+ }
+ }
+ if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {
+ fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
+ fprintf(stderr, "Available strategies are:");
+ for (i = 0; i < main_cmds.cnt; i++)
+ fprintf(stderr, " %s", main_cmds.names[i]->name);
+ fprintf(stderr, ".\n");
+ if (other_cmds.cnt) {
+ fprintf(stderr, "Available custom strategies are:");
+ for (i = 0; i < other_cmds.cnt; i++)
+ fprintf(stderr, " %s", other_cmds.names[i]->name);
+ fprintf(stderr, ".\n");
+ }
+ exit(1);
+ }
+
+ ret = xcalloc(1, sizeof(struct strategy));
+ ret->name = xstrdup(name);
+ return ret;
}
static void append_strategy(struct strategy *s)
@@ -145,6 +179,7 @@ static void drop_save(void)
{
unlink(git_path("MERGE_HEAD"));
unlink(git_path("MERGE_MSG"));
+ unlink(git_path("MERGE_MODE"));
}
static void save_state(void)
@@ -192,7 +227,7 @@ static void reset_hard(unsigned const char *sha1, int verbose)
static void restore_state(void)
{
- struct strbuf sb;
+ struct strbuf sb = STRBUF_INIT;
const char *args[] = { "stash", "apply", NULL, NULL };
if (is_null_sha1(stash))
@@ -200,7 +235,6 @@ static void restore_state(void)
reset_hard(head, 1);
- strbuf_init(&sb, 0);
args[2] = sha1_to_hex(stash);
/*
@@ -224,7 +258,7 @@ static void squash_message(void)
{
struct rev_info rev;
struct commit *commit;
- struct strbuf out;
+ struct strbuf out = STRBUF_INIT;
struct commit_list *j;
int fd;
@@ -248,7 +282,6 @@ static void squash_message(void)
if (prepare_revision_walk(&rev))
die("revision walk setup failed");
- strbuf_init(&out, 0);
strbuf_addstr(&out, "Squashed commit of the following:\n");
while ((commit = get_revision(&rev)) != NULL) {
strbuf_addch(&out, '\n');
@@ -293,9 +326,8 @@ static int run_hook(const char *name)
static void finish(const unsigned char *new_head, const char *msg)
{
- struct strbuf reflog_message;
+ struct strbuf reflog_message = STRBUF_INIT;
- strbuf_init(&reflog_message, 0);
if (!msg)
strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
else {
@@ -346,7 +378,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
{
struct object *remote_head;
unsigned char branch_head[20], buf_sha[20];
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
const char *ptr;
int len, early;
@@ -355,7 +387,6 @@ static void merge_name(const char *remote, struct strbuf *msg)
if (!remote_head)
die("'%s' does not point to a commit", remote);
- strbuf_init(&buf, 0);
strbuf_addstr(&buf, "refs/heads/");
strbuf_addstr(&buf, remote);
resolve_ref(buf.buf, branch_head, 0, 0);
@@ -410,10 +441,9 @@ static void merge_name(const char *remote, struct strbuf *msg)
if (!strcmp(remote, "FETCH_HEAD") &&
!access(git_path("FETCH_HEAD"), R_OK)) {
FILE *fp;
- struct strbuf line;
+ struct strbuf line = STRBUF_INIT;
char *ptr;
- strbuf_init(&line, 0);
fp = fopen(git_path("FETCH_HEAD"), "r");
if (!fp)
die("could not open %s for reading: %s",
@@ -511,30 +541,76 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
const char **args;
int i = 0, ret;
struct commit_list *j;
- struct strbuf buf;
-
- args = xmalloc((4 + commit_list_count(common) +
- commit_list_count(remoteheads)) * sizeof(char *));
- strbuf_init(&buf, 0);
- strbuf_addf(&buf, "merge-%s", strategy);
- args[i++] = buf.buf;
- for (j = common; j; j = j->next)
- args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
- args[i++] = "--";
- args[i++] = head_arg;
- for (j = remoteheads; j; j = j->next)
- args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
- args[i] = NULL;
- ret = run_command_v_opt(args, RUN_GIT_CMD);
- strbuf_release(&buf);
- i = 1;
- for (j = common; j; j = j->next)
- free((void *)args[i++]);
- i += 2;
- for (j = remoteheads; j; j = j->next)
- free((void *)args[i++]);
- free(args);
- return -ret;
+ struct strbuf buf = STRBUF_INIT;
+ int index_fd;
+ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+
+ index_fd = hold_locked_index(lock, 1);
+ refresh_cache(REFRESH_QUIET);
+ if (active_cache_changed &&
+ (write_cache(index_fd, active_cache, active_nr) ||
+ commit_locked_index(lock)))
+ return error("Unable to write index.");
+ rollback_lock_file(lock);
+
+ if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) {
+ int clean;
+ struct commit *result;
+ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+ int index_fd;
+ struct commit_list *reversed = NULL;
+ struct merge_options o;
+
+ if (remoteheads->next) {
+ error("Not handling anything other than two heads merge.");
+ return 2;
+ }
+
+ init_merge_options(&o);
+ if (!strcmp(strategy, "subtree"))
+ o.subtree_merge = 1;
+
+ o.branch1 = head_arg;
+ o.branch2 = remoteheads->item->util;
+
+ for (j = common; j; j = j->next)
+ commit_list_insert(j->item, &reversed);
+
+ index_fd = hold_locked_index(lock, 1);
+ clean = merge_recursive(&o, lookup_commit(head),
+ remoteheads->item, reversed, &result);
+ if (active_cache_changed &&
+ (write_cache(index_fd, active_cache, active_nr) ||
+ commit_locked_index(lock)))
+ die ("unable to write %s", get_index_file());
+ rollback_lock_file(lock);
+ return clean ? 0 : 1;
+ } else {
+ args = xmalloc((4 + commit_list_count(common) +
+ commit_list_count(remoteheads)) * sizeof(char *));
+ strbuf_addf(&buf, "merge-%s", strategy);
+ args[i++] = buf.buf;
+ for (j = common; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i++] = "--";
+ args[i++] = head_arg;
+ for (j = remoteheads; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i] = NULL;
+ ret = run_command_v_opt(args, RUN_GIT_CMD);
+ strbuf_release(&buf);
+ i = 1;
+ for (j = common; j; j = j->next)
+ free((void *)args[i++]);
+ i += 2;
+ for (j = remoteheads; j; j = j->next)
+ free((void *)args[i++]);
+ free(args);
+ discard_cache();
+ if (read_cache() < 0)
+ die("failed to read the cache");
+ return -ret;
+ }
}
static void count_diff_files(struct diff_queue_struct *q,
@@ -659,7 +735,7 @@ static int merge_trivial(void)
parent->next = xmalloc(sizeof(*parent->next));
parent->next->item = remoteheads->item;
parent->next->next = NULL;
- commit_tree(merge_msg.buf, result_tree, parent, result_commit);
+ commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
finish(result_commit, "In-index merge");
drop_save();
return 0;
@@ -688,7 +764,7 @@ static int finish_automerge(struct commit_list *common,
}
free_commit_list(remoteheads);
strbuf_addch(&merge_msg, '\n');
- commit_tree(merge_msg.buf, result_tree, parents, result_commit);
+ commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
finish(result_commit, buf.buf);
strbuf_release(&buf);
@@ -745,10 +821,6 @@ static int evaluate_result(void)
int cnt = 0;
struct rev_info rev;
- discard_cache();
- if (read_cache() < 0)
- die("failed to read the cache");
-
/* Check how many files differ. */
init_revisions(&rev, "");
setup_revisions(0, NULL, &rev, NULL);
@@ -770,7 +842,7 @@ static int evaluate_result(void)
int cmd_merge(int argc, const char **argv, const char *prefix)
{
unsigned char result_tree[20];
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
const char *head_arg;
int flag, head_invalid = 0, i;
int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
@@ -819,7 +891,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* Traditional format never would have "-m" so it is an
* additional safety measure to check for it.
*/
- strbuf_init(&buf, 0);
if (!have_message && is_old_style_invocation(argc, argv)) {
strbuf_addstr(&merge_msg, argv[0]);
@@ -836,6 +907,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (argc != 1)
die("Can merge only exactly one commit into "
"empty head");
+ if (squash)
+ die("Squash commit into empty head not supported yet");
+ if (!allow_fast_forward)
+ die("Non-fast-forward commit does not make sense into "
+ "an empty head");
remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
if (!remote_head)
die("%s - not something we can merge", argv[0]);
@@ -844,7 +920,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
reset_hard(remote_head->sha1, 0);
return 0;
} else {
- struct strbuf msg;
+ struct strbuf msg = STRBUF_INIT;
/* We are invoked directly as the first-class UI. */
head_arg = "HEAD";
@@ -857,7 +933,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* codepath so we discard the error in this
* loop.
*/
- strbuf_init(&msg, 0);
for (i = 0; i < argc; i++)
merge_name(argv[i], &msg);
fmt_merge_msg(option_log, &msg, &merge_msg);
@@ -877,12 +952,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
for (i = 0; i < argc; i++) {
struct object *o;
+ struct commit *commit;
o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
if (!o)
die("%s - not something we can merge", argv[i]);
- remotes = &commit_list_insert(lookup_commit(o->sha1),
- remotes)->next;
+ commit = lookup_commit(o->sha1);
+ commit->util = (void *)argv[i];
+ remotes = &commit_list_insert(commit, remotes)->next;
strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
setenv(buf.buf, argv[i], 1);
@@ -930,7 +1007,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
!common->next &&
!hashcmp(common->item->object.sha1, head)) {
/* Again the most common case of merging one remote. */
- struct strbuf msg;
+ struct strbuf msg = STRBUF_INIT;
struct object *o;
char hex[41];
@@ -940,7 +1017,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
hex,
find_unique_abbrev(remoteheads->item->object.sha1,
DEFAULT_ABBREV));
- strbuf_init(&msg, 0);
strbuf_addstr(&msg, "Fast forward");
if (have_message)
strbuf_addstr(&msg,
@@ -1076,7 +1152,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
/* Automerge succeeded. */
- discard_cache();
write_tree_trivial(result_tree);
automerge_was_ok = 1;
break;
@@ -1136,6 +1211,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
merge_msg.len)
die("Could not write to %s", git_path("MERGE_MSG"));
close(fd);
+ fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0)
+ die("Could open %s for writing", git_path("MERGE_MODE"));
+ strbuf_reset(&buf);
+ if (!allow_fast_forward)
+ strbuf_addf(&buf, "no-ff");
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+ die("Could not write to %s", git_path("MERGE_MODE"));
+ close(fd);
}
if (merge_was_ok) {
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index 4004e73..59c30d1 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -1369,12 +1369,10 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
int window, int depth, unsigned *processed)
{
uint32_t i, idx = 0, count = 0;
- unsigned int array_size = window * sizeof(struct unpacked);
struct unpacked *array;
unsigned long mem_usage = 0;
- array = xmalloc(array_size);
- memset(array, 0, array_size);
+ array = xcalloc(window, sizeof(struct unpacked));
for (;;) {
struct object_entry *entry = *list++;
@@ -1725,6 +1723,14 @@ static void prepare_pack(int window, int depth)
if (entry->type < 0)
die("unable to get type of object %s",
sha1_to_hex(entry->idx.sha1));
+ } else {
+ if (entry->type < 0) {
+ /*
+ * This object is not found, but we
+ * don't have to include it anyway.
+ */
+ continue;
+ }
}
delta_list[n++] = entry;
diff --git a/builtin-prune.c b/builtin-prune.c
index 1663f8b..7b4ec80 100644
--- a/builtin-prune.c
+++ b/builtin-prune.c
@@ -7,10 +7,11 @@
#include "parse-options.h"
static const char * const prune_usage[] = {
- "git prune [-n] [--expire <time>] [--] [<head>...]",
+ "git prune [-n] [-v] [--expire <time>] [--] [<head>...]",
NULL
};
static int show_only;
+static int verbose;
static unsigned long expire;
static int prune_tmp_object(const char *path, const char *filename)
@@ -39,11 +40,12 @@ static int prune_object(char *path, const char *filename, const unsigned char *s
if (st.st_mtime > expire)
return 0;
}
- if (show_only) {
+ if (show_only || verbose) {
enum object_type type = sha1_object_info(sha1, NULL);
printf("%s %s\n", sha1_to_hex(sha1),
(type > 0) ? typename(type) : "unknown");
- } else
+ }
+ if (!show_only)
unlink(fullpath);
return 0;
}
@@ -135,6 +137,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
const struct option options[] = {
OPT_BOOLEAN('n', NULL, &show_only,
"do not remove, show only"),
+ OPT_BOOLEAN('v', NULL, &verbose,
+ "report pruned objects"),
OPT_DATE(0, "expire", &expire,
"expire objects older than <time>"),
OPT_END()
diff --git a/builtin-push.c b/builtin-push.c
index f5cc762..122fdcf 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -59,8 +59,17 @@ static int do_push(const char *repo, int flags)
if (remote->mirror)
flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
- if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) && refspec)
- return -1;
+ if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
+ if (!strcmp(*refspec, "refs/tags/*"))
+ return error("--all and --tags are incompatible");
+ return error("--all can't be combined with refspecs");
+ }
+
+ if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) {
+ if (!strcmp(*refspec, "refs/tags/*"))
+ return error("--mirror and --tags are incompatible");
+ return error("--mirror can't be combined with refspecs");
+ }
if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==
(TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {
diff --git a/receive-pack.c b/builtin-receive-pack.c
index d44c19e..9f60f31 100644
--- a/receive-pack.c
+++ b/builtin-receive-pack.c
@@ -6,6 +6,8 @@
#include "exec_cmd.h"
#include "commit.h"
#include "object.h"
+#include "remote.h"
+#include "transport.h"
static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
@@ -407,7 +409,7 @@ static const char *unpack(void)
char keep_arg[256];
struct child_process ip;
- s = sprintf(keep_arg, "--keep=receive-pack %i on ", getpid());
+ s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid());
if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
strcpy(keep_arg + s, "localhost");
@@ -462,14 +464,53 @@ static int delete_only(struct command *cmd)
return 1;
}
-int main(int argc, char **argv)
+static int add_refs_from_alternate(struct alternate_object_database *e, void *unused)
+{
+ char *other;
+ size_t len;
+ struct remote *remote;
+ struct transport *transport;
+ const struct ref *extra;
+
+ e->name[-1] = '\0';
+ other = xstrdup(make_absolute_path(e->base));
+ e->name[-1] = '/';
+ len = strlen(other);
+
+ while (other[len-1] == '/')
+ other[--len] = '\0';
+ if (len < 8 || memcmp(other + len - 8, "/objects", 8))
+ return 0;
+ /* Is this a git repository with refs? */
+ memcpy(other + len - 8, "/refs", 6);
+ if (!is_directory(other))
+ return 0;
+ other[len - 8] = '\0';
+ remote = remote_get(other);
+ transport = transport_get(remote, other);
+ for (extra = transport_get_remote_refs(transport);
+ extra;
+ extra = extra->next) {
+ add_extra_ref(".have", extra->old_sha1, 0);
+ }
+ transport_disconnect(transport);
+ free(other);
+ return 0;
+}
+
+static void add_alternate_refs(void)
+{
+ foreach_alt_odb(add_refs_from_alternate, NULL);
+}
+
+int cmd_receive_pack(int argc, const char **argv, const char *prefix)
{
int i;
char *dir = NULL;
argv++;
for (i = 1; i < argc; i++) {
- char *arg = *argv++;
+ const char *arg = *argv++;
if (*arg == '-') {
/* Do flag handling here */
@@ -477,7 +518,7 @@ int main(int argc, char **argv)
}
if (dir)
usage(receive_pack_usage);
- dir = arg;
+ dir = xstrdup(arg);
}
if (!dir)
usage(receive_pack_usage);
@@ -497,7 +538,9 @@ int main(int argc, char **argv)
else if (0 <= receive_unpack_limit)
unpack_limit = receive_unpack_limit;
+ add_alternate_refs();
write_head_info();
+ clear_extra_refs();
/* EOF */
packet_flush(1);
diff --git a/builtin-reflog.c b/builtin-reflog.c
index 196fa03..6b3667e 100644
--- a/builtin-reflog.c
+++ b/builtin-reflog.c
@@ -540,11 +540,11 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
free(collected.e);
}
- while (i < argc) {
- const char *ref = argv[i++];
+ for (; i < argc; i++) {
+ char *ref;
unsigned char sha1[20];
- if (!resolve_ref(ref, sha1, 1, NULL)) {
- status |= error("%s points nowhere!", ref);
+ if (!dwim_log(argv[i], strlen(argv[i]), sha1, &ref)) {
+ status |= error("%s points nowhere!", argv[i]);
continue;
}
set_reflog_expiry_param(&cb, explicit_expiry, ref);
diff --git a/builtin-remote.c b/builtin-remote.c
index 90a4e35..df2be06 100644
--- a/builtin-remote.c
+++ b/builtin-remote.c
@@ -54,7 +54,7 @@ static int add(int argc, const char **argv)
struct string_list track = { NULL, 0, 0 };
const char *master = NULL;
struct remote *remote;
- struct strbuf buf, buf2;
+ struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
const char *name, *url;
int i;
@@ -81,9 +81,6 @@ static int add(int argc, const char **argv)
remote->fetch_refspec_nr))
die("remote %s already exists.", name);
- strbuf_init(&buf, 0);
- strbuf_init(&buf2, 0);
-
strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
if (!valid_fetch_refspec(buf2.buf))
die("'%s' is not a valid remote name", name);
@@ -352,7 +349,7 @@ static int rm(int argc, const char **argv)
OPT_END()
};
struct remote *remote;
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
struct known_remotes known_remotes = { NULL, NULL };
struct string_list branches = { NULL, 0, 0, 1 };
struct branches_for_remote cb_data = { NULL, &branches, &known_remotes };
@@ -368,7 +365,6 @@ static int rm(int argc, const char **argv)
known_remotes.to_delete = remote;
for_each_remote(add_known_remote, &known_remotes);
- strbuf_init(&buf, 0);
strbuf_addf(&buf, "remote.%s", remote->name);
if (git_config_rename_section(buf.buf, NULL) < 1)
return error("Could not remove config section '%s'", buf.buf);
@@ -416,10 +412,9 @@ static void show_list(const char *title, struct string_list *list,
return;
printf(title, list->nr > 1 ? "es" : "", extra_arg);
- printf("\n ");
- for (i = 0; i < list->nr; i++)
- printf("%s%s", i ? " " : "", list->items[i].string);
printf("\n");
+ for (i = 0; i < list->nr; i++)
+ printf(" %s\n", list->items[i].string);
}
static int get_remote_ref_states(const char *name,
@@ -515,17 +510,17 @@ static int show(int argc, const char **argv)
show_list(" Tracked remote branch%s", &states.tracked, "");
if (states.remote->push_refspec_nr) {
- printf(" Local branch%s pushed with 'git push'\n ",
+ printf(" Local branch%s pushed with 'git push'\n",
states.remote->push_refspec_nr > 1 ?
"es" : "");
for (i = 0; i < states.remote->push_refspec_nr; i++) {
struct refspec *spec = states.remote->push + i;
- printf(" %s%s%s%s", spec->force ? "+" : "",
+ printf(" %s%s%s%s\n",
+ spec->force ? "+" : "",
abbrev_branch(spec->src),
spec->dst ? ":" : "",
spec->dst ? abbrev_branch(spec->dst) : "");
}
- printf("\n");
}
/* NEEDSWORK: free remote */
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
index facaff2..06cdeb7 100644
--- a/builtin-rev-list.c
+++ b/builtin-rev-list.c
@@ -107,8 +107,7 @@ static void show_commit(struct commit *commit)
putchar('\n');
if (revs.verbose_header && commit->buffer) {
- struct strbuf buf;
- strbuf_init(&buf, 0);
+ struct strbuf buf = STRBUF_INIT;
pretty_print_commit(revs.commit_format, commit,
&buf, revs.abbrev, NULL, NULL,
revs.date_mode, 0);
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index 9aa049e..81d5a6f 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -307,19 +307,17 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
OPT_END(),
};
- struct strbuf sb, parsed;
+ struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT;
const char **usage = NULL;
struct option *opts = NULL;
int onb = 0, osz = 0, unb = 0, usz = 0;
- strbuf_init(&parsed, 0);
strbuf_addstr(&parsed, "set --");
argc = parse_options(argc, argv, parseopt_opts, parseopt_usage,
PARSE_OPT_KEEP_DASHDASH);
if (argc < 1 || strcmp(argv[0], "--"))
usage_with_options(parseopt_usage, parseopt_opts);
- strbuf_init(&sb, 0);
/* get the usage up to the first line with a -- on it */
for (;;) {
if (strbuf_getline(&sb, stdin, '\n') == EOF)
diff --git a/builtin-revert.c b/builtin-revert.c
index 27881e9..4725540 100644
--- a/builtin-revert.c
+++ b/builtin-revert.c
@@ -11,6 +11,8 @@
#include "cache-tree.h"
#include "diff.h"
#include "revision.h"
+#include "rerere.h"
+#include "merge-recursive.h"
/*
* This implements the builtins revert and cherry-pick.
@@ -200,36 +202,6 @@ static void set_author_ident_env(const char *message)
sha1_to_hex(commit->object.sha1));
}
-static int merge_recursive(const char *base_sha1,
- const char *head_sha1, const char *head_name,
- const char *next_sha1, const char *next_name)
-{
- char buffer[256];
- const char *argv[6];
- int i = 0;
-
- sprintf(buffer, "GITHEAD_%s", head_sha1);
- setenv(buffer, head_name, 1);
- sprintf(buffer, "GITHEAD_%s", next_sha1);
- setenv(buffer, next_name, 1);
-
- /*
- * This three way merge is an interesting one. We are at
- * $head, and would want to apply the change between $commit
- * and $prev on top of us (when reverting), or the change between
- * $prev and $commit on top of us (when cherry-picking or replaying).
- */
- argv[i++] = "merge-recursive";
- if (base_sha1)
- argv[i++] = base_sha1;
- argv[i++] = "--";
- argv[i++] = head_sha1;
- argv[i++] = next_sha1;
- argv[i++] = NULL;
-
- return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | RUN_GIT_CMD);
-}
-
static char *help_msg(const unsigned char *sha1)
{
static char helpbuf[1024];
@@ -262,14 +234,27 @@ static int index_is_dirty(void)
return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);
}
+static struct tree *empty_tree(void)
+{
+ struct tree *tree = xcalloc(1, sizeof(struct tree));
+
+ tree->object.parsed = 1;
+ tree->object.type = OBJ_TREE;
+ pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
+ return tree;
+}
+
static int revert_or_cherry_pick(int argc, const char **argv)
{
unsigned char head[20];
struct commit *base, *next, *parent;
- int i;
+ int i, index_fd, clean;
char *oneline, *reencoded_message = NULL;
const char *message, *encoding;
const char *defmsg = xstrdup(git_path("MERGE_MSG"));
+ struct merge_options o;
+ struct tree *result, *next_tree, *base_tree, *head_tree;
+ static struct lock_file index_lock;
git_config(git_default_config, NULL);
me = action == REVERT ? "revert" : "cherry-pick";
@@ -280,6 +265,8 @@ static int revert_or_cherry_pick(int argc, const char **argv)
if (action == REVERT && !no_replay)
die("revert is incompatible with replay");
+ if (read_cache() < 0)
+ die("git %s: failed to read the index", me);
if (no_commit) {
/*
* We do not intend to commit immediately. We just want to
@@ -292,12 +279,12 @@ static int revert_or_cherry_pick(int argc, const char **argv)
} else {
if (get_sha1("HEAD", head))
die ("You do not have a valid HEAD");
- if (read_cache() < 0)
- die("could not read the index");
if (index_is_dirty())
die ("Dirty index: cannot %s", me);
- discard_cache();
}
+ discard_cache();
+
+ index_fd = hold_locked_index(&index_lock, 1);
if (!commit->parents) {
if (action == REVERT)
@@ -331,6 +318,10 @@ static int revert_or_cherry_pick(int argc, const char **argv)
die ("Cannot get commit message for %s",
sha1_to_hex(commit->object.sha1));
+ if (parent && parse_commit(parent) < 0)
+ die("%s: cannot parse parent commit %s",
+ me, sha1_to_hex(parent->object.sha1));
+
/*
* "commit" is an existing commit. We would want to apply
* the difference it introduces since its first parent "prev"
@@ -338,7 +329,8 @@ static int revert_or_cherry_pick(int argc, const char **argv)
* reverse of it if we are revert.
*/
- msg_fd = hold_lock_file_for_update(&msg_file, defmsg, 1);
+ msg_fd = hold_lock_file_for_update(&msg_file, defmsg,
+ LOCK_DIE_ON_ERROR);
encoding = get_encoding(message);
if (!encoding)
@@ -373,13 +365,26 @@ static int revert_or_cherry_pick(int argc, const char **argv)
}
}
- if (merge_recursive(base == NULL ?
- NULL : sha1_to_hex(base->object.sha1),
- sha1_to_hex(head), "HEAD",
- sha1_to_hex(next->object.sha1), oneline) ||
- write_cache_as_tree(head, 0, NULL)) {
+ read_cache();
+ init_merge_options(&o);
+ o.branch1 = "HEAD";
+ o.branch2 = oneline;
+
+ head_tree = parse_tree_indirect(head);
+ next_tree = next ? next->tree : empty_tree();
+ base_tree = base ? base->tree : empty_tree();
+
+ clean = merge_trees(&o,
+ head_tree,
+ next_tree, base_tree, &result);
+
+ if (active_cache_changed &&
+ (write_cache(index_fd, active_cache, active_nr) ||
+ commit_locked_index(&index_lock)))
+ die("%s: Unable to write new index file", me);
+
+ if (!clean) {
add_to_msg("\nConflicts:\n\n");
- read_cache();
for (i = 0; i < active_nr;) {
struct cache_entry *ce = active_cache[i++];
if (ce_stage(ce)) {
@@ -395,6 +400,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
die ("Error wrapping up %s", defmsg);
fprintf(stderr, "Automatic %s failed.%s\n",
me, help_msg(commit->object.sha1));
+ rerere();
exit(1);
}
if (commit_lock_file(&msg_file) < 0)
diff --git a/builtin-rm.c b/builtin-rm.c
index e06640c..b7126e3 100644
--- a/builtin-rm.c
+++ b/builtin-rm.c
@@ -79,7 +79,8 @@ static int check_local_mod(unsigned char *head, int index_only)
|| hashcmp(ce->sha1, sha1))
staged_changes = 1;
- if (local_changes && staged_changes)
+ if (local_changes && staged_changes &&
+ !(index_only && is_empty_blob_sha1(ce->sha1)))
errs = error("'%s' has staged content different "
"from both the file and the HEAD\n"
"(use -f to force removal)", name);
diff --git a/builtin-send-pack.c b/builtin-send-pack.c
index 2af9f29..910db92 100644
--- a/builtin-send-pack.c
+++ b/builtin-send-pack.c
@@ -18,7 +18,7 @@ static struct send_pack_args args = {
/*
* Make a pack stream and spit it out into file descriptor fd
*/
-static int pack_objects(int fd, struct ref *refs)
+static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra)
{
/*
* The child becomes pack-objects --revs; we feed
@@ -34,6 +34,8 @@ static int pack_objects(int fd, struct ref *refs)
NULL,
};
struct child_process po;
+ int i;
+ char buf[42];
if (args.use_thin_pack)
argv[4] = "--thin";
@@ -49,9 +51,15 @@ static int pack_objects(int fd, struct ref *refs)
* We feed the pack-objects we just spawned with revision
* parameters by writing to the pipe.
*/
- while (refs) {
- char buf[42];
+ for (i = 0; i < extra->nr; i++) {
+ memcpy(buf + 1, sha1_to_hex(&extra->array[i][0]), 40);
+ buf[0] = '^';
+ buf[41] = '\n';
+ if (!write_or_whine(po.in, buf, 42, "send-pack: send refs"))
+ break;
+ }
+ while (refs) {
if (!is_null_sha1(refs->old_sha1) &&
has_sha1_file(refs->old_sha1)) {
memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40);
@@ -381,14 +389,17 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
int expect_status_report = 0;
int flags = MATCH_REFS_NONE;
int ret;
+ struct extra_have_objects extra_have;
+ memset(&extra_have, 0, sizeof(extra_have));
if (args.send_all)
flags |= MATCH_REFS_ALL;
if (args.send_mirror)
flags |= MATCH_REFS_MIRROR;
/* No funny business with the matcher */
- remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL);
+ remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL,
+ &extra_have);
get_local_heads();
/* Does the other end support the reporting? */
@@ -496,7 +507,7 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
packet_flush(out);
if (new_refs && !args.dry_run) {
- if (pack_objects(out, remote_refs) < 0)
+ if (pack_objects(out, remote_refs, &extra_have) < 0)
return -1;
}
else
diff --git a/builtin-show-branch.c b/builtin-show-branch.c
index 233eed4..306b850 100644
--- a/builtin-show-branch.c
+++ b/builtin-show-branch.c
@@ -259,11 +259,10 @@ static void join_revs(struct commit_list **list_p,
static void show_one_commit(struct commit *commit, int no_name)
{
- struct strbuf pretty;
+ struct strbuf pretty = STRBUF_INIT;
const char *pretty_str = "(unavailable)";
struct commit_name *name = commit->util;
- strbuf_init(&pretty, 0);
if (commit->object.parsed) {
pretty_print_commit(CMIT_FMT_ONELINE, commit,
&pretty, 0, NULL, NULL, 0, 0);
diff --git a/builtin-stripspace.c b/builtin-stripspace.c
index c0b2130..d6e3896 100644
--- a/builtin-stripspace.c
+++ b/builtin-stripspace.c
@@ -70,14 +70,13 @@ void stripspace(struct strbuf *sb, int skip_comments)
int cmd_stripspace(int argc, const char **argv, const char *prefix)
{
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
int strip_comments = 0;
if (argc > 1 && (!strcmp(argv[1], "-s") ||
!strcmp(argv[1], "--strip-comments")))
strip_comments = 1;
- strbuf_init(&buf, 0);
if (strbuf_read(&buf, 0, 1024) < 0)
die("could not read the input");
diff --git a/builtin-tag.c b/builtin-tag.c
index f2853d0..b13fa34 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -338,7 +338,7 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
int cmd_tag(int argc, const char **argv, const char *prefix)
{
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
unsigned char object[20], prev[20];
char ref[PATH_MAX];
const char *object_ref, *tag;
@@ -388,7 +388,6 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (verify)
return for_each_tag_name(argv, verify_tag);
- strbuf_init(&buf, 0);
if (msg.given || msgfile) {
if (msg.given && msgfile)
die("only one -F or -m option is allowed.");
diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c
index 40b20f2..9f4bdd3 100644
--- a/builtin-unpack-objects.c
+++ b/builtin-unpack-objects.c
@@ -19,7 +19,7 @@ static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict]
static unsigned char buffer[4096];
static unsigned int offset, len;
static off_t consumed_bytes;
-static SHA_CTX ctx;
+static git_SHA_CTX ctx;
/*
* When running under --strict mode, objects whose reachability are
@@ -59,7 +59,7 @@ static void *fill(int min)
if (min > sizeof(buffer))
die("cannot fill %d bytes", min);
if (offset) {
- SHA1_Update(&ctx, buffer, offset);
+ git_SHA1_Update(&ctx, buffer, offset);
memmove(buffer, buffer + offset, len);
offset = 0;
}
@@ -477,8 +477,7 @@ static void unpack_all(void)
if (!quiet)
progress = start_progress("Unpacking objects", nr_objects);
- obj_list = xmalloc(nr_objects * sizeof(*obj_list));
- memset(obj_list, 0, nr_objects * sizeof(*obj_list));
+ obj_list = xcalloc(nr_objects, sizeof(*obj_list));
for (i = 0; i < nr_objects; i++) {
unpack_one(i);
display_progress(progress, i + 1);
@@ -539,10 +538,10 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
/* We don't take any non-flag arguments now.. Maybe some day */
usage(unpack_usage);
}
- SHA1_Init(&ctx);
+ git_SHA1_Init(&ctx);
unpack_all();
- SHA1_Update(&ctx, buffer, offset);
- SHA1_Final(sha1, &ctx);
+ git_SHA1_Update(&ctx, buffer, offset);
+ git_SHA1_Final(sha1, &ctx);
if (strict)
write_rest();
if (hashcmp(fill(20), sha1))
diff --git a/builtin-update-index.c b/builtin-update-index.c
index 9d19c51..65d5775 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -194,6 +194,10 @@ static int process_path(const char *path)
int len;
struct stat st;
+ len = strlen(path);
+ if (has_symlink_leading_path(len, path))
+ return error("'%s' is beyond a symbolic link", path);
+
/*
* First things first: get the stat information, to decide
* what to do about the pathname!
@@ -201,7 +205,6 @@ static int process_path(const char *path)
if (lstat(path, &st) < 0)
return process_lstat_error(path, errno);
- len = strlen(path);
if (S_ISDIR(st.st_mode))
return process_directory(path, len, &st);
@@ -215,7 +218,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
struct cache_entry *ce;
if (!verify_path(path))
- return -1;
+ return error("Invalid path '%s'", path);
len = strlen(path);
size = cache_entry_size(len);
@@ -294,11 +297,9 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
static void read_index_info(int line_termination)
{
- struct strbuf buf;
- struct strbuf uq;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf uq = STRBUF_INIT;
- strbuf_init(&buf, 0);
- strbuf_init(&uq, 0);
while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
char *ptr, *tab;
char *path_name;
@@ -714,10 +715,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
free((char*)p);
}
if (read_from_stdin) {
- struct strbuf buf, nbuf;
+ struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
- strbuf_init(&buf, 0);
- strbuf_init(&nbuf, 0);
setup_work_tree();
while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
const char *p;
diff --git a/builtin.h b/builtin.h
index f3502d3..1495cf6 100644
--- a/builtin.h
+++ b/builtin.h
@@ -11,13 +11,14 @@ extern const char git_usage_string[];
extern const char git_more_info_string[];
extern void list_common_cmds_help(void);
-extern void help_unknown_cmd(const char *cmd);
+extern const char *help_unknown_cmd(const char *cmd);
extern void prune_packed_objects(int);
extern int read_line_with_nul(char *buf, int size, FILE *file);
extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
struct strbuf *out);
extern int commit_tree(const char *msg, unsigned char *tree,
- struct commit_list *parents, unsigned char *ret);
+ struct commit_list *parents, unsigned char *ret,
+ const char *author);
extern int check_pager_config(const char *cmd);
extern int cmd_add(int argc, const char **argv, const char *prefix);
@@ -78,6 +79,7 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix);
extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
extern int cmd_push(int argc, const char **argv, const char *prefix);
extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
extern int cmd_reflog(int argc, const char **argv, const char *prefix);
extern int cmd_remote(int argc, const char **argv, const char *prefix);
extern int cmd_config(int argc, const char **argv, const char *prefix);
diff --git a/bundle.c b/bundle.c
index 00b2aab..7d17a1f 100644
--- a/bundle.c
+++ b/bundle.c
@@ -186,7 +186,8 @@ int create_bundle(struct bundle_header *header, const char *path,
if (bundle_to_stdout)
bundle_fd = 1;
else
- bundle_fd = hold_lock_file_for_update(&lock, path, 1);
+ bundle_fd = hold_lock_file_for_update(&lock, path,
+ LOCK_DIE_ON_ERROR);
/* write signature */
write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature));
diff --git a/cache.h b/cache.h
index aea13b0..629cdf3 100644
--- a/cache.h
+++ b/cache.h
@@ -6,8 +6,14 @@
#include "hash.h"
#include SHA1_HEADER
-#include <zlib.h>
+#ifndef git_SHA_CTX
+#define git_SHA_CTX SHA_CTX
+#define git_SHA1_Init SHA1_Init
+#define git_SHA1_Update SHA1_Update
+#define git_SHA1_Final SHA1_Final
+#endif
+#include <zlib.h>
#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200
#define deflateBound(c,s) ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
#endif
@@ -126,6 +132,7 @@ struct cache_entry {
#define CE_NAMEMASK (0x0fff)
#define CE_STAGEMASK (0x3000)
+#define CE_EXTENDED (0x4000)
#define CE_VALID (0x8000)
#define CE_STAGESHIFT 12
@@ -270,6 +277,7 @@ static inline void remove_name_hash(struct cache_entry *ce)
#define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
#define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
#define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase))
+#define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen))
#endif
enum object_type {
@@ -312,6 +320,7 @@ extern int is_bare_repository(void);
extern int is_inside_git_dir(void);
extern char *git_work_tree_cfg;
extern int is_inside_work_tree(void);
+extern int have_git_dir(void);
extern const char *get_git_dir(void);
extern char *get_object_directory(void);
extern char *get_index_file(void);
@@ -370,6 +379,7 @@ extern int index_name_pos(const struct index_state *, const char *name, int name
#define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */
#define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */
#define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */
+#define ADD_CACHE_NEW_ONLY 16 /* Do not replace existing ones */
extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
@@ -378,10 +388,13 @@ extern int remove_file_from_index(struct index_state *, const char *path);
#define ADD_CACHE_VERBOSE 1
#define ADD_CACHE_PRETEND 2
#define ADD_CACHE_IGNORE_ERRORS 4
+#define ADD_CACHE_IGNORE_REMOVAL 8
+#define ADD_CACHE_INTENT 16
extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
extern int add_file_to_index(struct index_state *, const char *path, int flags);
extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
+extern int index_name_is_other(const struct index_state *, const char *, int);
/* do stat comparison even if CE_VALID is true */
#define CE_MATCH_IGNORE_VALID 01
@@ -392,7 +405,6 @@ extern int ie_modified(const struct index_state *, struct cache_entry *, struct
extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
-extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
@@ -411,6 +423,8 @@ struct lock_file {
char on_list;
char filename[PATH_MAX];
};
+#define LOCK_DIE_ON_ERROR 1
+#define LOCK_NODEREF 2
extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
extern int commit_lock_file(struct lock_file *);
@@ -452,6 +466,7 @@ enum safe_crlf {
extern enum safe_crlf safe_crlf;
enum branch_track {
+ BRANCH_TRACK_UNSPECIFIED = -1,
BRANCH_TRACK_NEVER = 0,
BRANCH_TRACK_REMOTE,
BRANCH_TRACK_ALWAYS,
@@ -507,6 +522,7 @@ static inline void hashclr(unsigned char *hash)
{
memset(hash, 0, 20);
}
+extern int is_empty_blob_sha1(const unsigned char *sha1);
int git_mkstemp(char *path, size_t n, const char *template);
@@ -534,6 +550,7 @@ static inline int is_absolute_path(const char *path)
{
return path[0] == '/' || has_dos_drive_prefix(path);
}
+int is_directory(const char *);
const char *make_absolute_path(const char *path);
const char *make_nonrelative_path(const char *path);
const char *make_relative_path(const char *abs, const char *base);
@@ -641,6 +658,8 @@ extern struct alternate_object_database {
} *alt_odb_list;
extern void prepare_alt_odb(void);
extern void add_to_alternates_file(const char *reference);
+typedef int alt_odb_fn(struct alternate_object_database *, void *);
+extern void foreach_alt_odb(alt_odb_fn, void*);
struct pack_window {
struct pack_window *next;
@@ -709,7 +728,11 @@ extern struct child_process *git_connect(int fd[2], const char *url, const char
extern int finish_connect(struct child_process *conn);
extern int path_match(const char *path, int nr, char **match);
extern int get_ack(int fd, unsigned char *result_sha1);
-extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags);
+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 int server_supports(const char *feature);
extern struct packed_git *parse_pack_index(unsigned char *sha1);
@@ -743,7 +766,6 @@ typedef int (*config_fn_t)(const char *, const char *, void *);
extern int git_default_config(const char *, const char *, void *);
extern int git_config_from_file(config_fn_t fn, const char *, void *);
extern int git_config(config_fn_t fn, void *);
-extern int git_parse_long(const char *, long *);
extern int git_parse_ulong(const char *, unsigned long *);
extern int git_config_int(const char *, const char *);
extern unsigned long git_config_ulong(const char *, const char *);
diff --git a/combine-diff.c b/combine-diff.c
index aa9d79e..5aa1104 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -143,8 +143,6 @@ static void append_lost(struct sline *sline, int n, const char *line, int len)
}
struct combine_diff_state {
- struct xdiff_emit_state xm;
-
unsigned int lno;
int ob, on, nb, nn;
unsigned long nmask;
@@ -217,17 +215,15 @@ static void combine_diff(const unsigned char *parent, mmfile_t *result_file,
parent_file.size = sz;
xpp.flags = XDF_NEED_MINIMAL;
memset(&xecfg, 0, sizeof(xecfg));
- ecb.outf = xdiff_outf;
- ecb.priv = &state;
memset(&state, 0, sizeof(state));
- state.xm.consume = consume_line;
state.nmask = nmask;
state.sline = sline;
state.lno = 1;
state.num_parent = num_parent;
state.n = n;
- xdi_diff(&parent_file, result_file, &xpp, &xecfg, &ecb);
+ xdi_diff_outf(&parent_file, result_file, consume_line, &state,
+ &xpp, &xecfg, &ecb);
free(parent_file.ptr);
/* Assign line numbers for this parent.
@@ -687,9 +683,13 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
int i, show_hunks;
int working_tree_file = is_null_sha1(elem->sha1);
int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
+ const char *a_prefix, *b_prefix;
mmfile_t result_file;
context = opt->context;
+ a_prefix = opt->a_prefix ? opt->a_prefix : "a/";
+ b_prefix = opt->b_prefix ? opt->b_prefix : "b/";
+
/* Read the result of merge first */
if (!working_tree_file)
result = grab_blob(elem->sha1, &result_size);
@@ -742,9 +742,8 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
/* If not a fake symlink, apply filters, e.g. autocrlf */
if (is_file) {
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
- strbuf_init(&buf, 0);
if (convert_to_git(elem->path, result, len, &buf, safe_crlf)) {
free(result);
result = strbuf_detach(&buf, &len);
@@ -865,13 +864,13 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
dump_quoted_path("--- ", "", "/dev/null",
c_meta, c_reset);
else
- dump_quoted_path("--- ", opt->a_prefix, elem->path,
+ dump_quoted_path("--- ", a_prefix, elem->path,
c_meta, c_reset);
if (deleted)
dump_quoted_path("+++ ", "", "/dev/null",
c_meta, c_reset);
else
- dump_quoted_path("+++ ", opt->b_prefix, elem->path,
+ dump_quoted_path("+++ ", b_prefix, elem->path,
c_meta, c_reset);
dump_sline(sline, cnt, num_parent,
DIFF_OPT_TST(opt, COLOR_DIFF));
diff --git a/commit.c b/commit.c
index dc0c5bf..c99db16 100644
--- a/commit.c
+++ b/commit.c
@@ -160,7 +160,7 @@ struct commit_graft *read_graft_line(char *buf, int len)
return graft;
}
-int read_graft_file(const char *graft_file)
+static int read_graft_file(const char *graft_file)
{
FILE *fp = fopen(graft_file, "r");
char buf[1024];
diff --git a/commit.h b/commit.h
index ecdd573..4c05864 100644
--- a/commit.h
+++ b/commit.h
@@ -118,10 +118,10 @@ struct commit_graft {
struct commit_graft *read_graft_line(char *buf, int len);
int register_commit_graft(struct commit_graft *, int);
-int read_graft_file(const char *graft_file);
struct commit_graft *lookup_commit_graft(const unsigned char *sha1);
extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
+extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos, int cleanup);
extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
extern int register_shallow(const unsigned char *sha1);
diff --git a/compat/cygwin.c b/compat/cygwin.c
new file mode 100644
index 0000000..f196753
--- /dev/null
+++ b/compat/cygwin.c
@@ -0,0 +1,137 @@
+#define WIN32_LEAN_AND_MEAN
+#include "../git-compat-util.h"
+#include "win32.h"
+#include "../cache.h" /* to read configuration */
+
+static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts)
+{
+ long long winTime = ((long long)ft->dwHighDateTime << 32) +
+ ft->dwLowDateTime;
+ winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */
+ /* convert 100-nsecond interval to seconds and nanoseconds */
+ ts->tv_sec = (time_t)(winTime/10000000);
+ ts->tv_nsec = (long)(winTime - ts->tv_sec*10000000LL) * 100;
+}
+
+#define size_to_blocks(s) (((s)+511)/512)
+
+/* do_stat is a common implementation for cygwin_lstat and cygwin_stat.
+ *
+ * To simplify its logic, in the case of cygwin symlinks, this implementation
+ * falls back to the cygwin version of stat/lstat, which is provided as the
+ * last argument.
+ */
+static int do_stat(const char *file_name, struct stat *buf, stat_fn_t cygstat)
+{
+ WIN32_FILE_ATTRIBUTE_DATA fdata;
+
+ if (file_name[0] == '/')
+ return cygstat (file_name, buf);
+
+ if (!(errno = get_file_attr(file_name, &fdata))) {
+ /*
+ * If the system attribute is set and it is not a directory then
+ * it could be a symbol link created in the nowinsymlinks mode.
+ * Normally, Cygwin works in the winsymlinks mode, so this situation
+ * is very unlikely. For the sake of simplicity of our code, let's
+ * Cygwin to handle it.
+ */
+ if ((fdata.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) &&
+ !(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ return cygstat(file_name, buf);
+
+ /* fill out the stat structure */
+ buf->st_dev = buf->st_rdev = 0; /* not used by Git */
+ buf->st_ino = 0;
+ buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
+ buf->st_nlink = 1;
+ buf->st_uid = buf->st_gid = 0;
+#ifdef __CYGWIN_USE_BIG_TYPES__
+ buf->st_size = ((_off64_t)fdata.nFileSizeHigh << 32) +
+ fdata.nFileSizeLow;
+#else
+ buf->st_size = (off_t)fdata.nFileSizeLow;
+#endif
+ buf->st_blocks = size_to_blocks(buf->st_size);
+ filetime_to_timespec(&fdata.ftLastAccessTime, &buf->st_atim);
+ filetime_to_timespec(&fdata.ftLastWriteTime, &buf->st_mtim);
+ filetime_to_timespec(&fdata.ftCreationTime, &buf->st_ctim);
+ return 0;
+ } else if (errno == ENOENT) {
+ /*
+ * In the winsymlinks mode (which is the default), Cygwin
+ * emulates symbol links using Windows shortcut files. These
+ * files are formed by adding .lnk extension. So, if we have
+ * not found the specified file name, it could be that it is
+ * a symbol link. Let's Cygwin to deal with that.
+ */
+ return cygstat(file_name, buf);
+ }
+ return -1;
+}
+
+/* We provide our own lstat/stat functions, since the provided Cygwin versions
+ * of these functions are too slow. These stat functions are tailored for Git's
+ * usage, and therefore they are not meant to be complete and correct emulation
+ * of lstat/stat functionality.
+ */
+static int cygwin_lstat(const char *path, struct stat *buf)
+{
+ return do_stat(path, buf, lstat);
+}
+
+static int cygwin_stat(const char *path, struct stat *buf)
+{
+ return do_stat(path, buf, stat);
+}
+
+
+/*
+ * At start up, we are trying to determine whether Win32 API or cygwin stat
+ * functions should be used. The choice is determined by core.ignorecygwinfstricks.
+ * Reading this option is not always possible immediately as git_dir may be
+ * not be set yet. So until it is set, use cygwin lstat/stat functions.
+ * However, if the trust_executable_bit is set, we must use the Cygwin posix
+ * stat/lstat as the Windows stat fuctions do not determine posix filemode.
+ */
+static int native_stat = 1;
+extern int trust_executable_bit;
+
+static int git_cygwin_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "core.ignorecygwinfstricks")) {
+ native_stat = git_config_bool(var, value);
+ return 0;
+ }
+ return git_default_config(var, value, cb);
+}
+
+static int init_stat(void)
+{
+ if (have_git_dir()) {
+ git_config(git_cygwin_config, NULL);
+ if (!trust_executable_bit && native_stat) {
+ cygwin_stat_fn = cygwin_stat;
+ cygwin_lstat_fn = cygwin_lstat;
+ } else {
+ cygwin_stat_fn = stat;
+ cygwin_lstat_fn = lstat;
+ }
+ return 0;
+ }
+ return 1;
+}
+
+static int cygwin_stat_stub(const char *file_name, struct stat *buf)
+{
+ return (init_stat() ? stat : *cygwin_stat_fn)(file_name, buf);
+}
+
+static int cygwin_lstat_stub(const char *file_name, struct stat *buf)
+{
+ return (init_stat() ? lstat : *cygwin_lstat_fn)(file_name, buf);
+}
+
+stat_fn_t cygwin_stat_fn = cygwin_stat_stub;
+stat_fn_t cygwin_lstat_fn = cygwin_lstat_stub;
+
diff --git a/compat/cygwin.h b/compat/cygwin.h
new file mode 100644
index 0000000..a3229f5
--- /dev/null
+++ b/compat/cygwin.h
@@ -0,0 +1,9 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+typedef int (*stat_fn_t)(const char*, struct stat*);
+extern stat_fn_t cygwin_stat_fn;
+extern stat_fn_t cygwin_lstat_fn;
+
+#define stat(path, buf) (*cygwin_stat_fn)(path, buf)
+#define lstat(path, buf) (*cygwin_lstat_fn)(path, buf)
diff --git a/compat/mingw.c b/compat/mingw.c
index 772cad5..09858f6 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1,4 +1,5 @@
#include "../git-compat-util.h"
+#include "win32.h"
#include "../strbuf.h"
unsigned int _CRT_fmode = _O_BINARY;
@@ -31,12 +32,6 @@ static inline time_t filetime_to_time_t(const FILETIME *ft)
return (time_t)winTime;
}
-static inline size_t size_to_blocks(size_t s)
-{
- return (s+511)/512;
-}
-
-extern int _getdrive( void );
/* We keep the do_lstat code in a separate function to avoid recursion.
* When a path ends with a slash, the stat will fail with ENOENT. In
* this case, we strip the trailing slashes and stat again.
@@ -45,46 +40,19 @@ static int do_lstat(const char *file_name, struct stat *buf)
{
WIN32_FILE_ATTRIBUTE_DATA fdata;
- if (GetFileAttributesExA(file_name, GetFileExInfoStandard, &fdata)) {
- int fMode = S_IREAD;
- if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- fMode |= S_IFDIR;
- else
- fMode |= S_IFREG;
- if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
- fMode |= S_IWRITE;
-
+ if (!(errno = get_file_attr(file_name, &fdata))) {
buf->st_ino = 0;
buf->st_gid = 0;
buf->st_uid = 0;
- buf->st_mode = fMode;
+ buf->st_nlink = 1;
+ buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */
- buf->st_blocks = size_to_blocks(buf->st_size);
- buf->st_dev = _getdrive() - 1;
+ buf->st_dev = buf->st_rdev = 0; /* not used by Git */
buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
- errno = 0;
return 0;
}
-
- switch (GetLastError()) {
- case ERROR_ACCESS_DENIED:
- case ERROR_SHARING_VIOLATION:
- case ERROR_LOCK_VIOLATION:
- case ERROR_SHARING_BUFFER_EXCEEDED:
- errno = EACCES;
- break;
- case ERROR_BUFFER_OVERFLOW:
- errno = ENAMETOOLONG;
- break;
- case ERROR_NOT_ENOUGH_MEMORY:
- errno = ENOMEM;
- break;
- default:
- errno = ENOENT;
- break;
- }
return -1;
}
@@ -94,7 +62,7 @@ static int do_lstat(const char *file_name, struct stat *buf)
* complete. Note that Git stat()s are redirected to mingw_lstat()
* too, since Windows doesn't really handle symlinks that well.
*/
-int mingw_lstat(const char *file_name, struct mingw_stat *buf)
+int mingw_lstat(const char *file_name, struct stat *buf)
{
int namelen;
static char alt_name[PATH_MAX];
@@ -122,8 +90,7 @@ int mingw_lstat(const char *file_name, struct mingw_stat *buf)
}
#undef fstat
-#undef stat
-int mingw_fstat(int fd, struct mingw_stat *buf)
+int mingw_fstat(int fd, struct stat *buf)
{
HANDLE fh = (HANDLE)_get_osfhandle(fd);
BY_HANDLE_FILE_INFORMATION fdata;
@@ -133,39 +100,17 @@ int mingw_fstat(int fd, struct mingw_stat *buf)
return -1;
}
/* direct non-file handles to MS's fstat() */
- if (GetFileType(fh) != FILE_TYPE_DISK) {
- struct stat st;
- if (fstat(fd, &st))
- return -1;
- buf->st_ino = st.st_ino;
- buf->st_gid = st.st_gid;
- buf->st_uid = st.st_uid;
- buf->st_mode = st.st_mode;
- buf->st_size = st.st_size;
- buf->st_blocks = size_to_blocks(buf->st_size);
- buf->st_dev = st.st_dev;
- buf->st_atime = st.st_atime;
- buf->st_mtime = st.st_mtime;
- buf->st_ctime = st.st_ctime;
- return 0;
- }
+ if (GetFileType(fh) != FILE_TYPE_DISK)
+ return fstat(fd, buf);
if (GetFileInformationByHandle(fh, &fdata)) {
- int fMode = S_IREAD;
- if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- fMode |= S_IFDIR;
- else
- fMode |= S_IFREG;
- if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
- fMode |= S_IWRITE;
-
buf->st_ino = 0;
buf->st_gid = 0;
buf->st_uid = 0;
- buf->st_mode = fMode;
+ buf->st_nlink = 1;
+ buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */
- buf->st_blocks = size_to_blocks(buf->st_size);
- buf->st_dev = _getdrive() - 1;
+ buf->st_dev = buf->st_rdev = 0; /* not used by Git */
buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
@@ -283,8 +228,13 @@ int poll(struct pollfd *ufds, unsigned int nfds, int timeout)
{
int i, pending;
- if (timeout != -1)
+ if (timeout >= 0) {
+ if (nfds == 0) {
+ Sleep(timeout);
+ return 0;
+ }
return errno = EINVAL, error("poll timeout not supported");
+ }
/* When there is only one fd to wait for, then we pretend that
* input is available and let the actual wait happen when the
diff --git a/compat/mingw.h b/compat/mingw.h
index a52e657..4f275cb 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -162,22 +162,12 @@ int mingw_rename(const char*, const char*);
/* Use mingw_lstat() instead of lstat()/stat() and
* mingw_fstat() instead of fstat() on Windows.
- * struct stat is redefined because it lacks the st_blocks member.
*/
-struct mingw_stat {
- unsigned st_mode;
- time_t st_mtime, st_atime, st_ctime;
- unsigned st_dev, st_ino, st_uid, st_gid;
- size_t st_size;
- size_t st_blocks;
-};
-int mingw_lstat(const char *file_name, struct mingw_stat *buf);
-int mingw_fstat(int fd, struct mingw_stat *buf);
+int mingw_lstat(const char *file_name, struct stat *buf);
+int mingw_fstat(int fd, struct stat *buf);
#define fstat mingw_fstat
#define lstat mingw_lstat
-#define stat mingw_stat
-static inline int mingw_stat(const char *file_name, struct mingw_stat *buf)
-{ return mingw_lstat(file_name, buf); }
+#define stat(x,y) mingw_lstat(x,y)
int mingw_utime(const char *file_name, const struct utimbuf *times);
#define utime mingw_utime
diff --git a/compat/win32.h b/compat/win32.h
new file mode 100644
index 0000000..c26384e
--- /dev/null
+++ b/compat/win32.h
@@ -0,0 +1,34 @@
+/* common Win32 functions for MinGW and Cygwin */
+#include <windows.h>
+
+static inline int file_attr_to_st_mode (DWORD attr)
+{
+ int fMode = S_IREAD;
+ if (attr & FILE_ATTRIBUTE_DIRECTORY)
+ fMode |= S_IFDIR;
+ else
+ fMode |= S_IFREG;
+ if (!(attr & FILE_ATTRIBUTE_READONLY))
+ fMode |= S_IWRITE;
+ return fMode;
+}
+
+static inline int get_file_attr(const char *fname, WIN32_FILE_ATTRIBUTE_DATA *fdata)
+{
+ if (GetFileAttributesExA(fname, GetFileExInfoStandard, fdata))
+ return 0;
+
+ switch (GetLastError()) {
+ case ERROR_ACCESS_DENIED:
+ case ERROR_SHARING_VIOLATION:
+ case ERROR_LOCK_VIOLATION:
+ case ERROR_SHARING_BUFFER_EXCEEDED:
+ return EACCES;
+ case ERROR_BUFFER_OVERFLOW:
+ return ENAMETOOLONG;
+ case ERROR_NOT_ENOUGH_MEMORY:
+ return ENOMEM;
+ default:
+ return ENOENT;
+ }
+}
diff --git a/config.c b/config.c
index 53f04a0..b8d289d 100644
--- a/config.c
+++ b/config.c
@@ -205,8 +205,27 @@ static int git_parse_file(config_fn_t fn, void *data)
int baselen = 0;
static char var[MAXNAME];
+ /* U+FEFF Byte Order Mark in UTF8 */
+ static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf";
+ const unsigned char *bomptr = utf8_bom;
+
for (;;) {
int c = get_next_char();
+ if (bomptr && *bomptr) {
+ /* We are at the file beginning; skip UTF8-encoded BOM
+ * if present. Sane editors won't put this in on their
+ * own, but e.g. Windows Notepad will do it happily. */
+ if ((unsigned char) c == *bomptr) {
+ bomptr++;
+ continue;
+ } else {
+ /* Do not tolerate partial BOM. */
+ if (bomptr != utf8_bom)
+ break;
+ /* No BOM at file beginning. Cool. */
+ bomptr = NULL;
+ }
+ }
if (c == '\n') {
if (config_file_eof)
return 0;
@@ -255,7 +274,7 @@ static int parse_unit_factor(const char *end, unsigned long *val)
return 0;
}
-int git_parse_long(const char *value, long *ret)
+static int git_parse_long(const char *value, long *ret)
{
if (value && *value) {
char *end;
@@ -291,7 +310,7 @@ static void die_bad_config(const char *name)
int git_config_int(const char *name, const char *value)
{
- long ret;
+ long ret = 0;
if (!git_parse_long(value, &ret))
die_bad_config(name);
return ret;
@@ -734,9 +753,8 @@ static int store_write_section(int fd, const char* key)
{
const char *dot;
int i, success;
- struct strbuf sb;
+ struct strbuf sb = STRBUF_INIT;
- strbuf_init(&sb, 0);
dot = memchr(key, '.', store.baselen);
if (dot) {
strbuf_addf(&sb, "[%.*s \"", (int)(dot - key), key);
@@ -761,7 +779,7 @@ static int store_write_pair(int fd, const char* key, const char* value)
int i, success;
int length = strlen(key + store.baselen + 1);
const char *quote = "";
- struct strbuf sb;
+ struct strbuf sb = STRBUF_INIT;
/*
* Check to see if the value needs to be surrounded with a dq pair.
@@ -778,7 +796,6 @@ static int store_write_pair(int fd, const char* key, const char* value)
if (i && value[i - 1] == ' ')
quote = "\"";
- strbuf_init(&sb, 0);
strbuf_addf(&sb, "\t%.*s = %s",
length, key + store.baselen + 1, quote);
diff --git a/config.mak.in b/config.mak.in
index b776149..17e9861 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -3,6 +3,8 @@
CC = @CC@
CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+CC_LD_DYNPATH = @CC_LD_DYNPATH@
AR = @AR@
TAR = @TAR@
#INSTALL = @INSTALL@ # needs install-sh or install.sh in sources
diff --git a/configure.ac b/configure.ac
index 7c2856e..27bab00 100644
--- a/configure.ac
+++ b/configure.ac
@@ -103,6 +103,38 @@ GIT_PARSE_WITH(tcltk))
AC_MSG_NOTICE([CHECKS for programs])
#
AC_PROG_CC([cc gcc])
+# which switch to pass runtime path to dynamic libraries to the linker
+AC_CACHE_CHECK([if linker supports -R], ld_dashr, [
+ SAVE_LDFLAGS="${LDFLAGS}"
+ LDFLAGS="${SAVE_LDFLAGS} -R /"
+ AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_dashr=yes], [ld_dashr=no])
+ LDFLAGS="${SAVE_LDFLAGS}"
+])
+if test "$ld_dashr" = "yes"; then
+ AC_SUBST(CC_LD_DYNPATH, [-R])
+else
+ AC_CACHE_CHECK([if linker supports -Wl,-rpath,], ld_wl_rpath, [
+ SAVE_LDFLAGS="${LDFLAGS}"
+ LDFLAGS="${SAVE_LDFLAGS} -Wl,-rpath,/"
+ AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_wl_rpath=yes], [ld_wl_rpath=no])
+ LDFLAGS="${SAVE_LD_FLAGS}"
+ ])
+ if test "$ld_wl_rpath" = "yes"; then
+ AC_SUBST(CC_LD_DYNPATH, [-Wl,-rpath,])
+ else
+ AC_CACHE_CHECK([if linker supports -rpath], ld_rpath, [
+ SAVE_LDFLAGS="${LDFLAGS}"
+ LDFLAGS="${SAVE_LDFLAGS} -rpath /"
+ AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_rpath=yes], [ld_rpath=no])
+ LDFLAGS="${SAVE_LD_FLAGS}"
+ ])
+ if test "$ld_rpath" = "yes"; then
+ AC_SUBST(CC_LD_DYNPATH, [-rpath])
+ else
+ AC_MSG_WARN([linker does not support runtime path to dynamic libraries])
+ fi
+ fi
+fi
#AC_PROG_INSTALL # needs install-sh or install.sh in sources
AC_CHECK_TOOLS(AR, [gar ar], :)
AC_CHECK_PROGS(TAR, [gtar tar])
diff --git a/connect.c b/connect.c
index dd96f8e..0c50d0a 100644
--- a/connect.c
+++ b/connect.c
@@ -41,12 +41,20 @@ int check_ref_type(const struct ref *ref, int flags)
return check_ref(ref->name, strlen(ref->name), flags);
}
+static void add_extra_have(struct extra_have_objects *extra, unsigned char *sha1)
+{
+ ALLOC_GROW(extra->array, extra->nr + 1, extra->alloc);
+ hashcpy(&(extra->array[extra->nr][0]), sha1);
+ extra->nr++;
+}
+
/*
* 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)
+ unsigned int flags,
+ struct extra_have_objects *extra_have)
{
*list = NULL;
for (;;) {
@@ -72,13 +80,18 @@ struct ref **get_remote_heads(int in, struct ref **list,
server_capabilities = xstrdup(name + name_len + 1);
}
+ if (extra_have &&
+ name_len == 5 && !memcmp(".have", name, 5)) {
+ add_extra_have(extra_have, old_sha1);
+ continue;
+ }
+
if (!check_ref(name, name_len, flags))
continue;
if (nr_match && !path_match(name, nr_match, match))
continue;
- ref = alloc_ref(name_len + 1);
+ ref = alloc_ref(buffer + 41);
hashcpy(ref->old_sha1, old_sha1);
- memcpy(ref->name, buffer + 41, name_len + 1);
*list = ref;
list = &ref->next;
}
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 751e273..eebe734 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -154,11 +154,8 @@ __git_heads ()
{
local cmd i is_hash=y dir="$(__gitdir "$1")"
if [ -d "$dir" ]; then
- for i in $(git --git-dir="$dir" \
- for-each-ref --format='%(refname)' \
- refs/heads ); do
- echo "${i#refs/heads/}"
- done
+ 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
@@ -175,11 +172,8 @@ __git_tags ()
{
local cmd i is_hash=y dir="$(__gitdir "$1")"
if [ -d "$dir" ]; then
- for i in $(git --git-dir="$dir" \
- for-each-ref --format='%(refname)' \
- refs/tags ); do
- echo "${i#refs/tags/}"
- done
+ 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
@@ -197,16 +191,8 @@ __git_refs ()
local cmd i is_hash=y dir="$(__gitdir "$1")"
if [ -d "$dir" ]; then
if [ -e "$dir/HEAD" ]; then echo HEAD; fi
- for i in $(git --git-dir="$dir" \
- for-each-ref --format='%(refname)' \
- refs/tags refs/heads refs/remotes); do
- case "$i" in
- refs/tags/*) echo "${i#refs/tags/}" ;;
- refs/heads/*) echo "${i#refs/heads/}" ;;
- refs/remotes/*) echo "${i#refs/remotes/}" ;;
- *) echo "$i" ;;
- esac
- done
+ git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
+ refs/tags refs/heads refs/remotes
return
fi
for i in $(git ls-remote "$dir" 2>/dev/null); do
@@ -895,6 +881,7 @@ _git_help ()
attributes cli core-tutorial cvs-migration
diffcore gitk glossary hooks ignore modules
repository-layout tutorial tutorial-2
+ workflows
"
}
@@ -1137,7 +1124,8 @@ _git_send_email ()
--no-suppress-from --no-thread --quiet
--signed-off-by-cc --smtp-pass --smtp-server
--smtp-server-port --smtp-ssl --smtp-user --subject
- --suppress-cc --suppress-from --thread --to"
+ --suppress-cc --suppress-from --thread --to
+ --validate --no-validate"
return
;;
esac
@@ -1475,7 +1463,7 @@ _git_submodule ()
{
__git_has_doubledash && return
- local subcommands="add status init update"
+ local subcommands="add status init update summary foreach sync"
if [ -z "$(__git_find_subcommand "$subcommands")" ]; then
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
index 46136d4..2216cac 100755
--- a/contrib/fast-import/git-p4
+++ b/contrib/fast-import/git-p4
@@ -708,6 +708,7 @@ class P4Submit(Command):
newdiff = newdiff.replace("\n", "\r\n")
tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
tmpFile.close()
+ mtime = os.stat(fileName).st_mtime
defaultEditor = "vi"
if platform.system() == "Windows":
defaultEditor = "notepad"
@@ -716,15 +717,29 @@ class P4Submit(Command):
else:
editor = os.environ.get("EDITOR", defaultEditor);
system(editor + " " + fileName)
- tmpFile = open(fileName, "rb")
- message = tmpFile.read()
- tmpFile.close()
- os.remove(fileName)
- submitTemplate = message[:message.index(separatorLine)]
- if self.isWindows:
- submitTemplate = submitTemplate.replace("\r\n", "\n")
- p4_write_pipe("submit -i", submitTemplate)
+ response = "y"
+ if 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":
+ tmpFile = open(fileName, "rb")
+ message = tmpFile.read()
+ tmpFile.close()
+ submitTemplate = message[:message.index(separatorLine)]
+ if self.isWindows:
+ submitTemplate = submitTemplate.replace("\r\n", "\n")
+ p4_write_pipe("submit -i", submitTemplate)
+ else:
+ for f in editedFiles:
+ p4_system("revert \"%s\"" % f);
+ for f in filesToAdd:
+ p4_system("revert \"%s\"" % f);
+ system("rm %s" %f)
+
+ os.remove(fileName)
else:
fileName = "submit.txt"
file = open(fileName, "w+")
@@ -1733,8 +1748,12 @@ class P4Clone(P4Sync):
if not P4Sync.run(self, depotPaths):
return False
if self.branch != "master":
- if gitBranchExists("refs/remotes/p4/master"):
- system("git branch master refs/remotes/p4/master")
+ if self.importIntoRemotes:
+ masterbranch = "refs/remotes/p4/master"
+ else:
+ masterbranch = "refs/heads/p4/master"
+ if gitBranchExists(masterbranch):
+ system("git branch master %s" % masterbranch)
system("git checkout -f")
else:
print "Could not detect main branch. No checkout/master branch created."
diff --git a/contrib/hooks/pre-auto-gc-battery b/contrib/hooks/pre-auto-gc-battery
index 0096f57..1f914c9 100644
--- a/contrib/hooks/pre-auto-gc-battery
+++ b/contrib/hooks/pre-auto-gc-battery
@@ -1,9 +1,9 @@
#!/bin/sh
#
# An example hook script to verify if you are on battery, in case you
-# are running Linux. Called by git-gc --auto with no arguments. The hook
-# should exit with non-zero status after issuing an appropriate message
-# if it wants to stop the auto repacking.
+# are running Linux or OS X. Called by git-gc --auto with no arguments.
+# The hook should exit with non-zero status after issuing an appropriate
+# message if it wants to stop the auto repacking.
#
# This hook is stored in the contrib/hooks directory. Your distribution
# may have put this somewhere else. If you want to use this hook, you
@@ -30,6 +30,13 @@ then
elif grep -q '0x01$' /proc/apm 2>/dev/null
then
exit 0
+elif grep -q "AC Power \+: 1" /proc/pmu/info 2>/dev/null
+then
+ exit 0
+elif test -x /usr/bin/pmset && /usr/bin/pmset -g batt |
+ grep -q "Currently drawing from 'AC Power'"
+then
+ exit 0
fi
echo "Auto packing deferred; not on AC"
diff --git a/contrib/rerere-train.sh b/contrib/rerere-train.sh
new file mode 100755
index 0000000..2cfe1b9
--- /dev/null
+++ b/contrib/rerere-train.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+# Copyright (c) 2008, Nanako Shiraishi
+# Prime rerere database from existing merge commits
+
+me=rerere-train
+USAGE="$me rev-list-args"
+
+SUBDIRECTORY_OK=Yes
+OPTIONS_SPEC=
+. git-sh-setup
+require_work_tree
+cd_to_toplevel
+
+# Remember original branch
+branch=$(git symbolic-ref -q HEAD) ||
+original_HEAD=$(git rev-parse --verify HEAD) || {
+ echo >&2 "Not on any branch and no commit yet?"
+ exit 1
+}
+
+mkdir -p "$GIT_DIR/rr-cache" || exit
+
+git rev-list --parents "$@" |
+while read commit parent1 other_parents
+do
+ if test -z "$other_parents"
+ then
+ # Skip non-merges
+ continue
+ fi
+ git checkout -q "$parent1^0"
+ if git merge $other_parents >/dev/null 2>&1
+ then
+ # Cleanly merges
+ continue
+ fi
+ if test -s "$GIT_DIR/MERGE_RR"
+ then
+ git show -s --pretty=format:"Learning from %h %s" "$commit"
+ git rerere
+ git checkout -q $commit -- .
+ git rerere
+ fi
+ git reset -q --hard
+done
+
+if test -z "$branch"
+then
+ git checkout "$original_HEAD"
+else
+ git checkout "${branch#refs/heads/}"
+fi
diff --git a/contrib/vim/README b/contrib/vim/README
index 9e7881f..c487346 100644
--- a/contrib/vim/README
+++ b/contrib/vim/README
@@ -1,8 +1,30 @@
-To syntax highlight git's commit messages, you need to:
- 1. Copy syntax/gitcommit.vim to vim's syntax directory:
- $ mkdir -p $HOME/.vim/syntax
- $ cp syntax/gitcommit.vim $HOME/.vim/syntax
- 2. Auto-detect the editing of git commit files:
- $ cat >>$HOME/.vimrc <<'EOF'
- autocmd BufNewFile,BufRead COMMIT_EDITMSG set filetype=gitcommit
- EOF
+Syntax highlighting for git commit messages, config files, etc. is
+included with the vim distribution as of vim 7.2, and should work
+automatically.
+
+If you have an older version of vim, you can get the latest syntax
+files from the vim project:
+
+ http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/git.vim
+ http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitcommit.vim
+ http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitconfig.vim
+ http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitrebase.vim
+ http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitsendemail.vim
+
+To install:
+
+ 1. Copy these files to vim's syntax directory $HOME/.vim/syntax
+ 2. To auto-detect the editing of various git-related filetypes:
+ $ cat >>$HOME/.vim/filetype.vim <<'EOF'
+ autocmd BufNewFile,BufRead *.git/COMMIT_EDITMSG setf gitcommit
+ autocmd BufNewFile,BufRead *.git/config,.gitconfig setf gitconfig
+ autocmd BufNewFile,BufRead git-rebase-todo setf gitrebase
+ autocmd BufNewFile,BufRead .msg.[0-9]*
+ \ if getline(1) =~ '^From.*# This line is ignored.$' |
+ \ setf gitsendemail |
+ \ endif
+ autocmd BufNewFile,BufRead *.git/**
+ \ if getline(1) =~ '^\x\{40\}\>\|^ref: ' |
+ \ setf git |
+ \ endif
+ EOF
diff --git a/contrib/vim/syntax/gitcommit.vim b/contrib/vim/syntax/gitcommit.vim
deleted file mode 100644
index 332121b..0000000
--- a/contrib/vim/syntax/gitcommit.vim
+++ /dev/null
@@ -1,18 +0,0 @@
-syn region gitLine start=/^#/ end=/$/
-syn region gitCommit start=/^# Changes to be committed:$/ end=/^#$/ contains=gitHead,gitCommitFile
-syn region gitHead contained start=/^# (.*)/ end=/^#$/
-syn region gitChanged start=/^# Changed but not updated:/ end=/^#$/ contains=gitHead,gitChangedFile
-syn region gitUntracked start=/^# Untracked files:/ end=/^#$/ contains=gitHead,gitUntrackedFile
-
-syn match gitCommitFile contained /^#\t.*/hs=s+2
-syn match gitChangedFile contained /^#\t.*/hs=s+2
-syn match gitUntrackedFile contained /^#\t.*/hs=s+2
-
-hi def link gitLine Comment
-hi def link gitCommit Comment
-hi def link gitChanged Comment
-hi def link gitHead Comment
-hi def link gitUntracked Comment
-hi def link gitCommitFile Type
-hi def link gitChangedFile Constant
-hi def link gitUntrackedFile Constant
diff --git a/convert.c b/convert.c
index 78efed8..1816e97 100644
--- a/convert.c
+++ b/convert.c
@@ -281,7 +281,7 @@ static int apply_filter(const char *path, const char *src, size_t len,
* (child --> cmd) --> us
*/
int ret = 1;
- struct strbuf nbuf;
+ struct strbuf nbuf = STRBUF_INIT;
struct async async;
struct filter_params params;
@@ -299,7 +299,6 @@ static int apply_filter(const char *path, const char *src, size_t len,
if (start_async(&async))
return 0; /* error was already reported */
- strbuf_init(&nbuf, 0);
if (strbuf_read(&nbuf, async.out, len) < 0) {
error("read from external filter %s failed", cmd);
ret = 0;
diff --git a/csum-file.c b/csum-file.c
index cfc1ac4..2ddb12a 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -11,10 +11,8 @@
#include "progress.h"
#include "csum-file.h"
-static void flush(struct sha1file *f, unsigned int count)
+static void flush(struct sha1file *f, void * buf, unsigned int count)
{
- void *buf = f->buffer;
-
for (;;) {
int ret = xwrite(f->fd, buf, count);
if (ret > 0) {
@@ -37,8 +35,8 @@ void sha1flush(struct sha1file *f)
unsigned offset = f->offset;
if (offset) {
- SHA1_Update(&f->ctx, f->buffer, offset);
- flush(f, offset);
+ git_SHA1_Update(&f->ctx, f->buffer, offset);
+ flush(f, f->buffer, offset);
f->offset = 0;
}
}
@@ -48,12 +46,12 @@ int sha1close(struct sha1file *f, unsigned char *result, unsigned int flags)
int fd;
sha1flush(f);
- SHA1_Final(f->buffer, &f->ctx);
+ git_SHA1_Final(f->buffer, &f->ctx);
if (result)
hashcpy(result, f->buffer);
if (flags & (CSUM_CLOSE | CSUM_FSYNC)) {
/* write checksum and close fd */
- flush(f, 20);
+ flush(f, f->buffer, 20);
if (flags & CSUM_FSYNC)
fsync_or_die(f->fd, f->name);
if (close(f->fd))
@@ -68,21 +66,30 @@ int sha1close(struct sha1file *f, unsigned char *result, unsigned int flags)
int sha1write(struct sha1file *f, void *buf, unsigned int count)
{
- if (f->do_crc)
- f->crc32 = crc32(f->crc32, buf, count);
while (count) {
unsigned offset = f->offset;
unsigned left = sizeof(f->buffer) - offset;
unsigned nr = count > left ? left : count;
+ void *data;
+
+ if (f->do_crc)
+ f->crc32 = crc32(f->crc32, buf, nr);
+
+ if (nr == sizeof(f->buffer)) {
+ /* process full buffer directly without copy */
+ data = buf;
+ } else {
+ memcpy(f->buffer + offset, buf, nr);
+ data = f->buffer;
+ }
- memcpy(f->buffer + offset, buf, nr);
count -= nr;
offset += nr;
buf = (char *) buf + nr;
left -= nr;
if (!left) {
- SHA1_Update(&f->ctx, f->buffer, offset);
- flush(f, offset);
+ git_SHA1_Update(&f->ctx, data, offset);
+ flush(f, data, offset);
offset = 0;
}
f->offset = offset;
@@ -104,7 +111,7 @@ struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp
f->tp = tp;
f->name = name;
f->do_crc = 0;
- SHA1_Init(&f->ctx);
+ git_SHA1_Init(&f->ctx);
return f;
}
diff --git a/csum-file.h b/csum-file.h
index 01f13b5..294add2 100644
--- a/csum-file.h
+++ b/csum-file.h
@@ -7,7 +7,7 @@ struct progress;
struct sha1file {
int fd;
unsigned int offset;
- SHA_CTX ctx;
+ git_SHA_CTX ctx;
off_t total;
struct progress *tp;
const char *name;
diff --git a/ctype.c b/ctype.c
index d2bd38e..9208d67 100644
--- a/ctype.c
+++ b/ctype.c
@@ -9,18 +9,20 @@
#undef SS
#undef AA
#undef DD
+#undef GS
#define SS GIT_SPACE
#define AA GIT_ALPHA
#define DD GIT_DIGIT
+#define GS GIT_SPECIAL /* \0, *, ?, [, \\ */
unsigned char sane_ctype[256] = {
- 0, 0, 0, 0, 0, 0, 0, 0, 0, SS, SS, 0, 0, SS, 0, 0, /* 0-15 */
+ GS, 0, 0, 0, 0, 0, 0, 0, 0, SS, SS, 0, 0, SS, 0, 0, /* 0-15 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-15 */
- SS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 32-15 */
- DD, DD, DD, DD, DD, DD, DD, DD, DD, DD, 0, 0, 0, 0, 0, 0, /* 48-15 */
+ SS, 0, 0, 0, 0, 0, 0, 0, 0, 0, GS, 0, 0, 0, 0, 0, /* 32-15 */
+ DD, DD, DD, DD, DD, DD, DD, DD, DD, DD, 0, 0, 0, 0, 0, GS, /* 48-15 */
0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 64-15 */
- AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, 0, 0, 0, 0, 0, /* 80-15 */
+ AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, GS, GS, 0, 0, 0, /* 80-15 */
0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 96-15 */
AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, 0, 0, 0, 0, 0, /* 112-15 */
/* Nothing in the 128.. range */
diff --git a/daemon.c b/daemon.c
index 8dcde73..b9ba44c 100644
--- a/daemon.c
+++ b/daemon.c
@@ -16,12 +16,11 @@
static int log_syslog;
static int verbose;
static int reuseaddr;
-static int child_handler_pipe[2];
static const char daemon_usage[] =
"git daemon [--verbose] [--syslog] [--export-all]\n"
-" [--timeout=n] [--init-timeout=n] [--strict-paths]\n"
-" [--base-path=path] [--base-path-relaxed]\n"
+" [--timeout=n] [--init-timeout=n] [--max-connections=n]\n"
+" [--strict-paths] [--base-path=path] [--base-path-relaxed]\n"
" [--user-path | --user-path=path]\n"
" [--interpolated-path=path]\n"
" [--reuseaddr] [--detach] [--pid-file=file]\n"
@@ -78,38 +77,19 @@ static struct interp interp_table[] = {
static void logreport(int priority, const char *err, va_list params)
{
- /* We should do a single write so that it is atomic and output
- * of several processes do not get intermingled. */
- char buf[1024];
- int buflen;
- int maxlen, msglen;
-
- /* sizeof(buf) should be big enough for "[pid] \n" */
- buflen = snprintf(buf, sizeof(buf), "[%ld] ", (long) getpid());
-
- maxlen = sizeof(buf) - buflen - 1; /* -1 for our own LF */
- msglen = vsnprintf(buf + buflen, maxlen, err, params);
-
if (log_syslog) {
+ char buf[1024];
+ vsnprintf(buf, sizeof(buf), err, params);
syslog(priority, "%s", buf);
- return;
+ } else {
+ /*
+ * Since stderr is set to linebuffered mode, the
+ * logging of different processes will not overlap
+ */
+ fprintf(stderr, "[%"PRIuMAX"] ", (uintmax_t)getpid());
+ vfprintf(stderr, err, params);
+ fputc('\n', stderr);
}
-
- /* maxlen counted our own LF but also counts space given to
- * vsnprintf for the terminating NUL. We want to make sure that
- * we have space for our own LF and NUL after the "meat" of the
- * message, so truncate it at maxlen - 1.
- */
- if (msglen > maxlen - 1)
- msglen = maxlen - 1;
- else if (msglen < 0)
- msglen = 0; /* Protect against weird return values. */
- buflen += msglen;
-
- buf[buflen++] = '\n';
- buf[buflen] = '\0';
-
- write_in_full(2, buf, buflen);
}
static void logerror(const char *err, ...)
@@ -557,6 +537,10 @@ static int execute(struct sockaddr *addr)
#endif
}
loginfo("Connection from %s:%d", addrbuf, port);
+ setenv("REMOTE_ADDR", addrbuf, 1);
+ }
+ else {
+ unsetenv("REMOTE_ADDR");
}
alarm(init_timeout ? init_timeout : timeout);
@@ -604,169 +588,107 @@ static int execute(struct sockaddr *addr)
return -1;
}
+static int max_connections = 32;
-/*
- * We count spawned/reaped separately, just to avoid any
- * races when updating them from signals. The SIGCHLD handler
- * will only update children_reaped, and the fork logic will
- * only update children_spawned.
- *
- * MAX_CHILDREN should be a power-of-two to make the modulus
- * operation cheap. It should also be at least twice
- * the maximum number of connections we will ever allow.
- */
-#define MAX_CHILDREN 128
-
-static int max_connections = 25;
-
-/* These are updated by the signal handler */
-static volatile unsigned int children_reaped;
-static pid_t dead_child[MAX_CHILDREN];
-
-/* These are updated by the main loop */
-static unsigned int children_spawned;
-static unsigned int children_deleted;
+static unsigned int live_children;
static struct child {
+ struct child *next;
pid_t pid;
- int addrlen;
struct sockaddr_storage address;
-} live_child[MAX_CHILDREN];
+} *firstborn;
-static void add_child(int idx, pid_t pid, struct sockaddr *addr, int addrlen)
+static void add_child(pid_t pid, struct sockaddr *addr, int addrlen)
{
- live_child[idx].pid = pid;
- live_child[idx].addrlen = addrlen;
- memcpy(&live_child[idx].address, addr, addrlen);
+ struct child *newborn, **cradle;
+
+ /*
+ * This must be xcalloc() -- we'll compare the whole sockaddr_storage
+ * but individual address may be shorter.
+ */
+ newborn = xcalloc(1, sizeof(*newborn));
+ live_children++;
+ newborn->pid = pid;
+ memcpy(&newborn->address, addr, addrlen);
+ for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next)
+ if (!memcmp(&(*cradle)->address, &newborn->address,
+ sizeof(newborn->address)))
+ break;
+ newborn->next = *cradle;
+ *cradle = newborn;
}
-/*
- * Walk from "deleted" to "spawned", and remove child "pid".
- *
- * We move everything up by one, since the new "deleted" will
- * be one higher.
- */
-static void remove_child(pid_t pid, unsigned deleted, unsigned spawned)
+static void remove_child(pid_t pid)
{
- struct child n;
+ struct child **cradle, *blanket;
- deleted %= MAX_CHILDREN;
- spawned %= MAX_CHILDREN;
- if (live_child[deleted].pid == pid) {
- live_child[deleted].pid = -1;
- return;
- }
- n = live_child[deleted];
- for (;;) {
- struct child m;
- deleted = (deleted + 1) % MAX_CHILDREN;
- if (deleted == spawned)
- die("could not find dead child %d\n", pid);
- m = live_child[deleted];
- live_child[deleted] = n;
- if (m.pid == pid)
- return;
- n = m;
- }
+ for (cradle = &firstborn; (blanket = *cradle); cradle = &blanket->next)
+ if (blanket->pid == pid) {
+ *cradle = blanket->next;
+ live_children--;
+ free(blanket);
+ break;
+ }
}
/*
* This gets called if the number of connections grows
* past "max_connections".
*
- * We _should_ start off by searching for connections
- * from the same IP, and if there is some address wth
- * multiple connections, we should kill that first.
- *
- * As it is, we just "randomly" kill 25% of the connections,
- * and our pseudo-random generator sucks too. I have no
- * shame.
- *
- * Really, this is just a place-holder for a _real_ algorithm.
+ * We kill the newest connection from a duplicate IP.
*/
-static void kill_some_children(int signo, unsigned start, unsigned stop)
-{
- start %= MAX_CHILDREN;
- stop %= MAX_CHILDREN;
- while (start != stop) {
- if (!(start & 3))
- kill(live_child[start].pid, signo);
- start = (start + 1) % MAX_CHILDREN;
- }
-}
-
-static void check_dead_children(void)
+static void kill_some_child(void)
{
- unsigned spawned, reaped, deleted;
-
- spawned = children_spawned;
- reaped = children_reaped;
- deleted = children_deleted;
+ const struct child *blanket, *next;
- while (deleted < reaped) {
- pid_t pid = dead_child[deleted % MAX_CHILDREN];
- const char *dead = pid < 0 ? " (with error)" : "";
-
- if (pid < 0)
- pid = -pid;
+ if (!(blanket = firstborn))
+ return;
- /* XXX: Custom logging, since we don't wanna getpid() */
- if (verbose) {
- if (log_syslog)
- syslog(LOG_INFO, "[%d] Disconnected%s",
- pid, dead);
- else
- fprintf(stderr, "[%d] Disconnected%s\n",
- pid, dead);
+ for (; (next = blanket->next); blanket = next)
+ if (!memcmp(&blanket->address, &next->address,
+ sizeof(next->address))) {
+ kill(blanket->pid, SIGTERM);
+ break;
}
- remove_child(pid, deleted, spawned);
- deleted++;
- }
- children_deleted = deleted;
}
-static void check_max_connections(void)
+static void check_dead_children(void)
{
- for (;;) {
- int active;
- unsigned spawned, deleted;
-
- check_dead_children();
-
- spawned = children_spawned;
- deleted = children_deleted;
-
- active = spawned - deleted;
- if (active <= max_connections)
- break;
-
- /* Kill some unstarted connections with SIGTERM */
- kill_some_children(SIGTERM, deleted, spawned);
- if (active <= max_connections << 1)
- break;
+ int status;
+ pid_t pid;
- /* If the SIGTERM thing isn't helping use SIGKILL */
- kill_some_children(SIGKILL, deleted, spawned);
- sleep(1);
+ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+ const char *dead = "";
+ remove_child(pid);
+ if (!WIFEXITED(status) || (WEXITSTATUS(status) > 0))
+ dead = " (with error)";
+ loginfo("[%"PRIuMAX"] Disconnected%s", (uintmax_t)pid, dead);
}
}
static void handle(int incoming, struct sockaddr *addr, int addrlen)
{
- pid_t pid = fork();
+ pid_t pid;
- if (pid) {
- unsigned idx;
+ if (max_connections && live_children >= max_connections) {
+ kill_some_child();
+ sleep(1); /* give it some time to die */
+ check_dead_children();
+ if (live_children >= max_connections) {
+ close(incoming);
+ logerror("Too many children, dropping connection");
+ return;
+ }
+ }
+ if ((pid = fork())) {
close(incoming);
- if (pid < 0)
+ if (pid < 0) {
+ logerror("Couldn't fork %s", strerror(errno));
return;
+ }
- idx = children_spawned % MAX_CHILDREN;
- children_spawned++;
- add_child(idx, pid, addr, addrlen);
-
- check_max_connections();
+ add_child(pid, addr, addrlen);
return;
}
@@ -779,21 +701,11 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen)
static void child_handler(int signo)
{
- for (;;) {
- int status;
- pid_t pid = waitpid(-1, &status, WNOHANG);
-
- if (pid > 0) {
- unsigned reaped = children_reaped;
- if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
- pid = -pid;
- dead_child[reaped % MAX_CHILDREN] = pid;
- children_reaped = reaped + 1;
- write(child_handler_pipe[1], &status, 1);
- continue;
- }
- break;
- }
+ /*
+ * Otherwise empty handler because systemcalls will get interrupted
+ * upon signal receipt
+ * SysV needs the handler to be rearmed
+ */
signal(SIGCHLD, child_handler);
}
@@ -836,7 +748,7 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
if (sockfd < 0)
continue;
if (sockfd >= FD_SETSIZE) {
- error("too large socket descriptor.");
+ logerror("Socket descriptor too large");
close(sockfd);
continue;
}
@@ -936,35 +848,28 @@ static int service_loop(int socknum, int *socklist)
struct pollfd *pfd;
int i;
- if (pipe(child_handler_pipe) < 0)
- die ("Could not set up pipe for child handler");
-
- pfd = xcalloc(socknum + 1, sizeof(struct pollfd));
+ pfd = xcalloc(socknum, sizeof(struct pollfd));
for (i = 0; i < socknum; i++) {
pfd[i].fd = socklist[i];
pfd[i].events = POLLIN;
}
- pfd[socknum].fd = child_handler_pipe[0];
- pfd[socknum].events = POLLIN;
signal(SIGCHLD, child_handler);
for (;;) {
int i;
- if (poll(pfd, socknum + 1, -1) < 0) {
+ check_dead_children();
+
+ if (poll(pfd, socknum, -1) < 0) {
if (errno != EINTR) {
- error("poll failed, resuming: %s",
+ logerror("Poll failed, resuming: %s",
strerror(errno));
sleep(1);
}
continue;
}
- if (pfd[socknum].revents & POLLIN) {
- read(child_handler_pipe[0], &i, 1);
- check_dead_children();
- }
for (i = 0; i < socknum; i++) {
if (pfd[i].revents & POLLIN) {
@@ -1022,7 +927,7 @@ static void store_pid(const char *path)
FILE *f = fopen(path, "w");
if (!f)
die("cannot open pid file %s: %s", path, strerror(errno));
- if (fprintf(f, "%d\n", getpid()) < 0 || fclose(f) != 0)
+ if (fprintf(f, "%"PRIuMAX"\n", (uintmax_t) getpid()) < 0 || fclose(f) != 0)
die("failed to write pid file %s: %s", path, strerror(errno));
}
@@ -1055,11 +960,6 @@ int main(int argc, char **argv)
gid_t gid = 0;
int i;
- /* Without this we cannot rely on waitpid() to tell
- * what happened to our children.
- */
- signal(SIGCHLD, SIG_DFL);
-
for (i = 1; i < argc; i++) {
char *arg = argv[i];
@@ -1105,6 +1005,12 @@ int main(int argc, char **argv)
init_timeout = atoi(arg+15);
continue;
}
+ if (!prefixcmp(arg, "--max-connections=")) {
+ max_connections = atoi(arg+18);
+ if (max_connections < 0)
+ max_connections = 0; /* unlimited */
+ continue;
+ }
if (!strcmp(arg, "--strict-paths")) {
strict_paths = 1;
continue;
@@ -1178,9 +1084,11 @@ int main(int argc, char **argv)
}
if (log_syslog) {
- openlog("git-daemon", 0, LOG_DAEMON);
+ openlog("git-daemon", LOG_PID, LOG_DAEMON);
set_die_routine(daemon_die);
- }
+ } else
+ /* avoid splitting a message in the middle */
+ setvbuf(stderr, NULL, _IOLBF, 0);
if (inetd_mode && (group_name || user_name))
die("--user and --group are incompatible with --inetd");
@@ -1212,13 +1120,9 @@ int main(int argc, char **argv)
if (strict_paths && (!ok_paths || !*ok_paths))
die("option --strict-paths requires a whitelist");
- if (base_path) {
- struct stat st;
-
- if (stat(base_path, &st) || !S_ISDIR(st.st_mode))
- die("base-path '%s' does not exist or "
- "is not a directory", base_path);
- }
+ if (base_path && !is_directory(base_path))
+ die("base-path '%s' does not exist or is not a directory",
+ base_path);
if (inetd_mode) {
struct sockaddr_storage ss;
@@ -1233,8 +1137,10 @@ int main(int argc, char **argv)
return execute(peer);
}
- if (detach)
+ if (detach) {
daemonize();
+ loginfo("Ready to rumble");
+ }
else
sanitize_stdfds();
diff --git a/date.c b/date.c
index 35a5257..950b88f 100644
--- a/date.c
+++ b/date.c
@@ -402,6 +402,15 @@ static int match_multi_number(unsigned long num, char c, const char *date, char
return end - date;
}
+/* Have we filled in any part of the time/date yet? */
+static inline int nodate(struct tm *tm)
+{
+ return tm->tm_year < 0 &&
+ tm->tm_mon < 0 &&
+ tm->tm_mday < 0 &&
+ !(tm->tm_hour | tm->tm_min | tm->tm_sec);
+}
+
/*
* We've seen a digit. Time? Year? Date?
*/
@@ -418,7 +427,7 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
* more than 8 digits. This is because we don't want to rule out
* numbers like 20070606 as a YYYYMMDD date.
*/
- if (num >= 100000000) {
+ if (num >= 100000000 && nodate(tm)) {
time_t time = num;
if (gmtime_r(&time, tm)) {
*tm_gmt = 1;
@@ -463,6 +472,13 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
}
/*
+ * Ignore lots of numerals. We took care of 4-digit years above.
+ * Days or months must be one or two digits.
+ */
+ if (n > 2)
+ return n;
+
+ /*
* NOTE! We will give precedence to day-of-month over month or
* year numbers in the 1-12 range. So 05 is always "mday 5",
* unless we already have a mday..
@@ -488,10 +504,6 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
if (num > 0 && num < 32) {
tm->tm_mday = num;
- } else if (num > 1900) {
- tm->tm_year = num - 1900;
- } else if (num > 70) {
- tm->tm_year = num;
} else if (num > 0 && num < 13) {
tm->tm_mon = num-1;
}
@@ -823,7 +835,9 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
}
}
- *num = number;
+ /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */
+ if (date[0] != '0' || end - date <= 2)
+ *num = number;
return end;
}
diff --git a/diff-lib.c b/diff-lib.c
index e7eaff9..ae96c64 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -63,6 +63,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
? CE_MATCH_RACY_IS_DIRTY : 0);
char symcache[PATH_MAX];
+ diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/");
+
if (diff_unmerged_stage < 0)
diff_unmerged_stage = 2;
entries = active_nr;
@@ -469,6 +471,7 @@ int run_diff_index(struct rev_info *revs, int cached)
if (unpack_trees(1, &t, &opts))
exit(128);
+ diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
return 0;
diff --git a/diff-no-index.c b/diff-no-index.c
index 7d68b7f..b60d345 100644
--- a/diff-no-index.c
+++ b/diff-no-index.c
@@ -252,6 +252,7 @@ void diff_no_index(struct rev_info *revs,
if (queue_diff(&revs->diffopt, revs->diffopt.paths[0],
revs->diffopt.paths[1]))
exit(1);
+ diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/");
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
diff --git a/diff.c b/diff.c
index f91f256..e368fef 100644
--- a/diff.c
+++ b/diff.c
@@ -11,6 +11,7 @@
#include "attr.h"
#include "run-command.h"
#include "utf8.h"
+#include "userdiff.h"
#ifdef NO_FAST_WORKING_DIRECTORY
#define FAST_WORKING_DIRECTORY 0
@@ -20,9 +21,11 @@
static int diff_detect_rename_default;
static int diff_rename_limit_default = 200;
+static int diff_suppress_blank_empty;
int diff_use_color_default = -1;
static const char *external_diff_cmd_cfg;
int diff_auto_refresh_index = 1;
+static int diff_mnemonic_prefix;
static char diff_colors[][COLOR_MAXLEN] = {
"\033[m", /* reset */
@@ -35,6 +38,9 @@ static char diff_colors[][COLOR_MAXLEN] = {
"\033[41m", /* WHITESPACE (red background) */
};
+static void diff_filespec_load_driver(struct diff_filespec *one);
+static char *run_textconv(const char *, struct diff_filespec *, size_t *);
+
static int parse_diff_color_slot(const char *var, int ofs)
{
if (!strcasecmp(var+ofs, "plain"))
@@ -54,80 +60,6 @@ static int parse_diff_color_slot(const char *var, int ofs)
die("bad config variable '%s'", var);
}
-static struct ll_diff_driver {
- const char *name;
- struct ll_diff_driver *next;
- const char *cmd;
-} *user_diff, **user_diff_tail;
-
-/*
- * Currently there is only "diff.<drivername>.command" variable;
- * because there are "diff.color.<slot>" variables, we are parsing
- * this in a bit convoluted way to allow low level diff driver
- * called "color".
- */
-static int parse_lldiff_command(const char *var, const char *ep, const char *value)
-{
- const char *name;
- int namelen;
- struct ll_diff_driver *drv;
-
- name = var + 5;
- namelen = ep - name;
- for (drv = user_diff; drv; drv = drv->next)
- if (!strncmp(drv->name, name, namelen) && !drv->name[namelen])
- break;
- if (!drv) {
- drv = xcalloc(1, sizeof(struct ll_diff_driver));
- drv->name = xmemdupz(name, namelen);
- if (!user_diff_tail)
- user_diff_tail = &user_diff;
- *user_diff_tail = drv;
- user_diff_tail = &(drv->next);
- }
-
- return git_config_string(&(drv->cmd), var, value);
-}
-
-/*
- * 'diff.<what>.funcname' attribute can be specified in the configuration
- * to define a customized regexp to find the beginning of a function to
- * be used for hunk header lines of "diff -p" style output.
- */
-struct funcname_pattern_entry {
- char *name;
- char *pattern;
- int cflags;
-};
-static struct funcname_pattern_list {
- struct funcname_pattern_list *next;
- struct funcname_pattern_entry e;
-} *funcname_pattern_list;
-
-static int parse_funcname_pattern(const char *var, const char *ep, const char *value, int cflags)
-{
- const char *name;
- int namelen;
- struct funcname_pattern_list *pp;
-
- name = var + 5; /* "diff." */
- namelen = ep - name;
-
- for (pp = funcname_pattern_list; pp; pp = pp->next)
- if (!strncmp(pp->e.name, name, namelen) && !pp->e.name[namelen])
- break;
- if (!pp) {
- pp = xcalloc(1, sizeof(*pp));
- pp->e.name = xmemdupz(name, namelen);
- pp->next = funcname_pattern_list;
- funcname_pattern_list = pp;
- }
- free(pp->e.pattern);
- pp->e.pattern = xstrdup(value);
- pp->e.cflags = cflags;
- return 0;
-}
-
/*
* These are to give UI layer defaults.
* The core-level commands such as git-diff-files should
@@ -154,13 +86,17 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
diff_auto_refresh_index = git_config_bool(var, value);
return 0;
}
+ if (!strcmp(var, "diff.mnemonicprefix")) {
+ diff_mnemonic_prefix = git_config_bool(var, value);
+ return 0;
+ }
if (!strcmp(var, "diff.external"))
return git_config_string(&external_diff_cmd_cfg, var, value);
- if (!prefixcmp(var, "diff.")) {
- const char *ep = strrchr(var, '.');
- if (ep != var + 4 && !strcmp(ep, ".command"))
- return parse_lldiff_command(var, ep, value);
+ switch (userdiff_config_porcelain(var, value)) {
+ case 0: break;
+ case -1: return -1;
+ default: return 0;
}
return git_diff_basic_config(var, value, cb);
@@ -181,21 +117,16 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
return 0;
}
- if (!prefixcmp(var, "diff.")) {
- const char *ep = strrchr(var, '.');
- if (ep != var + 4) {
- if (!strcmp(ep, ".funcname")) {
- if (!value)
- return config_error_nonbool(var);
- return parse_funcname_pattern(var, ep, value,
- 0);
- } else if (!strcmp(ep, ".xfuncname")) {
- if (!value)
- return config_error_nonbool(var);
- return parse_funcname_pattern(var, ep, value,
- REG_EXTENDED);
- }
- }
+ /* like GNU diff's --suppress-blank-empty option */
+ if (!strcmp(var, "diff.suppress-blank-empty")) {
+ diff_suppress_blank_empty = git_config_bool(var, value);
+ return 0;
+ }
+
+ switch (userdiff_config_basic(var, value)) {
+ case 0: break;
+ case -1: return -1;
+ default: return 0;
}
return git_color_default_config(var, value, cb);
@@ -205,9 +136,8 @@ static char *quote_two(const char *one, const char *two)
{
int need_one = quote_c_style(one, NULL, NULL, 1);
int need_two = quote_c_style(two, NULL, NULL, 1);
- struct strbuf res;
+ struct strbuf res = STRBUF_INIT;
- strbuf_init(&res, 0);
if (need_one + need_two) {
strbuf_addch(&res, '"');
quote_c_style(one, &res, NULL, 1);
@@ -316,6 +246,15 @@ static void emit_rewrite_diff(const char *name_a,
const char *new = diff_get_color(color_diff, DIFF_FILE_NEW);
const char *reset = diff_get_color(color_diff, DIFF_RESET);
static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
+ const char *a_prefix, *b_prefix;
+
+ if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) {
+ a_prefix = o->b_prefix;
+ b_prefix = o->a_prefix;
+ } else {
+ a_prefix = o->a_prefix;
+ b_prefix = o->b_prefix;
+ }
name_a += (*name_a == '/');
name_b += (*name_b == '/');
@@ -324,8 +263,8 @@ static void emit_rewrite_diff(const char *name_a,
strbuf_reset(&a_name);
strbuf_reset(&b_name);
- quote_two_c_style(&a_name, o->a_prefix, name_a, 0);
- quote_two_c_style(&b_name, o->b_prefix, name_b, 0);
+ quote_two_c_style(&a_name, a_prefix, name_a, 0);
+ quote_two_c_style(&b_name, b_prefix, name_b, 0);
diff_populate_filespec(one, 0);
diff_populate_filespec(two, 0);
@@ -354,8 +293,19 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
}
else if (diff_populate_filespec(one, 0))
return -1;
- mf->ptr = one->data;
- mf->size = one->size;
+
+ diff_filespec_load_driver(one);
+ if (one->driver->textconv) {
+ size_t size;
+ mf->ptr = run_textconv(one->driver->textconv, one, &size);
+ if (!mf->ptr)
+ return -1;
+ mf->size = size;
+ }
+ else {
+ mf->ptr = one->data;
+ mf->size = one->size;
+ }
return 0;
}
@@ -380,7 +330,6 @@ static void diff_words_append(char *line, unsigned long len,
}
struct diff_words_data {
- struct xdiff_emit_state xm;
struct diff_words_buffer minus, plus;
FILE *file;
};
@@ -470,11 +419,8 @@ static void diff_words_show(struct diff_words_data *diff_words)
xpp.flags = XDF_NEED_MINIMAL;
xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc;
- ecb.outf = xdiff_outf;
- ecb.priv = diff_words;
- diff_words->xm.consume = fn_out_diff_words_aux;
- xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
-
+ xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
+ &xpp, &xecfg, &ecb);
free(minus.ptr);
free(plus.ptr);
diff_words->minus.text.size = diff_words->plus.text.size = 0;
@@ -488,7 +434,6 @@ static void diff_words_show(struct diff_words_data *diff_words)
typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
struct emit_callback {
- struct xdiff_emit_state xm;
int nparents, color_diff;
unsigned ws_rule;
sane_truncate_fn truncate;
@@ -598,6 +543,12 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
}
+ if (diff_suppress_blank_empty
+ && len == 2 && line[0] == ' ' && line[1] == '\n') {
+ line[0] = '\n';
+ len = 1;
+ }
+
/* This is not really necessary for now because
* this codepath only deals with two-way diffs.
*/
@@ -661,7 +612,7 @@ static char *pprint_rename(const char *a, const char *b)
{
const char *old = a;
const char *new = b;
- struct strbuf name;
+ struct strbuf name = STRBUF_INIT;
int pfx_length, sfx_length;
int len_a = strlen(a);
int len_b = strlen(b);
@@ -669,7 +620,6 @@ static char *pprint_rename(const char *a, const char *b)
int qlen_a = quote_c_style(a, NULL, NULL, 0);
int qlen_b = quote_c_style(b, NULL, NULL, 0);
- strbuf_init(&name, 0);
if (qlen_a || qlen_b) {
quote_c_style(a, &name, NULL, 0);
strbuf_addstr(&name, " => ");
@@ -726,8 +676,6 @@ static char *pprint_rename(const char *a, const char *b)
}
struct diffstat_t {
- struct xdiff_emit_state xm;
-
int nr;
int alloc;
struct diffstat_file {
@@ -814,8 +762,7 @@ static void fill_print_name(struct diffstat_file *file)
return;
if (!file->is_renamed) {
- struct strbuf buf;
- strbuf_init(&buf, 0);
+ struct strbuf buf = STRBUF_INIT;
if (quote_c_style(file->name, &buf, NULL, 0)) {
pname = strbuf_detach(&buf, NULL);
} else {
@@ -1122,9 +1069,13 @@ static void show_dirstat(struct diff_options *options)
/*
* Original minus copied is the removed material,
* added is the new material. They are both damages
- * made to the preimage.
+ * made to the preimage. In --dirstat-by-file mode, count
+ * damaged files, not damaged lines. This is done by
+ * counting only a single damaged line per file.
*/
damage = (p->one->size - copied) + added;
+ if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE) && damage > 0)
+ damage = 1;
ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc);
dir.files[dir.nr].name = name;
@@ -1157,7 +1108,6 @@ static void free_diffstat_info(struct diffstat_t *diffstat)
}
struct checkdiff_t {
- struct xdiff_emit_state xm;
const char *filename;
int lineno;
struct diff_options *o;
@@ -1332,123 +1282,45 @@ static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two)
emit_binary_diff_body(file, two, one);
}
-static void setup_diff_attr_check(struct git_attr_check *check)
+void diff_filespec_load_driver(struct diff_filespec *one)
{
- static struct git_attr *attr_diff;
-
- if (!attr_diff) {
- attr_diff = git_attr("diff", 4);
- }
- check[0].attr = attr_diff;
+ if (!one->driver)
+ one->driver = userdiff_find_by_path(one->path);
+ if (!one->driver)
+ one->driver = userdiff_find_by_name("default");
}
-static void diff_filespec_check_attr(struct diff_filespec *one)
+int diff_filespec_is_binary(struct diff_filespec *one)
{
- struct git_attr_check attr_diff_check;
- int check_from_data = 0;
-
- if (one->checked_attr)
- return;
-
- setup_diff_attr_check(&attr_diff_check);
- one->is_binary = 0;
- one->funcname_pattern_ident = NULL;
-
- if (!git_checkattr(one->path, 1, &attr_diff_check)) {
- const char *value;
-
- /* binaryness */
- value = attr_diff_check.value;
- if (ATTR_TRUE(value))
- ;
- else if (ATTR_FALSE(value))
- one->is_binary = 1;
- else
- check_from_data = 1;
-
- /* funcname pattern ident */
- if (ATTR_TRUE(value) || ATTR_FALSE(value) || ATTR_UNSET(value))
- ;
- else
- one->funcname_pattern_ident = value;
- }
-
- if (check_from_data) {
- if (!one->data && DIFF_FILE_VALID(one))
- diff_populate_filespec(one, 0);
-
- if (one->data)
- one->is_binary = buffer_is_binary(one->data, one->size);
+ if (one->is_binary == -1) {
+ diff_filespec_load_driver(one);
+ if (one->driver->binary != -1)
+ one->is_binary = one->driver->binary;
+ else {
+ if (!one->data && DIFF_FILE_VALID(one))
+ diff_populate_filespec(one, 0);
+ if (one->data)
+ one->is_binary = buffer_is_binary(one->data,
+ one->size);
+ if (one->is_binary == -1)
+ one->is_binary = 0;
+ }
}
+ return one->is_binary;
}
-int diff_filespec_is_binary(struct diff_filespec *one)
+static const struct userdiff_funcname *diff_funcname_pattern(struct diff_filespec *one)
{
- diff_filespec_check_attr(one);
- return one->is_binary;
+ diff_filespec_load_driver(one);
+ return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
}
-static const struct funcname_pattern_entry *funcname_pattern(const char *ident)
-{
- struct funcname_pattern_list *pp;
-
- for (pp = funcname_pattern_list; pp; pp = pp->next)
- if (!strcmp(ident, pp->e.name))
- return &pp->e;
- return NULL;
-}
-
-static const struct funcname_pattern_entry builtin_funcname_pattern[] = {
- { "java",
- "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
- "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$",
- REG_EXTENDED },
- { "pascal",
- "^((procedure|function|constructor|destructor|interface|"
- "implementation|initialization|finalization)[ \t]*.*)$"
- "|"
- "^(.*=[ \t]*(class|record).*)$",
- REG_EXTENDED },
- { "bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
- REG_EXTENDED },
- { "tex",
- "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
- REG_EXTENDED },
- { "ruby", "^[ \t]*((class|module|def)[ \t].*)$",
- REG_EXTENDED },
-};
-
-static const struct funcname_pattern_entry *diff_funcname_pattern(struct diff_filespec *one)
+void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b)
{
- const char *ident;
- const struct funcname_pattern_entry *pe;
- int i;
-
- diff_filespec_check_attr(one);
- ident = one->funcname_pattern_ident;
-
- if (!ident)
- /*
- * If the config file has "funcname.default" defined, that
- * regexp is used; otherwise NULL is returned and xemit uses
- * the built-in default.
- */
- return funcname_pattern("default");
-
- /* Look up custom "funcname.$ident" regexp from config. */
- pe = funcname_pattern(ident);
- if (pe)
- return pe;
-
- /*
- * And define built-in fallback patterns here. Note that
- * these can be overridden by the user's config settings.
- */
- for (i = 0; i < ARRAY_SIZE(builtin_funcname_pattern); i++)
- if (!strcmp(ident, builtin_funcname_pattern[i].name))
- return &builtin_funcname_pattern[i];
-
- return NULL;
+ if (!options->a_prefix)
+ options->a_prefix = a;
+ if (!options->b_prefix)
+ options->b_prefix = b;
}
static void builtin_diff(const char *name_a,
@@ -1464,13 +1336,23 @@ static void builtin_diff(const char *name_a,
char *a_one, *b_two;
const char *set = diff_get_color_opt(o, DIFF_METAINFO);
const char *reset = diff_get_color_opt(o, DIFF_RESET);
+ const char *a_prefix, *b_prefix;
+
+ diff_set_mnemonic_prefix(o, "a/", "b/");
+ if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
+ a_prefix = o->b_prefix;
+ b_prefix = o->a_prefix;
+ } else {
+ a_prefix = o->a_prefix;
+ b_prefix = o->b_prefix;
+ }
/* Never use a non-valid filename anywhere if at all possible */
name_a = DIFF_FILE_VALID(one) ? name_a : name_b;
name_b = DIFF_FILE_VALID(two) ? name_b : name_a;
- a_one = quote_two(o->a_prefix, name_a + (*name_a == '/'));
- b_two = quote_two(o->b_prefix, name_b + (*name_b == '/'));
+ a_one = quote_two(a_prefix, name_a + (*name_a == '/'));
+ b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
@@ -1528,7 +1410,7 @@ static void builtin_diff(const char *name_a,
xdemitconf_t xecfg;
xdemitcb_t ecb;
struct emit_callback ecbdata;
- const struct funcname_pattern_entry *pe;
+ const struct userdiff_funcname *pe;
pe = diff_funcname_pattern(one);
if (!pe)
@@ -1552,15 +1434,13 @@ static void builtin_diff(const char *name_a,
xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
else if (!prefixcmp(diffopts, "-u"))
xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
- ecb.outf = xdiff_outf;
- ecb.priv = &ecbdata;
- ecbdata.xm.consume = fn_out_consume;
if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) {
ecbdata.diff_words =
xcalloc(1, sizeof(struct diff_words_data));
ecbdata.diff_words->file = o->file;
}
- xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
+ &xpp, &xecfg, &ecb);
if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS))
free_diff_words_data(&ecbdata);
}
@@ -1611,9 +1491,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
memset(&xecfg, 0, sizeof(xecfg));
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
- ecb.outf = xdiff_outf;
- ecb.priv = diffstat;
- xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
+ &xpp, &xecfg, &ecb);
}
free_and_return:
@@ -1634,7 +1513,6 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
return;
memset(&data, 0, sizeof(data));
- data.xm.consume = checkdiff_consume;
data.filename = name_b ? name_b : name_a;
data.lineno = 0;
data.o = o;
@@ -1660,9 +1538,8 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = 1; /* at least one context line */
xpp.flags = XDF_NEED_MINIMAL;
- ecb.outf = xdiff_outf;
- ecb.priv = &data;
- xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
+ &xpp, &xecfg, &ecb);
if ((data.ws_rule & WS_TRAILING_SPACE) &&
data.trailing_blanks_start) {
@@ -1687,6 +1564,7 @@ struct diff_filespec *alloc_filespec(const char *path)
spec->path = (char *)(spec + 1);
memcpy(spec->path, path, namelen+1);
spec->count = 1;
+ spec->is_binary = -1;
return spec;
}
@@ -1771,10 +1649,9 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
static int populate_from_stdin(struct diff_filespec *s)
{
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
size_t size = 0;
- strbuf_init(&buf, 0);
if (strbuf_read(&buf, 0, 0) < 0)
return error("error while reading from stdin %s",
strerror(errno));
@@ -1826,7 +1703,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
if (!s->sha1_valid ||
reuse_worktree_file(s->path, s->sha1, 0)) {
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
struct stat st;
int fd;
@@ -1869,7 +1746,6 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
/*
* Convert from working tree format to canonical git format
*/
- strbuf_init(&buf, 0);
if (convert_to_git(s->path, s->data, s->size, &buf, safe_crlf)) {
size_t size = 0;
munmap(s->data, s->size);
@@ -2073,29 +1949,6 @@ static void run_external_diff(const char *pgm,
}
}
-static const char *external_diff_attr(const char *name)
-{
- struct git_attr_check attr_diff_check;
-
- if (!name)
- return NULL;
-
- setup_diff_attr_check(&attr_diff_check);
- if (!git_checkattr(name, 1, &attr_diff_check)) {
- const char *value = attr_diff_check.value;
- if (!ATTR_TRUE(value) &&
- !ATTR_FALSE(value) &&
- !ATTR_UNSET(value)) {
- struct ll_diff_driver *drv;
-
- for (drv = user_diff; drv; drv = drv->next)
- if (!strcmp(drv->name, value))
- return drv->cmd;
- }
- }
- return NULL;
-}
-
static void run_diff_cmd(const char *pgm,
const char *name,
const char *other,
@@ -2109,9 +1962,9 @@ static void run_diff_cmd(const char *pgm,
if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
pgm = NULL;
else {
- const char *cmd = external_diff_attr(attr_path);
- if (cmd)
- pgm = cmd;
+ struct userdiff_driver *drv = userdiff_find_by_path(attr_path);
+ if (drv && drv->external)
+ pgm = drv->external;
}
if (pgm) {
@@ -2332,8 +2185,10 @@ void diff_setup(struct diff_options *options)
DIFF_OPT_CLR(options, COLOR_DIFF);
options->detect_rename = diff_detect_rename_default;
- options->a_prefix = "a/";
- options->b_prefix = "b/";
+ if (!diff_mnemonic_prefix) {
+ options->a_prefix = "a/";
+ options->b_prefix = "b/";
+ }
}
int diff_setup_done(struct diff_options *options)
@@ -2490,6 +2345,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (!strcmp(arg, "--cumulative")) {
options->output_format |= DIFF_FORMAT_DIRSTAT;
DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
+ } else if (opt_arg(arg, 0, "dirstat-by-file",
+ &options->dirstat_percent)) {
+ options->output_format |= DIFF_FORMAT_DIRSTAT;
+ DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
}
else if (!strcmp(arg, "--check"))
options->output_format |= DIFF_FORMAT_CHECKDIFF;
@@ -3046,8 +2905,7 @@ static void diff_summary(FILE *file, struct diff_filepair *p)
}
struct patch_id_t {
- struct xdiff_emit_state xm;
- SHA_CTX *ctx;
+ git_SHA_CTX *ctx;
int patchlen;
};
@@ -3075,7 +2933,7 @@ static void patch_id_consume(void *priv, char *line, unsigned long len)
new_len = remove_space(line, len);
- SHA1_Update(data->ctx, line, new_len);
+ git_SHA1_Update(data->ctx, line, new_len);
data->patchlen += new_len;
}
@@ -3084,14 +2942,13 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
{
struct diff_queue_struct *q = &diff_queued_diff;
int i;
- SHA_CTX ctx;
+ git_SHA_CTX ctx;
struct patch_id_t data;
char buffer[PATH_MAX * 4 + 20];
- SHA1_Init(&ctx);
+ git_SHA1_Init(&ctx);
memset(&data, 0, sizeof(struct patch_id_t));
data.ctx = &ctx;
- data.xm.consume = patch_id_consume;
for (i = 0; i < q->nr; i++) {
xpparam_t xpp;
@@ -3151,17 +3008,16 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
len2, p->two->path,
len1, p->one->path,
len2, p->two->path);
- SHA1_Update(&ctx, buffer, len1);
+ git_SHA1_Update(&ctx, buffer, len1);
xpp.flags = XDF_NEED_MINIMAL;
xecfg.ctxlen = 3;
xecfg.flags = XDL_EMIT_FUNCNAMES;
- ecb.outf = xdiff_outf;
- ecb.priv = &data;
- xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
+ &xpp, &xecfg, &ecb);
}
- SHA1_Final(sha1, &ctx);
+ git_SHA1_Final(sha1, &ctx);
return 0;
}
@@ -3235,7 +3091,6 @@ void diff_flush(struct diff_options *options)
struct diffstat_t diffstat;
memset(&diffstat, 0, sizeof(struct diffstat_t));
- diffstat.xm.consume = diffstat_consume;
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
if (check_pair_status(p))
@@ -3532,3 +3387,34 @@ void diff_unmerge(struct diff_options *options,
fill_filespec(one, sha1, mode);
diff_queue(&diff_queued_diff, one, two)->is_unmerged = 1;
}
+
+static char *run_textconv(const char *pgm, struct diff_filespec *spec,
+ size_t *outsize)
+{
+ struct diff_tempfile temp;
+ const char *argv[3];
+ const char **arg = argv;
+ struct child_process child;
+ struct strbuf buf = STRBUF_INIT;
+
+ prepare_temp_file(spec->path, &temp, spec);
+ *arg++ = pgm;
+ *arg++ = temp.name;
+ *arg = NULL;
+
+ memset(&child, 0, sizeof(child));
+ child.argv = argv;
+ child.out = -1;
+ if (start_command(&child) != 0 ||
+ strbuf_read(&buf, child.out, 0) < 0 ||
+ finish_command(&child) != 0) {
+ if (temp.name == temp.tmp_path)
+ unlink(temp.name);
+ error("error running textconv command '%s'", pgm);
+ return NULL;
+ }
+ if (temp.name == temp.tmp_path)
+ unlink(temp.name);
+
+ return strbuf_detach(&buf, outsize);
+}
diff --git a/diff.h b/diff.h
index 7f53beb..a49d865 100644
--- a/diff.h
+++ b/diff.h
@@ -64,6 +64,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
#define DIFF_OPT_RELATIVE_NAME (1 << 17)
#define DIFF_OPT_IGNORE_SUBMODULES (1 << 18)
#define DIFF_OPT_DIRSTAT_CUMULATIVE (1 << 19)
+#define DIFF_OPT_DIRSTAT_BY_FILE (1 << 20)
#define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag)
#define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag)
#define DIFF_OPT_CLR(opts, flag) ((opts)->flags &= ~DIFF_OPT_##flag)
@@ -160,6 +161,8 @@ extern void diff_tree_combined(const unsigned char *sha1, const unsigned char pa
extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_info *);
+void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b);
+
extern void diff_addremove(struct diff_options *,
int addremove,
unsigned mode,
diff --git a/diffcore.h b/diffcore.h
index 8ae3578..713cca7 100644
--- a/diffcore.h
+++ b/diffcore.h
@@ -22,6 +22,8 @@
#define MINIMUM_BREAK_SIZE 400 /* do not break a file smaller than this */
+struct userdiff_driver;
+
struct diff_filespec {
unsigned char sha1[20];
char *path;
@@ -40,8 +42,10 @@ struct diff_filespec {
#define DIFF_FILE_VALID(spec) (((spec)->mode) != 0)
unsigned should_free : 1; /* data should be free()'ed */
unsigned should_munmap : 1; /* data should be munmap()'ed */
- unsigned checked_attr : 1;
- unsigned is_binary : 1; /* data should be considered "binary" */
+
+ struct userdiff_driver *driver;
+ /* data should be considered "binary"; -1 means "don't know yet" */
+ int is_binary;
};
extern struct diff_filespec *alloc_filespec(const char *);
diff --git a/dir.c b/dir.c
index cfaa28f..0131983d 100644
--- a/dir.c
+++ b/dir.c
@@ -52,11 +52,6 @@ int common_prefix(const char **pathspec)
return prefix;
}
-static inline int special_char(unsigned char c1)
-{
- return !c1 || c1 == '*' || c1 == '[' || c1 == '?' || c1 == '\\';
-}
-
/*
* Does 'match' matches the given name?
* A match is found if
@@ -80,7 +75,7 @@ static int match_one(const char *match, const char *name, int namelen)
for (;;) {
unsigned char c1 = *match;
unsigned char c2 = *name;
- if (special_char(c1))
+ if (isspecial(c1))
break;
if (c1 != c2)
return 0;
@@ -387,7 +382,7 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len)
return ent;
}
-struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
+static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
{
if (cache_name_exists(pathname, len, ignore_case))
return NULL;
@@ -396,7 +391,7 @@ struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int
return dir->entries[dir->nr++] = dir_entry_new(pathname, len);
}
-struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
+static struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
{
if (cache_name_pos(pathname, len) >= 0)
return NULL;
@@ -680,17 +675,12 @@ static int cmp_name(const void *p1, const void *p2)
*/
static int simple_length(const char *match)
{
- const char special[256] = {
- [0] = 1, ['?'] = 1,
- ['\\'] = 1, ['*'] = 1,
- ['['] = 1
- };
int len = -1;
for (;;) {
unsigned char c = *match++;
len++;
- if (special[c])
+ if (isspecial(c))
return len;
}
}
@@ -727,8 +717,12 @@ static void free_simplify(struct path_simplify *simplify)
int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec)
{
- struct path_simplify *simplify = create_simplify(pathspec);
+ struct path_simplify *simplify;
+
+ if (has_symlink_leading_path(strlen(path), path))
+ return dir->nr;
+ simplify = create_simplify(pathspec);
read_directory_recursive(dir, path, base, baselen, 0, simplify);
free_simplify(simplify);
qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
diff --git a/dir.h b/dir.h
index 278ee42..768425a 100644
--- a/dir.h
+++ b/dir.h
@@ -73,7 +73,6 @@ extern void add_excludes_from_file(struct dir_struct *, const char *fname);
extern void add_exclude(const char *string, const char *base,
int baselen, struct exclude_list *which);
extern int file_exists(const char *);
-extern struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len);
extern char *get_relative_cwd(char *buffer, int size, const char *dir);
extern int is_inside_dir(const char *dir);
diff --git a/editor.c b/editor.c
index eebc3e9..4d469d0 100644
--- a/editor.c
+++ b/editor.c
@@ -26,9 +26,8 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
int i = 0;
int failed;
const char *args[6];
- struct strbuf arg0;
+ struct strbuf arg0 = STRBUF_INIT;
- strbuf_init(&arg0, 0);
if (strcspn(editor, "$ \t'") != len) {
/* there are specials */
strbuf_addf(&arg0, "%s \"$@\"", editor);
diff --git a/environment.c b/environment.c
index 0c6d11f..0693cd9 100644
--- a/environment.c
+++ b/environment.c
@@ -80,6 +80,11 @@ int is_bare_repository(void)
return is_bare_repository_cfg && !get_git_work_tree();
}
+int have_git_dir(void)
+{
+ return !!git_dir;
+}
+
const char *get_git_dir(void)
{
if (!git_dir)
diff --git a/exec_cmd.c b/exec_cmd.c
index ce6741e..cdd35f9 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -59,9 +59,7 @@ static void add_path(struct strbuf *out, const char *path)
void setup_path(void)
{
const char *old_path = getenv("PATH");
- struct strbuf new_path;
-
- strbuf_init(&new_path, 0);
+ struct strbuf new_path = STRBUF_INIT;
add_path(&new_path, argv_exec_path);
add_path(&new_path, getenv(EXEC_PATH_ENVIRONMENT));
diff --git a/fast-import.c b/fast-import.c
index 5473cd4..3c035a5 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -376,7 +376,7 @@ static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *);
static void write_crash_report(const char *err)
{
- char *loc = git_path("fast_import_crash_%d", getpid());
+ char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
FILE *rpt = fopen(loc, "w");
struct branch *b;
unsigned long lu;
@@ -390,8 +390,8 @@ static void write_crash_report(const char *err)
fprintf(stderr, "fast-import: dumping crash report to %s\n", loc);
fprintf(rpt, "fast-import crash report:\n");
- fprintf(rpt, " fast-import process: %d\n", getpid());
- fprintf(rpt, " parent process : %d\n", getppid());
+ fprintf(rpt, " fast-import process: %"PRIuMAX"\n", (uintmax_t) getpid());
+ fprintf(rpt, " parent process : %"PRIuMAX"\n", (uintmax_t) getppid());
fprintf(rpt, " at %s\n", show_date(time(NULL), 0, DATE_LOCAL));
fputc('\n', rpt);
@@ -845,7 +845,7 @@ static int oecmp (const void *a_, const void *b_)
static char *create_index(void)
{
static char tmpfile[PATH_MAX];
- SHA_CTX ctx;
+ git_SHA_CTX ctx;
struct sha1file *f;
struct object_entry **idx, **c, **last, *e;
struct object_entry_pool *o;
@@ -882,17 +882,17 @@ static char *create_index(void)
idx_fd = xmkstemp(tmpfile);
f = sha1fd(idx_fd, tmpfile);
sha1write(f, array, 256 * sizeof(int));
- SHA1_Init(&ctx);
+ git_SHA1_Init(&ctx);
for (c = idx; c != last; c++) {
uint32_t offset = htonl((*c)->offset);
sha1write(f, &offset, 4);
sha1write(f, (*c)->sha1, sizeof((*c)->sha1));
- SHA1_Update(&ctx, (*c)->sha1, 20);
+ git_SHA1_Update(&ctx, (*c)->sha1, 20);
}
sha1write(f, pack_data->sha1, sizeof(pack_data->sha1));
sha1close(f, NULL, CSUM_FSYNC);
free(idx);
- SHA1_Final(pack_data->sha1, &ctx);
+ git_SHA1_Final(pack_data->sha1, &ctx);
return tmpfile;
}
@@ -1033,15 +1033,15 @@ static int store_object(
unsigned char hdr[96];
unsigned char sha1[20];
unsigned long hdrlen, deltalen;
- SHA_CTX c;
+ git_SHA_CTX c;
z_stream s;
hdrlen = sprintf((char*)hdr,"%s %lu", typename(type),
(unsigned long)dat->len) + 1;
- SHA1_Init(&c);
- SHA1_Update(&c, hdr, hdrlen);
- SHA1_Update(&c, dat->buf, dat->len);
- SHA1_Final(sha1, &c);
+ git_SHA1_Init(&c);
+ git_SHA1_Update(&c, hdr, hdrlen);
+ git_SHA1_Update(&c, dat->buf, dat->len);
+ git_SHA1_Final(sha1, &c);
if (sha1out)
hashcpy(sha1out, sha1);
diff --git a/fsck.c b/fsck.c
index 797e317..0cf5f01 100644
--- a/fsck.c
+++ b/fsck.c
@@ -307,9 +307,8 @@ int fsck_error_function(struct object *obj, int type, const char *fmt, ...)
{
va_list ap;
int len;
- struct strbuf sb;
+ struct strbuf sb = STRBUF_INIT;
- strbuf_init(&sb, 0);
strbuf_addf(&sb, "object %s:", obj->sha1?sha1_to_hex(obj->sha1):"(null)");
va_start(ap, fmt);
diff --git a/git-bisect.sh b/git-bisect.sh
index 97ac600..79de701 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -172,6 +172,25 @@ bisect_write() {
test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
}
+is_expected_rev() {
+ test -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
+ test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
+}
+
+mark_expected_rev() {
+ echo "$1" > "$GIT_DIR/BISECT_EXPECTED_REV"
+}
+
+check_expected_revs() {
+ for _rev in "$@"; do
+ if ! is_expected_rev "$_rev"; then
+ rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
+ rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
+ return
+ fi
+ done
+}
+
bisect_state() {
bisect_autostart
state=$1
@@ -181,7 +200,8 @@ bisect_state() {
1,bad|1,good|1,skip)
rev=$(git rev-parse --verify HEAD) ||
die "Bad rev input: HEAD"
- bisect_write "$state" "$rev" ;;
+ bisect_write "$state" "$rev"
+ check_expected_revs "$rev" ;;
2,bad|*,good|*,skip)
shift
eval=''
@@ -191,7 +211,8 @@ bisect_state() {
die "Bad rev input: $rev"
eval="$eval bisect_write '$state' '$sha'; "
done
- eval "$eval" ;;
+ eval "$eval"
+ check_expected_revs "$@" ;;
*,bad)
die "'git bisect bad' can take only one argument." ;;
*)
@@ -243,33 +264,18 @@ bisect_auto_next() {
bisect_next_check && bisect_next || :
}
-eval_rev_list() {
- _eval="$1"
-
- eval $_eval
- res=$?
-
- if [ $res -ne 0 ]; then
- echo >&2 "'git rev-list --bisect-vars' failed:"
- echo >&2 "maybe you mistake good and bad revs?"
- exit $res
- fi
-
- return $res
-}
-
filter_skipped() {
_eval="$1"
_skip="$2"
if [ -z "$_skip" ]; then
- eval_rev_list "$_eval"
+ eval "$_eval"
return
fi
# Let's parse the output of:
# "git rev-list --bisect-vars --bisect-all ..."
- eval_rev_list "$_eval" | while read hash line
+ eval "$_eval" | while read hash line
do
case "$VARS,$FOUND,$TRIED,$hash" in
# We display some vars.
@@ -332,20 +338,133 @@ exit_if_skipped_commits () {
fi
}
+bisect_checkout() {
+ _rev="$1"
+ _msg="$2"
+ echo "Bisecting: $_msg"
+ mark_expected_rev "$_rev"
+ git checkout -q "$_rev" || exit
+ git show-branch "$_rev"
+}
+
+is_among() {
+ _rev="$1"
+ _list="$2"
+ case "$_list" in *$_rev*) return 0 ;; esac
+ return 1
+}
+
+handle_bad_merge_base() {
+ _badmb="$1"
+ _good="$2"
+ if is_expected_rev "$_badmb"; then
+ cat >&2 <<EOF
+The merge base $_badmb is bad.
+This means the bug has been fixed between $_badmb and [$_good].
+EOF
+ exit 3
+ else
+ cat >&2 <<EOF
+Some good revs are not ancestor of the bad rev.
+git bisect cannot work properly in this case.
+Maybe you mistake good and bad revs?
+EOF
+ exit 1
+ fi
+}
+
+handle_skipped_merge_base() {
+ _mb="$1"
+ _bad="$2"
+ _good="$3"
+ cat >&2 <<EOF
+Warning: the merge base between $_bad and [$_good] must be skipped.
+So we cannot be sure the first bad commit is between $_mb and $_bad.
+We continue anyway.
+EOF
+}
+
+#
+# "check_merge_bases" checks that merge bases are not "bad".
+#
+# - If one is "good", that's good, we have nothing to do.
+# - If one is "bad", it means the user assumed something wrong
+# and we must exit.
+# - If one is "skipped", we can't know but we should warn.
+# - If we don't know, we should check it out and ask the user to test.
+#
+# In the last case we will return 1, and otherwise 0.
+#
+check_merge_bases() {
+ _bad="$1"
+ _good="$2"
+ _skip="$3"
+ for _mb in $(git merge-base --all $_bad $_good)
+ do
+ if is_among "$_mb" "$_good"; then
+ continue
+ elif test "$_mb" = "$_bad"; then
+ handle_bad_merge_base "$_bad" "$_good"
+ elif is_among "$_mb" "$_skip"; then
+ handle_skipped_merge_base "$_mb" "$_bad" "$_good"
+ else
+ bisect_checkout "$_mb" "a merge base must be tested"
+ return 1
+ fi
+ done
+ return 0
+}
+
+#
+# "check_good_are_ancestors_of_bad" checks that all "good" revs are
+# ancestor of the "bad" rev.
+#
+# If that's not the case, we need to check the merge bases.
+# If a merge base must be tested by the user we return 1 and
+# otherwise 0.
+#
+check_good_are_ancestors_of_bad() {
+ test -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
+ return
+
+ _bad="$1"
+ _good=$(echo $2 | sed -e 's/\^//g')
+ _skip="$3"
+
+ # Bisecting with no good rev is ok
+ test -z "$_good" && return
+
+ _side=$(git rev-list $_good ^$_bad)
+ if test -n "$_side"; then
+ # Return if a checkout was done
+ check_merge_bases "$_bad" "$_good" "$_skip" || return
+ fi
+
+ : > "$GIT_DIR/BISECT_ANCESTORS_OK"
+
+ return 0
+}
+
bisect_next() {
case "$#" in 0) ;; *) usage ;; esac
bisect_autostart
bisect_next_check good
+ # Get bad, good and skipped revs
+ bad=$(git rev-parse --verify refs/bisect/bad) &&
+ good=$(git for-each-ref --format='^%(objectname)' \
+ "refs/bisect/good-*" | tr '\012' ' ') &&
skip=$(git for-each-ref --format='%(objectname)' \
- "refs/bisect/skip-*" | tr '\012' ' ') || exit
+ "refs/bisect/skip-*" | tr '\012' ' ') &&
+
+ # Maybe some merge bases must be tested first
+ check_good_are_ancestors_of_bad "$bad" "$good" "$skip"
+ # Return now if a checkout has already been done
+ test "$?" -eq "1" && return
+ # Get bisection information
BISECT_OPT=''
test -n "$skip" && BISECT_OPT='--bisect-all'
-
- bad=$(git rev-parse --verify refs/bisect/bad) &&
- good=$(git for-each-ref --format='^%(objectname)' \
- "refs/bisect/good-*" | tr '\012' ' ') &&
eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
eval=$(filter_skipped "$eval" "$skip") &&
@@ -366,9 +485,7 @@ bisect_next() {
# commit is also a "skip" commit (see above).
exit_if_skipped_commits "$bisect_rev"
- echo "Bisecting: $bisect_nr revisions left to test after this"
- git checkout -q "$bisect_rev" || exit
- git show-branch "$bisect_rev"
+ bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this"
}
bisect_visualize() {
@@ -415,6 +532,8 @@ bisect_clean_state() {
do
git update-ref -d $ref $hash || exit
done
+ rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
+ rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
rm -f "$GIT_DIR/BISECT_LOG" &&
rm -f "$GIT_DIR/BISECT_NAMES" &&
rm -f "$GIT_DIR/BISECT_RUN" &&
diff --git a/git-compat-util.h b/git-compat-util.h
index cf89cdf..e20b1e8 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -85,6 +85,7 @@
#undef _XOPEN_SOURCE
#include <grp.h>
#define _XOPEN_SOURCE 600
+#include "compat/cygwin.h"
#else
#undef _ALL_SOURCE /* AIX 5.3L defines a struct list with _ALL_SOURCE. */
#include <grp.h>
@@ -99,6 +100,11 @@
#include <iconv.h>
#endif
+#ifndef NO_OPENSSL
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#endif
+
/* On most systems <limits.h> would have given us this, but
* not on some systems (e.g. GNU/Hurd).
*/
@@ -149,10 +155,7 @@ extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1,
extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
-extern void set_usage_routine(void (*routine)(const char *err) NORETURN);
extern void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN);
-extern void set_error_routine(void (*routine)(const char *err, va_list params));
-extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
extern int prefixcmp(const char *str, const char *prefix);
extern time_t tm_to_time_t(const struct tm *tm);
@@ -192,6 +195,12 @@ extern int git_munmap(void *start, size_t length);
#endif /* NO_MMAP */
+#ifdef NO_ST_BLOCKS_IN_STRUCT_STAT
+#define on_disk_bytes(st) ((st).st_size)
+#else
+#define on_disk_bytes(st) ((st).st_blocks * 512)
+#endif
+
#define DEFAULT_PACKED_GIT_LIMIT \
((1024L * 1024L) * (sizeof(void*) >= 8 ? 8192 : 256))
@@ -318,11 +327,13 @@ extern unsigned char sane_ctype[256];
#define GIT_SPACE 0x01
#define GIT_DIGIT 0x02
#define GIT_ALPHA 0x04
+#define GIT_SPECIAL 0x08
#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
#define isspace(x) sane_istest(x,GIT_SPACE)
#define isdigit(x) sane_istest(x,GIT_DIGIT)
#define isalpha(x) sane_istest(x,GIT_ALPHA)
#define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
+#define isspecial(x) sane_istest(x,GIT_SPECIAL)
#define tolower(x) sane_case((unsigned char)(x), 0x20)
#define toupper(x) sane_case((unsigned char)(x), 0)
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index a324cf0..81392ad 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -232,11 +232,11 @@ mkdir ../map || die "Could not create map/ directory"
case "$filter_subdir" in
"")
git rev-list --reverse --topo-order --default HEAD \
- --parents "$@"
+ --parents --simplify-merges "$@"
;;
*)
git rev-list --reverse --topo-order --default HEAD \
- --parents "$@" -- "$filter_subdir"
+ --parents --simplify-merges "$@" -- "$filter_subdir"
esac > ../revs || die "Could not get the commits"
commits=$(wc -l <../revs | tr -d " ")
@@ -317,24 +317,20 @@ done <../revs
# In case of a subdirectory filter, it is possible that a specified head
# is not in the set of rewritten commits, because it was pruned by the
-# revision walker. Fix it by mapping these heads to the next rewritten
-# ancestor(s), i.e. the boundaries in the set of rewritten commits.
+# revision walker. Fix it by mapping these heads to the unique nearest
+# ancestor that survived the pruning.
-# NEEDSWORK: we should sort the unmapped refs topologically first
-while read ref
-do
- sha1=$(git rev-parse "$ref"^0)
- test -f "$workdir"/../map/$sha1 && continue
- # Assign the boundarie(s) in the set of rewritten commits
- # as the replacement commit(s).
- # (This would look a bit nicer if --not --stdin worked.)
- for p in $( (cd "$workdir"/../map; ls | sed "s/^/^/") |
- git rev-list $ref --boundary --stdin |
- sed -n "s/^-//p")
+if test "$filter_subdir"
+then
+ while read ref
do
- map $p >> "$workdir"/../map/$sha1
- done
-done < "$tempdir"/heads
+ sha1=$(git rev-parse "$ref"^0)
+ test -f "$workdir"/../map/$sha1 && continue
+ ancestor=$(git rev-list --simplify-merges -1 \
+ $ref -- "$filter_subdir")
+ test "$ancestor" && echo $(map $ancestor) >> "$workdir"/../map/$sha1
+ done < "$tempdir"/heads
+fi
# Finally update the refs
@@ -416,15 +412,17 @@ if [ "$filter_tag_name" ]; then
echo "$ref -> $new_ref ($sha1 -> $new_sha1)"
if [ "$type" = "tag" ]; then
- new_sha1=$(git cat-file tag "$ref" |
+ new_sha1=$( ( printf 'object %s\ntype commit\ntag %s\n' \
+ "$new_sha1" "$new_ref"
+ git cat-file tag "$ref" |
sed -n \
-e "1,/^$/{
- s/^object .*/object $new_sha1/
- s/^type .*/type commit/
- s/^tag .*/tag $new_ref/
+ /^object /d
+ /^type /d
+ /^tag /d
}" \
-e '/^-----BEGIN PGP SIGNATURE-----/q' \
- -e 'p' |
+ -e 'p' ) |
git mktag) ||
die "Could not create new tag object for $ref"
if git cat-file tag "$ref" | \
diff --git a/git-gui/.gitattributes b/git-gui/.gitattributes
new file mode 100644
index 0000000..f96112d
--- /dev/null
+++ b/git-gui/.gitattributes
@@ -0,0 +1,3 @@
+* encoding=US-ASCII
+git-gui.sh encoding=UTF-8
+/po/*.po encoding=UTF-8
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 86402d4..4085e8f 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -521,6 +521,19 @@ proc kill_file_process {fd} {
}
}
+proc gitattr {path attr default} {
+ if {[catch {set r [git check-attr $attr -- $path]}]} {
+ set r unspecified
+ } else {
+ set r [join [lrange [split $r :] 2 end] :]
+ regsub {^ } $r {} r
+ }
+ if {$r eq {unspecified}} {
+ return $default
+ }
+ return $r
+}
+
proc sq {value} {
regsub -all ' $value "'\\''" value
return "'$value'"
@@ -657,17 +670,21 @@ proc apply_config {} {
}
set default_config(branch.autosetupmerge) true
+set default_config(merge.tool) {}
+set default_config(merge.keepbackup) true
set default_config(merge.diffstat) true
set default_config(merge.summary) false
set default_config(merge.verbosity) 2
set default_config(user.name) {}
set default_config(user.email) {}
+set default_config(gui.encoding) [encoding system]
set default_config(gui.matchtrackingbranch) false
set default_config(gui.pruneduringfetch) false
set default_config(gui.trustmtime) false
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.commitmsgwidth) 75
set default_config(gui.newbranchtemplate) {}
@@ -945,10 +962,32 @@ blame {
}
citool {
enable_option singlecommit
+ enable_option retcode
disable_option multicommit
disable_option branch
disable_option transport
+
+ while {[llength $argv] > 0} {
+ set a [lindex $argv 0]
+ switch -- $a {
+ --amend {
+ enable_option initialamend
+ }
+ --nocommit {
+ enable_option nocommit
+ enable_option nocommitmsg
+ }
+ --commitmsg {
+ disable_option nocommitmsg
+ }
+ default {
+ break
+ }
+ }
+
+ set argv [lrange $argv 1 end]
+ }
}
}
@@ -1020,8 +1059,12 @@ set current_branch {}
set is_detached 0
set current_diff_path {}
set is_3way_diff 0
+set is_conflict_diff 0
set selected_commit_type new
+set nullid "0000000000000000000000000000000000000000"
+set nullid2 "0000000000000000000000000000000000000001"
+
######################################################################
##
## task management
@@ -1102,6 +1145,20 @@ proc PARENT {} {
return $empty_tree
}
+proc force_amend {} {
+ global selected_commit_type
+ global HEAD PARENT MERGE_HEAD commit_type
+
+ repository_state newType newHEAD newMERGE_HEAD
+ set HEAD $newHEAD
+ set PARENT $newHEAD
+ set MERGE_HEAD $newMERGE_HEAD
+ set commit_type $newType
+
+ set selected_commit_type amend
+ do_select_commit_type
+}
+
proc rescan {after {honor_trustmtime 1}} {
global HEAD PARENT MERGE_HEAD commit_type
global ui_index ui_workdir ui_comm
@@ -1128,6 +1185,7 @@ proc rescan {after {honor_trustmtime 1}} {
|| [string trim [$ui_comm get 0.0 end]] eq {})} {
if {[string match amend* $commit_type]} {
} elseif {[load_message GITGUI_MSG]} {
+ } elseif {[run_prepare_commit_msg_hook]} {
} elseif {[load_message MERGE_MSG]} {
} elseif {[load_message SQUASH_MSG]} {
}
@@ -1227,6 +1285,70 @@ proc load_message {file} {
return 0
}
+proc run_prepare_commit_msg_hook {} {
+ global pch_error
+
+ # prepare-commit-msg requires PREPARE_COMMIT_MSG exist. From git-gui
+ # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
+ # empty file but existant file.
+
+ set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a]
+
+ if {[file isfile [gitdir MERGE_MSG]]} {
+ set pcm_source "merge"
+ set fd_mm [open [gitdir MERGE_MSG] r]
+ puts -nonewline $fd_pcm [read $fd_mm]
+ close $fd_mm
+ } elseif {[file isfile [gitdir SQUASH_MSG]]} {
+ set pcm_source "squash"
+ set fd_sm [open [gitdir SQUASH_MSG] r]
+ puts -nonewline $fd_pcm [read $fd_sm]
+ close $fd_sm
+ } else {
+ set pcm_source ""
+ }
+
+ close $fd_pcm
+
+ set fd_ph [githook_read prepare-commit-msg \
+ [gitdir PREPARE_COMMIT_MSG] $pcm_source]
+ if {$fd_ph eq {}} {
+ catch {file delete [gitdir PREPARE_COMMIT_MSG]}
+ return 0;
+ }
+
+ ui_status [mc "Calling prepare-commit-msg hook..."]
+ set pch_error {}
+
+ fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
+ fileevent $fd_ph readable \
+ [list prepare_commit_msg_hook_wait $fd_ph]
+
+ return 1;
+}
+
+proc prepare_commit_msg_hook_wait {fd_ph} {
+ global pch_error
+
+ append pch_error [read $fd_ph]
+ fconfigure $fd_ph -blocking 1
+ if {[eof $fd_ph]} {
+ if {[catch {close $fd_ph}]} {
+ ui_status [mc "Commit declined by prepare-commit-msg hook."]
+ hook_failed_popup prepare-commit-msg $pch_error
+ catch {file delete [gitdir PREPARE_COMMIT_MSG]}
+ exit 1
+ } else {
+ load_message PREPARE_COMMIT_MSG
+ }
+ set pch_error {}
+ catch {file delete [gitdir PREPARE_COMMIT_MSG]}
+ return
+ }
+ fconfigure $fd_ph -blocking 0
+ catch {file delete [gitdir PREPARE_COMMIT_MSG]}
+}
+
proc read_diff_index {fd after} {
global buf_rdi
@@ -1323,6 +1445,8 @@ proc rescan_done {fd buf after} {
unlock_index
display_all_files
if {$current_diff_path ne {}} reshow_diff
+ if {$current_diff_path eq {}} select_first_diff
+
uplevel #0 $after
}
@@ -1619,6 +1743,15 @@ static unsigned char file_merge_bits[] = {
0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
} -maskdata $filemask
+image create bitmap file_statechange -background white -foreground green -data {
+#define file_merge_width 14
+#define file_merge_height 15
+static unsigned char file_statechange_bits[] = {
+ 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10,
+ 0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10,
+ 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
+} -maskdata $filemask
+
set ui_index .vpane.files.index.list
set ui_workdir .vpane.files.workdir.list
@@ -1627,12 +1760,14 @@ set all_icons(A$ui_index) file_fulltick
set all_icons(M$ui_index) file_fulltick
set all_icons(D$ui_index) file_removed
set all_icons(U$ui_index) file_merge
+set all_icons(T$ui_index) file_statechange
set all_icons(_$ui_workdir) file_plain
set all_icons(M$ui_workdir) file_mod
set all_icons(D$ui_workdir) file_question
set all_icons(U$ui_workdir) file_merge
set all_icons(O$ui_workdir) file_plain
+set all_icons(T$ui_workdir) file_statechange
set max_status_desc 0
foreach i {
@@ -1643,6 +1778,9 @@ foreach i {
{MM {mc "Portions staged for commit"}}
{MD {mc "Staged for commit, missing"}}
+ {_T {mc "File type changed, not staged"}}
+ {T_ {mc "File type changed, staged"}}
+
{_O {mc "Untracked, not staged"}}
{A_ {mc "Staged for commit"}}
{AM {mc "Portions staged for commit"}}
@@ -1652,10 +1790,12 @@ foreach i {
{D_ {mc "Staged for removal"}}
{DO {mc "Staged for removal, still present"}}
+ {_U {mc "Requires merge resolution"}}
{U_ {mc "Requires merge resolution"}}
{UU {mc "Requires merge resolution"}}
{UM {mc "Requires merge resolution"}}
{UD {mc "Requires merge resolution"}}
+ {UT {mc "Requires merge resolution"}}
} {
set text [eval [lindex $i 1]]
if {$max_status_desc < [string length $text]} {
@@ -1730,11 +1870,19 @@ proc do_gitk {revs} {
}
set is_quitting 0
+set ret_code 1
+
+proc terminate_me {win} {
+ global ret_code
+ if {$win ne {.}} return
+ exit $ret_code
+}
-proc do_quit {} {
+proc do_quit {{rc {1}}} {
global ui_comm is_quitting repo_config commit_type
global GITGUI_BCK_exists GITGUI_BCK_i
global ui_comm_spell
+ global ret_code
if {$is_quitting} return
set is_quitting 1
@@ -1789,6 +1937,7 @@ proc do_quit {} {
}
}
+ set ret_code $rc
destroy .
}
@@ -1796,13 +1945,120 @@ proc do_rescan {} {
rescan ui_ready
}
+proc ui_do_rescan {} {
+ rescan {force_first_diff; ui_ready}
+}
+
proc do_commit {} {
commit_tree
}
proc next_diff {} {
global next_diff_p next_diff_w next_diff_i
- show_diff $next_diff_p $next_diff_w $next_diff_i
+ show_diff $next_diff_p $next_diff_w {}
+}
+
+proc find_anchor_pos {lst name} {
+ set lid [lsearch -sorted -exact $lst $name]
+
+ if {$lid == -1} {
+ set lid 0
+ foreach lname $lst {
+ if {$lname >= $name} break
+ incr lid
+ }
+ }
+
+ return $lid
+}
+
+proc find_file_from {flist idx delta path mmask} {
+ global file_states
+
+ set len [llength $flist]
+ while {$idx >= 0 && $idx < $len} {
+ set name [lindex $flist $idx]
+
+ if {$name ne $path && [info exists file_states($name)]} {
+ set state [lindex $file_states($name) 0]
+
+ if {$mmask eq {} || [regexp $mmask $state]} {
+ return $idx
+ }
+ }
+
+ incr idx $delta
+ }
+
+ return {}
+}
+
+proc find_next_diff {w path {lno {}} {mmask {}}} {
+ global next_diff_p next_diff_w next_diff_i
+ global file_lists ui_index ui_workdir
+
+ set flist $file_lists($w)
+ if {$lno eq {}} {
+ set lno [find_anchor_pos $flist $path]
+ } else {
+ incr lno -1
+ }
+
+ if {$mmask ne {} && ![regexp {(^\^)|(\$$)} $mmask]} {
+ if {$w eq $ui_index} {
+ set mmask "^$mmask"
+ } else {
+ set mmask "$mmask\$"
+ }
+ }
+
+ set idx [find_file_from $flist $lno 1 $path $mmask]
+ if {$idx eq {}} {
+ incr lno -1
+ set idx [find_file_from $flist $lno -1 $path $mmask]
+ }
+
+ if {$idx ne {}} {
+ set next_diff_w $w
+ set next_diff_p [lindex $flist $idx]
+ set next_diff_i [expr {$idx+1}]
+ return 1
+ } else {
+ return 0
+ }
+}
+
+proc next_diff_after_action {w path {lno {}} {mmask {}}} {
+ global current_diff_path
+
+ if {$path ne $current_diff_path} {
+ return {}
+ } elseif {[find_next_diff $w $path $lno $mmask]} {
+ return {next_diff;}
+ } else {
+ return {reshow_diff;}
+ }
+}
+
+proc select_first_diff {} {
+ global ui_workdir
+
+ if {[find_next_diff $ui_workdir {} 1 {^_?U}] ||
+ [find_next_diff $ui_workdir {} 1 {[^O]$}]} {
+ next_diff
+ }
+}
+
+proc force_first_diff {} {
+ global current_diff_path
+
+ if {[info exists file_states($current_diff_path)]} {
+ set state [lindex $file_states($current_diff_path) 0]
+
+ if {[string index $state 1] ne {O}} return
+ }
+
+ select_first_diff
}
proc toggle_or_diff {w x y} {
@@ -1823,34 +2079,29 @@ proc toggle_or_diff {w x y} {
$ui_index tag remove in_sel 0.0 end
$ui_workdir tag remove in_sel 0.0 end
+ # Determine the state of the file
+ if {[info exists file_states($path)]} {
+ set state [lindex $file_states($path) 0]
+ } else {
+ set state {__}
+ }
+
+ # Restage the file, or simply show the diff
if {$col == 0 && $y > 1} {
- set i [expr {$lno-1}]
- set ll [expr {[llength $file_lists($w)]-1}]
+ # Conflicts need special handling
+ if {[string first {U} $state] >= 0} {
+ merge_stage_workdir $path $w $lno
+ return
+ }
- if {$i == $ll && $i == 0} {
- set after {reshow_diff;}
+ if {[string index $state 1] eq {O}} {
+ set mmask {}
} else {
- global next_diff_p next_diff_w next_diff_i
-
- set next_diff_w $w
-
- if {$i < $ll} {
- set i [expr {$i + 1}]
- set next_diff_i $i
- } else {
- set next_diff_i $i
- set i [expr {$i - 1}]
- }
-
- set next_diff_p [lindex $file_lists($w) $i]
-
- if {$next_diff_p ne {} && $current_diff_path ne {}} {
- set after {next_diff;}
- } else {
- set after {}
- }
+ set mmask {[^O]}
}
+ set after [next_diff_after_action $w $path $lno $mmask]
+
if {$w eq $ui_index} {
update_indexinfo \
"Unstaging [short_path $path] from commit" \
@@ -2091,29 +2342,39 @@ if {[is_enabled branch]} {
# -- Commit Menu
#
+proc commit_btn_caption {} {
+ if {[is_enabled nocommit]} {
+ return [mc "Done"]
+ } else {
+ return [mc Commit@@verb]
+ }
+}
+
if {[is_enabled multicommit] || [is_enabled singlecommit]} {
menu .mbar.commit
- .mbar.commit add radiobutton \
- -label [mc "New Commit"] \
- -command do_select_commit_type \
- -variable selected_commit_type \
- -value new
- lappend disable_on_lock \
- [list .mbar.commit entryconf [.mbar.commit index last] -state]
+ if {![is_enabled nocommit]} {
+ .mbar.commit add radiobutton \
+ -label [mc "New Commit"] \
+ -command do_select_commit_type \
+ -variable selected_commit_type \
+ -value new
+ lappend disable_on_lock \
+ [list .mbar.commit entryconf [.mbar.commit index last] -state]
- .mbar.commit add radiobutton \
- -label [mc "Amend Last Commit"] \
- -command do_select_commit_type \
- -variable selected_commit_type \
- -value amend
- lappend disable_on_lock \
- [list .mbar.commit entryconf [.mbar.commit index last] -state]
+ .mbar.commit add radiobutton \
+ -label [mc "Amend Last Commit"] \
+ -command do_select_commit_type \
+ -variable selected_commit_type \
+ -value amend
+ lappend disable_on_lock \
+ [list .mbar.commit entryconf [.mbar.commit index last] -state]
- .mbar.commit add separator
+ .mbar.commit add separator
+ }
.mbar.commit add command -label [mc Rescan] \
- -command do_rescan \
+ -command ui_do_rescan \
-accelerator F5
lappend disable_on_lock \
[list .mbar.commit entryconf [.mbar.commit index last] -state]
@@ -2152,11 +2413,13 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
.mbar.commit add separator
- .mbar.commit add command -label [mc "Sign Off"] \
- -command do_signoff \
- -accelerator $M1T-S
+ if {![is_enabled nocommit]} {
+ .mbar.commit add command -label [mc "Sign Off"] \
+ -command do_signoff \
+ -accelerator $M1T-S
+ }
- .mbar.commit add command -label [mc Commit@@verb] \
+ .mbar.commit add command -label [commit_btn_caption] \
-command do_commit \
-accelerator $M1T-Return
lappend disable_on_lock \
@@ -2281,10 +2544,15 @@ proc usage {} {
switch -- $subcommand {
browser -
blame {
- set subcommand_args {rev? path}
+ if {$subcommand eq "blame"} {
+ set subcommand_args {[--line=<num>] rev? path}
+ } else {
+ set subcommand_args {rev? path}
+ }
if {$argv eq {}} usage
set head {}
set path {}
+ set jump_spec {}
set is_path 0
foreach a $argv {
if {$is_path || [file exists $_prefix$a]} {
@@ -2298,6 +2566,9 @@ blame {
set path {}
}
set is_path 1
+ } elseif {[regexp {^--line=(\d+)$} $a a lnum]} {
+ if {$jump_spec ne {} || $head ne {}} usage
+ set jump_spec [list $lnum]
} elseif {$head eq {}} {
if {$head ne {}} usage
set head $a
@@ -2329,6 +2600,7 @@ blame {
switch -- $subcommand {
browser {
+ if {$jump_spec ne {}} usage
if {$head eq {}} {
if {$path ne {} && [file isdirectory $path]} {
set head $current_branch
@@ -2344,7 +2616,7 @@ blame {
puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
exit 1
}
- blame::new $head $path
+ blame::new $head $path $jump_spec
}
}
return
@@ -2460,7 +2732,7 @@ pack .vpane.lower.commarea.buttons.l -side top -fill x
pack .vpane.lower.commarea.buttons -side left -fill y
button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
- -command do_rescan
+ -command ui_do_rescan
pack .vpane.lower.commarea.buttons.rescan -side top -fill x
lappend disable_on_lock \
{.vpane.lower.commarea.buttons.rescan conf -state}
@@ -2471,19 +2743,23 @@ pack .vpane.lower.commarea.buttons.incall -side top -fill x
lappend disable_on_lock \
{.vpane.lower.commarea.buttons.incall conf -state}
-button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
- -command do_signoff
-pack .vpane.lower.commarea.buttons.signoff -side top -fill x
+if {![is_enabled nocommit]} {
+ button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
+ -command do_signoff
+ pack .vpane.lower.commarea.buttons.signoff -side top -fill x
+}
-button .vpane.lower.commarea.buttons.commit -text [mc Commit@@verb] \
+button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \
-command do_commit
pack .vpane.lower.commarea.buttons.commit -side top -fill x
lappend disable_on_lock \
{.vpane.lower.commarea.buttons.commit conf -state}
-button .vpane.lower.commarea.buttons.push -text [mc Push] \
- -command do_push_anywhere
-pack .vpane.lower.commarea.buttons.push -side top -fill x
+if {![is_enabled nocommit]} {
+ button .vpane.lower.commarea.buttons.push -text [mc Push] \
+ -command do_push_anywhere
+ pack .vpane.lower.commarea.buttons.push -side top -fill x
+}
# -- Commit Message Buffer
#
@@ -2491,20 +2767,24 @@ frame .vpane.lower.commarea.buffer
frame .vpane.lower.commarea.buffer.header
set ui_comm .vpane.lower.commarea.buffer.t
set ui_coml .vpane.lower.commarea.buffer.header.l
-radiobutton .vpane.lower.commarea.buffer.header.new \
- -text [mc "New Commit"] \
- -command do_select_commit_type \
- -variable selected_commit_type \
- -value new
-lappend disable_on_lock \
- [list .vpane.lower.commarea.buffer.header.new conf -state]
-radiobutton .vpane.lower.commarea.buffer.header.amend \
- -text [mc "Amend Last Commit"] \
- -command do_select_commit_type \
- -variable selected_commit_type \
- -value amend
-lappend disable_on_lock \
- [list .vpane.lower.commarea.buffer.header.amend conf -state]
+
+if {![is_enabled nocommit]} {
+ radiobutton .vpane.lower.commarea.buffer.header.new \
+ -text [mc "New Commit"] \
+ -command do_select_commit_type \
+ -variable selected_commit_type \
+ -value new
+ lappend disable_on_lock \
+ [list .vpane.lower.commarea.buffer.header.new conf -state]
+ radiobutton .vpane.lower.commarea.buffer.header.amend \
+ -text [mc "Amend Last Commit"] \
+ -command do_select_commit_type \
+ -variable selected_commit_type \
+ -value amend
+ lappend disable_on_lock \
+ [list .vpane.lower.commarea.buffer.header.amend conf -state]
+}
+
label $ui_coml \
-anchor w \
-justify left
@@ -2522,8 +2802,11 @@ proc trace_commit_type {varname args} {
}
trace add variable commit_type write trace_commit_type
pack $ui_coml -side left -fill x
-pack .vpane.lower.commarea.buffer.header.amend -side right
-pack .vpane.lower.commarea.buffer.header.new -side right
+
+if {![is_enabled nocommit]} {
+ pack .vpane.lower.commarea.buffer.header.amend -side right
+ pack .vpane.lower.commarea.buffer.header.new -side right
+}
text $ui_comm -background white -foreground black \
-borderwidth 1 \
@@ -2689,6 +2972,59 @@ $ui_diff tag raise sel
# -- Diff Body Context Menu
#
+
+proc create_common_diff_popup {ctxm} {
+ $ctxm add command \
+ -label [mc "Show Less Context"] \
+ -command show_less_context
+ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+ $ctxm add command \
+ -label [mc "Show More Context"] \
+ -command show_more_context
+ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+ $ctxm add separator
+ $ctxm add command \
+ -label [mc Refresh] \
+ -command reshow_diff
+ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+ $ctxm add command \
+ -label [mc Copy] \
+ -command {tk_textCopy $ui_diff}
+ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+ $ctxm add command \
+ -label [mc "Select All"] \
+ -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
+ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+ $ctxm add command \
+ -label [mc "Copy All"] \
+ -command {
+ $ui_diff tag add sel 0.0 end
+ tk_textCopy $ui_diff
+ $ui_diff tag remove sel 0.0 end
+ }
+ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+ $ctxm add separator
+ $ctxm add command \
+ -label [mc "Decrease Font Size"] \
+ -command {incr_font_size font_diff -1}
+ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+ $ctxm add command \
+ -label [mc "Increase Font Size"] \
+ -command {incr_font_size font_diff 1}
+ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+ $ctxm add separator
+ set emenu $ctxm.enc
+ menu $emenu
+ build_encoding_menu $emenu [list force_diff_encoding]
+ $ctxm add cascade \
+ -label [mc "Encoding"] \
+ -menu $emenu
+ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+ $ctxm add separator
+ $ctxm add command -label [mc "Options..."] \
+ -command do_options
+}
+
set ctxm .vpane.lower.diff.body.ctxm
menu $ctxm -tearoff 0
$ctxm add command \
@@ -2702,71 +3038,65 @@ $ctxm add command \
set ui_diff_applyline [$ctxm index last]
lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
$ctxm add separator
-$ctxm add command \
- -label [mc "Show Less Context"] \
- -command show_less_context
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add command \
- -label [mc "Show More Context"] \
- -command show_more_context
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add separator
-$ctxm add command \
- -label [mc Refresh] \
- -command reshow_diff
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add command \
- -label [mc Copy] \
- -command {tk_textCopy $ui_diff}
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add command \
- -label [mc "Select All"] \
- -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add command \
- -label [mc "Copy All"] \
- -command {
- $ui_diff tag add sel 0.0 end
- tk_textCopy $ui_diff
- $ui_diff tag remove sel 0.0 end
- }
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add separator
-$ctxm add command \
- -label [mc "Decrease Font Size"] \
- -command {incr_font_size font_diff -1}
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add command \
- -label [mc "Increase Font Size"] \
- -command {incr_font_size font_diff 1}
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add separator
-$ctxm add command -label [mc "Options..."] \
- -command do_options
-proc popup_diff_menu {ctxm x y X Y} {
+create_common_diff_popup $ctxm
+
+set ctxmmg .vpane.lower.diff.body.ctxmmg
+menu $ctxmmg -tearoff 0
+$ctxmmg add command \
+ -label [mc "Run Merge Tool"] \
+ -command {merge_resolve_tool}
+lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
+$ctxmmg add separator
+$ctxmmg add command \
+ -label [mc "Use Remote Version"] \
+ -command {merge_resolve_one 3}
+lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
+$ctxmmg add command \
+ -label [mc "Use Local Version"] \
+ -command {merge_resolve_one 2}
+lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
+$ctxmmg add command \
+ -label [mc "Revert To Base"] \
+ -command {merge_resolve_one 1}
+lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
+$ctxmmg add separator
+create_common_diff_popup $ctxmmg
+
+proc popup_diff_menu {ctxm ctxmmg x y X Y} {
global current_diff_path file_states
set ::cursorX $x
set ::cursorY $y
- if {$::ui_index eq $::current_diff_side} {
- set l [mc "Unstage Hunk From Commit"]
- set t [mc "Unstage Line From Commit"]
+ if {[info exists file_states($current_diff_path)]} {
+ set state [lindex $file_states($current_diff_path) 0]
} else {
- set l [mc "Stage Hunk For Commit"]
- set t [mc "Stage Line For Commit"]
- }
- if {$::is_3way_diff
- || $current_diff_path eq {}
- || ![info exists file_states($current_diff_path)]
- || {_O} eq [lindex $file_states($current_diff_path) 0]} {
- set s disabled
+ set state {__}
+ }
+ if {[string first {U} $state] >= 0} {
+ tk_popup $ctxmmg $X $Y
} else {
- set s normal
+ if {$::ui_index eq $::current_diff_side} {
+ set l [mc "Unstage Hunk From Commit"]
+ set t [mc "Unstage Line From Commit"]
+ } else {
+ set l [mc "Stage Hunk For Commit"]
+ set t [mc "Stage Line For Commit"]
+ }
+ if {$::is_3way_diff
+ || $current_diff_path eq {}
+ || {__} eq $state
+ || {_O} eq $state
+ || {_T} eq $state
+ || {T_} eq $state} {
+ set s disabled
+ } else {
+ set s normal
+ }
+ $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
+ $ctxm entryconf $::ui_diff_applyline -state $s -label $t
+ tk_popup $ctxm $X $Y
}
- $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
- $ctxm entryconf $::ui_diff_applyline -state $s -label $t
- tk_popup $ctxm $X $Y
}
-bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
+bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg %x %y %X %Y]
# -- Status Bar
#
@@ -2842,9 +3172,9 @@ if {[is_enabled transport]} {
bind . <$M1B-Key-P> do_push_anywhere
}
-bind . <Key-F5> do_rescan
-bind . <$M1B-Key-r> do_rescan
-bind . <$M1B-Key-R> do_rescan
+bind . <Key-F5> ui_do_rescan
+bind . <$M1B-Key-r> ui_do_rescan
+bind . <$M1B-Key-R> ui_do_rescan
bind . <$M1B-Key-s> do_signoff
bind . <$M1B-Key-S> do_signoff
bind . <$M1B-Key-t> do_add_selection
@@ -3022,7 +3352,20 @@ lock_index begin-read
if {![winfo ismapped .]} {
wm deiconify .
}
-after 1 do_rescan
+after 1 {
+ if {[is_enabled initialamend]} {
+ force_amend
+ } else {
+ do_rescan
+ }
+
+ if {[is_enabled nocommitmsg]} {
+ $ui_comm configure -state disabled -background gray
+ }
+}
if {[is_enabled multicommit]} {
after 1000 hint_gc
}
+if {[is_enabled retcode]} {
+ bind . <Destroy> {+terminate_me %W}
+}
diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl
index b6e42cb..eb61374 100644
--- a/git-gui/lib/blame.tcl
+++ b/git-gui/lib/blame.tcl
@@ -58,7 +58,7 @@ field tooltip_t {} ; # Text widget in $tooltip_wm
field tooltip_timer {} ; # Current timer event for our tooltip
field tooltip_commit {} ; # Commit(s) in tooltip
-constructor new {i_commit i_path} {
+constructor new {i_commit i_path i_jump} {
global cursor_ptr
variable active_color
variable group_colors
@@ -256,9 +256,22 @@ constructor new {i_commit i_path} {
$w.ctxm add command \
-label [mc "Copy Commit"] \
-command [cb _copycommit]
+ $w.ctxm add separator
+ menu $w.ctxm.enc
+ build_encoding_menu $w.ctxm.enc [cb _setencoding]
+ $w.ctxm add cascade \
+ -label [mc "Encoding"] \
+ -menu $w.ctxm.enc
$w.ctxm add command \
-label [mc "Do Full Copy Detection"] \
-command [cb _fullcopyblame]
+ $w.ctxm add separator
+ $w.ctxm add command \
+ -label [mc "Show History Context"] \
+ -command [cb _gitkcommit]
+ $w.ctxm add command \
+ -label [mc "Blame Parent Commit"] \
+ -command [cb _blameparent]
foreach i $w_columns {
for {set g 0} {$g < [llength $group_colors]} {incr g} {
@@ -332,7 +345,7 @@ constructor new {i_commit i_path} {
wm protocol $top WM_DELETE_WINDOW "destroy $top"
bind $top <Destroy> [cb _kill]
- _load $this {}
+ _load $this $i_jump
}
method _kill {} {
@@ -393,7 +406,10 @@ method _load {jump} {
} else {
set fd [git_read cat-file blob "$commit:$path"]
}
- fconfigure $fd -blocking 0 -translation lf -encoding binary
+ fconfigure $fd \
+ -blocking 0 \
+ -translation lf \
+ -encoding [get_path_encoding $path]
fileevent $fd readable [cb _read_file $fd $jump]
set current_fd $fd
}
@@ -502,7 +518,7 @@ method _exec_blame {cur_w cur_d options cur_s} {
}
lappend options -- $path
set fd [eval git_read --nice blame $options]
- fconfigure $fd -blocking 0 -translation lf -encoding binary
+ fconfigure $fd -blocking 0 -translation lf -encoding utf-8
fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d]
set current_fd $fd
set blame_lines 0
@@ -782,24 +798,42 @@ method _click {cur_w pos} {
_showcommit $this $cur_w $lno
}
+method _setencoding {enc} {
+ force_path_encoding $path $enc
+ _load $this [list \
+ $highlight_column \
+ $highlight_line \
+ [lindex [$w_file xview] 0] \
+ [lindex [$w_file yview] 0] \
+ ]
+}
+
method _load_commit {cur_w cur_d pos} {
upvar #0 $cur_d line_data
set lno [lindex [split [$cur_w index $pos] .] 0]
set dat [lindex $line_data $lno]
if {$dat ne {}} {
- lappend history [list \
- $commit $path \
- $highlight_column \
- $highlight_line \
- [lindex [$w_file xview] 0] \
- [lindex [$w_file yview] 0] \
- ]
- set commit [lindex $dat 0]
- set path [lindex $dat 1]
- _load $this [list [lindex $dat 2]]
+ _load_new_commit $this \
+ [lindex $dat 0] \
+ [lindex $dat 1] \
+ [list [lindex $dat 2]]
}
}
+method _load_new_commit {new_commit new_path jump} {
+ lappend history [list \
+ $commit $path \
+ $highlight_column \
+ $highlight_line \
+ [lindex [$w_file xview] 0] \
+ [lindex [$w_file yview] 0] \
+ ]
+
+ set commit $new_commit
+ set path $new_path
+ _load $this $jump
+}
+
method _showcommit {cur_w lno} {
global repo_config
variable active_color
@@ -867,12 +901,6 @@ method _showcommit {cur_w lno} {
set enc [tcl_encoding $enc]
if {$enc ne {}} {
set msg [encoding convertfrom $enc $msg]
- set author_name [encoding convertfrom $enc $author_name]
- set committer_name [encoding convertfrom $enc $committer_name]
- set header($cmit,author) $author_name
- set header($cmit,committer) $committer_name
- set header($cmit,summary) \
- [encoding convertfrom $enc $header($cmit,summary)]
}
set msg [string trim $msg]
}
@@ -905,10 +933,14 @@ method _showcommit {cur_w lno} {
}
}
-method _copycommit {} {
+method _get_click_amov_info {} {
set pos @$::cursorX,$::cursorY
set lno [lindex [split [$::cursorW index $pos] .] 0]
- set dat [lindex $amov_data $lno]
+ return [lindex $amov_data $lno]
+}
+
+method _copycommit {} {
+ set dat [_get_click_amov_info $this]
if {$dat ne {}} {
clipboard clear
clipboard append \
@@ -918,6 +950,147 @@ method _copycommit {} {
}
}
+method _format_offset_date {base offset} {
+ set exval [expr {$base + $offset*24*60*60}]
+ return [clock format $exval -format {%Y-%m-%d}]
+}
+
+method _gitkcommit {} {
+ global nullid
+
+ set dat [_get_click_amov_info $this]
+ if {$dat ne {}} {
+ set cmit [lindex $dat 0]
+
+ # If the line belongs to the working copy, use HEAD instead
+ if {$cmit eq $nullid} {
+ if {[catch {set cmit [git rev-parse --verify HEAD]} err]} {
+ error_popup [strcat [mc "Cannot find HEAD commit:"] "\n\n$err"]
+ return;
+ }
+ }
+
+ set radius [get_config gui.blamehistoryctx]
+ set cmdline [list --select-commit=$cmit]
+
+ if {$radius > 0} {
+ set author_time {}
+ set committer_time {}
+
+ catch {set author_time $header($cmit,author-time)}
+ catch {set committer_time $header($cmit,committer-time)}
+
+ if {$committer_time eq {}} {
+ set committer_time $author_time
+ }
+
+ set after_time [_format_offset_date $this $committer_time [expr {-$radius}]]
+ set before_time [_format_offset_date $this $committer_time $radius]
+
+ lappend cmdline --after=$after_time --before=$before_time
+ }
+
+ lappend cmdline $cmit
+
+ set base_rev "HEAD"
+ if {$commit ne {}} {
+ set base_rev $commit
+ }
+
+ if {$base_rev ne $cmit} {
+ lappend cmdline $base_rev
+ }
+
+ do_gitk $cmdline
+ }
+}
+
+method _blameparent {} {
+ global nullid
+
+ set dat [_get_click_amov_info $this]
+ if {$dat ne {}} {
+ set cmit [lindex $dat 0]
+ set new_path [lindex $dat 1]
+
+ # Allow using Blame Parent on lines modified in the working copy
+ if {$cmit eq $nullid} {
+ set parent_ref "HEAD"
+ } else {
+ set parent_ref "$cmit^"
+ }
+ if {[catch {set cparent [git rev-parse --verify $parent_ref]} err]} {
+ error_popup [strcat [mc "Cannot find parent commit:"] "\n\n$err"]
+ return;
+ }
+
+ _kill $this
+
+ # Generate a diff between the commit and its parent,
+ # and use the hunks to update the line number.
+ # Request zero context to simplify calculations.
+ if {$cmit eq $nullid} {
+ set diffcmd [list diff-index --unified=0 $cparent -- $new_path]
+ } else {
+ set diffcmd [list diff-tree --unified=0 $cparent $cmit -- $new_path]
+ }
+ if {[catch {set fd [eval git_read $diffcmd]} err]} {
+ $status stop [mc "Unable to display parent"]
+ error_popup [strcat [mc "Error loading diff:"] "\n\n$err"]
+ return
+ }
+
+ set r_orig_line [lindex $dat 2]
+
+ fconfigure $fd \
+ -blocking 0 \
+ -encoding binary \
+ -translation binary
+ fileevent $fd readable [cb _read_diff_load_commit \
+ $fd $cparent $new_path $r_orig_line]
+ set current_fd $fd
+ }
+}
+
+method _read_diff_load_commit {fd cparent new_path tline} {
+ if {$fd ne $current_fd} {
+ catch {close $fd}
+ return
+ }
+
+ while {[gets $fd line] >= 0} {
+ if {[regexp {^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@} $line line \
+ old_line osz old_size new_line nsz new_size]} {
+
+ if {$osz eq {}} { set old_size 1 }
+ if {$nsz eq {}} { set new_size 1 }
+
+ if {$new_line <= $tline} {
+ if {[expr {$new_line + $new_size}] > $tline} {
+ # Target line within the hunk
+ set line_shift [expr {
+ ($new_size-$old_size)*($tline-$new_line)/$new_size
+ }]
+ } else {
+ set line_shift [expr {$new_size-$old_size}]
+ }
+
+ set r_orig_line [expr {$r_orig_line - $line_shift}]
+ }
+ }
+ }
+
+ if {[eof $fd]} {
+ close $fd;
+ set current_fd {}
+
+ _load_new_commit $this \
+ $cparent \
+ $new_path \
+ [list $r_orig_line]
+ }
+} ifdeleted { catch {close $fd} }
+
method _show_tooltip {cur_w pos} {
if {$tooltip_wm ne {}} {
_open_tooltip $this $cur_w
diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl
index ab470d1..0410cc6 100644
--- a/git-gui/lib/browser.tcl
+++ b/git-gui/lib/browser.tcl
@@ -151,7 +151,7 @@ method _enter {} {
append p [lindex $n 1]
}
append p $name
- blame::new $browser_commit $p
+ blame::new $browser_commit $p {}
}
}
}
diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl
index 40a7103..3345149 100644
--- a/git-gui/lib/commit.tcl
+++ b/git-gui/lib/commit.tcl
@@ -149,7 +149,9 @@ The rescan will be automatically started now.
_? {continue}
A? -
D? -
+ T_ -
M? {set files_ready 1}
+ _U -
U? {
error_popup [mc "Unmerged files cannot be committed.
@@ -166,7 +168,7 @@ File %s cannot be committed by this program.
}
}
}
- if {!$files_ready && ![string match *merge $curType]} {
+ if {!$files_ready && ![string match *merge $curType] && ![is_enabled nocommit]} {
info_popup [mc "No changes to commit.
You must stage at least 1 file before you can commit.
@@ -175,6 +177,8 @@ You must stage at least 1 file before you can commit.
return
}
+ if {[is_enabled nocommitmsg]} { do_quit 0 }
+
# -- A message is required.
#
set msg [string trim [$ui_comm get 1.0 end]]
@@ -210,6 +214,8 @@ A good commit message has the following format:
puts $msg_wt $msg
close $msg_wt
+ if {[is_enabled nocommit]} { do_quit 0 }
+
# -- Run the pre-commit hook.
#
set fd_ph [githook_read pre-commit]
@@ -408,7 +414,7 @@ A rescan will be automatically started now.
set ::GITGUI_BCK_exists 0
}
- if {[is_enabled singlecommit]} do_quit
+ if {[is_enabled singlecommit]} { do_quit 0 }
# -- Update in memory status
#
@@ -428,6 +434,7 @@ A rescan will be automatically started now.
__ -
A_ -
M_ -
+ T_ -
D_ {
unset file_states($path)
catch {unset selected_paths($path)}
diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl
index 1970b60..abe502d 100644
--- a/git-gui/lib/diff.tcl
+++ b/git-gui/lib/diff.tcl
@@ -24,16 +24,31 @@ proc reshow_diff {} {
set p $current_diff_path
if {$p eq {}} {
# No diff is being shown.
- } elseif {$current_diff_side eq {}
- || [catch {set s $file_states($p)}]
- || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
+ } elseif {$current_diff_side eq {}} {
clear_diff
+ } elseif {[catch {set s $file_states($p)}]
+ || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
+
+ if {[find_next_diff $current_diff_side $p {} {[^O]}]} {
+ next_diff
+ } else {
+ clear_diff
+ }
} else {
set save_pos [lindex [$ui_diff yview] 0]
show_diff $p $current_diff_side {} $save_pos
}
}
+proc force_diff_encoding {enc} {
+ global current_diff_path
+
+ if {$current_diff_path ne {}} {
+ force_path_encoding $current_diff_path $enc
+ reshow_diff
+ }
+}
+
proc handle_empty_diff {} {
global current_diff_path file_states file_lists
@@ -54,11 +69,12 @@ A rescan will be automatically started to find other files which may have the sa
rescan ui_ready 0
}
-proc show_diff {path w {lno {}} {scroll_pos {}}} {
+proc show_diff {path w {lno {}} {scroll_pos {}} {callback {}}} {
global file_states file_lists
- global is_3way_diff diff_active repo_config
+ global is_3way_diff is_conflict_diff diff_active repo_config
global ui_diff ui_index ui_workdir
global current_diff_path current_diff_side current_diff_header
+ global current_diff_queue
if {$diff_active || ![lock_index read]} return
@@ -71,17 +87,80 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
}
if {$lno >= 1} {
$w tag add in_diff $lno.0 [expr {$lno + 1}].0
+ $w see $lno.0
}
set s $file_states($path)
set m [lindex $s 0]
- set is_3way_diff 0
- set diff_active 1
+ set is_conflict_diff 0
set current_diff_path $path
set current_diff_side $w
- set current_diff_header {}
+ set current_diff_queue {}
ui_status [mc "Loading diff of %s..." [escape_path $path]]
+ set cont_info [list $scroll_pos $callback]
+
+ if {[string first {U} $m] >= 0} {
+ merge_load_stages $path [list show_unmerged_diff $cont_info]
+ } elseif {$m eq {_O}} {
+ show_other_diff $path $w $m $cont_info
+ } else {
+ start_show_diff $cont_info
+ }
+}
+
+proc show_unmerged_diff {cont_info} {
+ global current_diff_path current_diff_side
+ global merge_stages ui_diff is_conflict_diff
+ global current_diff_queue
+
+ if {$merge_stages(2) eq {}} {
+ set is_conflict_diff 1
+ lappend current_diff_queue \
+ [list "LOCAL: deleted\nREMOTE:\n" d======= \
+ [list ":1:$current_diff_path" ":3:$current_diff_path"]]
+ } elseif {$merge_stages(3) eq {}} {
+ set is_conflict_diff 1
+ lappend current_diff_queue \
+ [list "REMOTE: deleted\nLOCAL:\n" d======= \
+ [list ":1:$current_diff_path" ":2:$current_diff_path"]]
+ } elseif {[lindex $merge_stages(1) 0] eq {120000}
+ || [lindex $merge_stages(2) 0] eq {120000}
+ || [lindex $merge_stages(3) 0] eq {120000}} {
+ set is_conflict_diff 1
+ lappend current_diff_queue \
+ [list "LOCAL:\n" d======= \
+ [list ":1:$current_diff_path" ":2:$current_diff_path"]]
+ lappend current_diff_queue \
+ [list "REMOTE:\n" d======= \
+ [list ":1:$current_diff_path" ":3:$current_diff_path"]]
+ } else {
+ start_show_diff $cont_info
+ return
+ }
+
+ advance_diff_queue $cont_info
+}
+
+proc advance_diff_queue {cont_info} {
+ global current_diff_queue ui_diff
+
+ set item [lindex $current_diff_queue 0]
+ set current_diff_queue [lrange $current_diff_queue 1 end]
+
+ $ui_diff conf -state normal
+ $ui_diff insert end [lindex $item 0] [lindex $item 1]
+ $ui_diff conf -state disabled
+
+ start_show_diff $cont_info [lindex $item 2]
+}
+
+proc show_other_diff {path w m cont_info} {
+ global file_states file_lists
+ global is_3way_diff diff_active repo_config
+ global ui_diff ui_index ui_workdir
+ global current_diff_path current_diff_side current_diff_header
+
# - Git won't give us the diff, there's nothing to compare to!
#
if {$m eq {_O}} {
@@ -101,7 +180,9 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
}
file {
set fd [open $path r]
- fconfigure $fd -eofchar {}
+ fconfigure $fd \
+ -eofchar {} \
+ -encoding [get_path_encoding $path]
set content [read $fd $max_sz]
close $fd
set sz [file size $path]
@@ -153,20 +234,41 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
$ui_diff conf -state disabled
set diff_active 0
unlock_index
+ set scroll_pos [lindex $cont_info 0]
if {$scroll_pos ne {}} {
update
$ui_diff yview moveto $scroll_pos
}
ui_ready
+ set callback [lindex $cont_info 1]
+ if {$callback ne {}} {
+ eval $callback
+ }
return
}
+}
+
+proc start_show_diff {cont_info {add_opts {}}} {
+ global file_states file_lists
+ global is_3way_diff diff_active repo_config
+ global ui_diff ui_index ui_workdir
+ global current_diff_path current_diff_side current_diff_header
+
+ set path $current_diff_path
+ set w $current_diff_side
+
+ set s $file_states($path)
+ set m [lindex $s 0]
+ set is_3way_diff 0
+ set diff_active 1
+ set current_diff_header {}
set cmd [list]
if {$w eq $ui_index} {
lappend cmd diff-index
lappend cmd --cached
} elseif {$w eq $ui_workdir} {
- if {[string index $m 0] eq {U}} {
+ if {[string first {U} $m] >= 0} {
lappend cmd diff
} else {
lappend cmd diff-files
@@ -181,8 +283,12 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
if {$w eq $ui_index} {
lappend cmd [PARENT]
}
- lappend cmd --
- lappend cmd $path
+ if {$add_opts ne {}} {
+ eval lappend cmd $add_opts
+ } else {
+ lappend cmd --
+ lappend cmd $path
+ }
if {[catch {set fd [eval git_read --nice $cmd]} err]} {
set diff_active 0
@@ -195,14 +301,15 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
set ::current_diff_inheader 1
fconfigure $fd \
-blocking 0 \
- -encoding binary \
- -translation binary
- fileevent $fd readable [list read_diff $fd $scroll_pos]
+ -encoding [get_path_encoding $path] \
+ -translation lf
+ fileevent $fd readable [list read_diff $fd $cont_info]
}
-proc read_diff {fd scroll_pos} {
+proc read_diff {fd cont_info} {
global ui_diff diff_active
- global is_3way_diff current_diff_header
+ global is_3way_diff is_conflict_diff current_diff_header
+ global current_diff_queue
$ui_diff conf -state normal
while {[gets $fd line] >= 0} {
@@ -249,6 +356,7 @@ proc read_diff {fd scroll_pos} {
{--} {set tags d_--}
{++} {
if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} {
+ set is_conflict_diff 1
set line [string replace $line 0 1 { }]
set tags d$op
} else {
@@ -268,6 +376,7 @@ proc read_diff {fd scroll_pos} {
{-} {set tags d_-}
{+} {
if {[regexp {^\+([<>]{7} |={7})} $line _g op]} {
+ set is_conflict_diff 1
set line [string replace $line 0 0 { }]
set tags d$op
} else {
@@ -290,8 +399,15 @@ proc read_diff {fd scroll_pos} {
if {[eof $fd]} {
close $fd
+
+ if {$current_diff_queue ne {}} {
+ advance_diff_queue $cont_info
+ return
+ }
+
set diff_active 0
unlock_index
+ set scroll_pos [lindex $cont_info 0]
if {$scroll_pos ne {}} {
update
$ui_diff yview moveto $scroll_pos
@@ -301,6 +417,10 @@ proc read_diff {fd scroll_pos} {
if {[$ui_diff index end] eq {2.0}} {
handle_empty_diff
}
+ set callback [lindex $cont_info 1]
+ if {$callback ne {}} {
+ eval $callback
+ }
}
}
@@ -341,8 +461,9 @@ proc apply_hunk {x y} {
}
if {[catch {
+ set enc [get_path_encoding $current_diff_path]
set p [eval git_write $apply_cmd]
- fconfigure $p -translation binary -encoding binary
+ fconfigure $p -translation binary -encoding $enc
puts -nonewline $p $current_diff_header
puts -nonewline $p [$ui_diff get $s_lno $e_lno]
close $p} err]} {
@@ -370,10 +491,9 @@ proc apply_hunk {x y} {
}
unlock_index
display_file $current_diff_path $mi
+ # This should trigger shift to the next changed file
if {$o eq {_}} {
- clear_diff
- } else {
- set current_diff_path $current_diff_path
+ reshow_diff
}
}
@@ -511,8 +631,9 @@ proc apply_line {x y} {
set patch "@@ -$hln,$n +$hln,[eval expr $n $sign 1] @@\n$patch"
if {[catch {
+ set enc [get_path_encoding $current_diff_path]
set p [eval git_write $apply_cmd]
- fconfigure $p -translation binary -encoding binary
+ fconfigure $p -translation binary -encoding $enc
puts -nonewline $p $current_diff_header
puts -nonewline $p $patch
close $p} err]} {
diff --git a/git-gui/lib/encoding.tcl b/git-gui/lib/encoding.tcl
index 7f06b0d..32668fc 100644
--- a/git-gui/lib/encoding.tcl
+++ b/git-gui/lib/encoding.tcl
@@ -206,7 +206,7 @@ set encoding_aliases {
{ ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
{ GBK CP936 MS936 windows-936 }
{ JIS_Encoding csJISEncoding }
- { Shift_JIS MS_Kanji csShiftJIS }
+ { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
{ Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
EUC-JP }
{ Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
@@ -240,37 +240,227 @@ set encoding_aliases {
{ Big5 csBig5 }
}
+set encoding_groups {
+ {"" ""
+ {"Unicode" UTF-8}
+ {"Western" ISO-8859-1}}
+ {we "West European"
+ {"Western" ISO-8859-15 CP-437 CP-850 MacRoman CP-1252 Windows-1252}
+ {"Celtic" ISO-8859-14}
+ {"Greek" ISO-8859-14 ISO-8859-7 CP-737 CP-869 MacGreek CP-1253 Windows-1253}
+ {"Icelandic" MacIceland MacIcelandic CP-861}
+ {"Nordic" ISO-8859-10 CP-865}
+ {"Portuguese" CP-860}
+ {"South European" ISO-8859-3}}
+ {ee "East European"
+ {"Baltic" CP-775 ISO-8859-4 ISO-8859-13 CP-1257 Windows-1257}
+ {"Central European" CP-852 ISO-8859-2 MacCE CP-1250 Windows-1250}
+ {"Croatian" MacCroatian}
+ {"Cyrillic" CP-855 ISO-8859-5 ISO-IR-111 KOI8-R MacCyrillic CP-1251 Windows-1251}
+ {"Russian" CP-866}
+ {"Ukrainian" KOI8-U MacUkraine MacUkrainian}
+ {"Romanian" ISO-8859-16 MacRomania MacRomanian}}
+ {ea "East Asian"
+ {"Generic" ISO-2022}
+ {"Chinese Simplified" GB2312 GB1988 GB12345 GB2312-RAW GBK EUC-CN GB18030 HZ ISO-2022-CN}
+ {"Chinese Traditional" Big5 Big5-HKSCS EUC-TW CP-950}
+ {"Japanese" EUC-JP ISO-2022-JP Shift-JIS JIS-0212 JIS-0208 JIS-0201 CP-932 MacJapan}
+ {"Korean" EUC-KR UHC JOHAB ISO-2022-KR CP-949 KSC5601}}
+ {sa "SE & SW Asian"
+ {"Armenian" ARMSCII-8}
+ {"Georgian" GEOSTD8}
+ {"Thai" TIS-620 ISO-8859-11 CP-874 Windows-874 MacThai}
+ {"Turkish" CP-857 CP857 ISO-8859-9 MacTurkish CP-1254 Windows-1254}
+ {"Vietnamese" TCVN VISCII VPS CP-1258 Windows-1258}
+ {"Hindi" MacDevanagari}
+ {"Gujarati" MacGujarati}
+ {"Gurmukhi" MacGurmukhi}}
+ {me "Middle Eastern"
+ {"Arabic" ISO-8859-6 Windows-1256 CP-1256 CP-864 MacArabic}
+ {"Farsi" MacFarsi}
+ {"Hebrew" ISO-8859-8-I Windows-1255 CP-1255 ISO-8859-8 CP-862 MacHebrew}}
+ {mi "Misc"
+ {"7-bit" ASCII}
+ {"16-bit" Unicode}
+ {"Legacy" CP-863 EBCDIC}
+ {"Symbol" Symbol Dingbats MacDingbats MacCentEuro}}
+}
+
+proc build_encoding_table {} {
+ global encoding_aliases encoding_lookup_table
+
+ # Prepare the lookup list; cannot use lsort -nocase because
+ # of compatibility issues with older Tcl (e.g. in msysgit)
+ set names [list]
+ foreach item [encoding names] {
+ lappend names [list [string tolower $item] $item]
+ }
+ set names [lsort -ascii -index 0 $names]
+ # neither can we use lsearch -index
+ set lnames [list]
+ foreach item $names {
+ lappend lnames [lindex $item 0]
+ }
+
+ foreach grp $encoding_aliases {
+ set target {}
+ foreach item $grp {
+ set i [lsearch -sorted -ascii $lnames \
+ [string tolower $item]]
+ if {$i >= 0} {
+ set target [lindex $names $i 1]
+ break
+ }
+ }
+ if {$target eq {}} continue
+ foreach item $grp {
+ set encoding_lookup_table([string tolower $item]) $target
+ }
+ }
+
+ foreach item $names {
+ set encoding_lookup_table([lindex $item 0]) [lindex $item 1]
+ }
+}
+
proc tcl_encoding {enc} {
- global encoding_aliases
- set names [encoding names]
- set lcnames [string tolower $names]
- set enc [string tolower $enc]
- set i [lsearch -exact $lcnames $enc]
- if {$i < 0} {
- # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
- if {[regsub {^iso[-_]} $enc iso encx]} {
- set i [lsearch -exact $lcnames $encx]
+ global encoding_lookup_table
+ if {$enc eq {}} {
+ return {}
+ }
+ if {![info exists encoding_lookup_table]} {
+ build_encoding_table
+ }
+ set enc [string tolower $enc]
+ if {![info exists encoding_lookup_table($enc)]} {
+ # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
+ if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
+ set enc $encx
+ }
+ }
+ if {[info exists encoding_lookup_table($enc)]} {
+ return $encoding_lookup_table($enc)
+ } else {
+ return {}
+ }
+}
+
+proc force_path_encoding {path enc} {
+ global path_encoding_overrides last_encoding_override
+
+ set enc [tcl_encoding $enc]
+ if {$enc eq {}} {
+ catch { unset last_encoding_override }
+ catch { unset path_encoding_overrides($path) }
+ } else {
+ set last_encoding_override $enc
+ if {$path ne {}} {
+ set path_encoding_overrides($path) $enc
+ }
+ }
+}
+
+proc get_path_encoding {path} {
+ global path_encoding_overrides last_encoding_override
+
+ if {[info exists last_encoding_override]} {
+ set tcl_enc $last_encoding_override
+ } else {
+ set tcl_enc [tcl_encoding [get_config gui.encoding]]
}
- }
- if {$i < 0} {
- foreach l $encoding_aliases {
- set ll [string tolower $l]
- if {[lsearch -exact $ll $enc] < 0} continue
- # look through the aliases for one that tcl knows about
- foreach e $ll {
- set i [lsearch -exact $lcnames $e]
- if {$i < 0} {
- if {[regsub {^iso[-_]} $e iso ex]} {
- set i [lsearch -exact $lcnames $ex]
- }
+ if {$tcl_enc eq {}} {
+ set tcl_enc [encoding system]
+ }
+ if {$path ne {}} {
+ if {[info exists path_encoding_overrides($path)]} {
+ set enc2 $path_encoding_overrides($path)
+ } else {
+ set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
+ }
+ if {$enc2 ne {}} {
+ set tcl_enc $enc2
+ }
+ }
+ return $tcl_enc
+}
+
+proc build_encoding_submenu {parent grp cmd} {
+ global used_encodings
+
+ set mid [lindex $grp 0]
+ set gname [mc [lindex $grp 1]]
+
+ set smenu {}
+ foreach subset [lrange $grp 2 end] {
+ set name [mc [lindex $subset 0]]
+
+ foreach enc [lrange $subset 1 end] {
+ set tcl_enc [tcl_encoding $enc]
+ if {$tcl_enc eq {}} continue
+
+ if {$smenu eq {}} {
+ if {$mid eq {}} {
+ set smenu $parent
+ } else {
+ set smenu "$parent.$mid"
+ menu $smenu
+ $parent add cascade \
+ -label $gname \
+ -menu $smenu
+ }
+ }
+
+ if {$name ne {}} {
+ set lbl "$name ($enc)"
+ } else {
+ set lbl $enc
+ }
+ $smenu add command \
+ -label $lbl \
+ -command [concat $cmd [list $tcl_enc]]
+
+ lappend used_encodings $tcl_enc
+ }
+ }
+}
+
+proc popup_btn_menu {m b} {
+ tk_popup $m [winfo pointerx $b] [winfo pointery $b]
+}
+
+proc build_encoding_menu {emenu cmd {nodef 0}} {
+ $emenu configure -postcommand \
+ [list do_build_encoding_menu $emenu $cmd $nodef]
+}
+
+proc do_build_encoding_menu {emenu cmd {nodef 0}} {
+ global used_encodings encoding_groups
+
+ $emenu configure -postcommand {}
+
+ if {!$nodef} {
+ $emenu add command \
+ -label [mc "Default"] \
+ -command [concat $cmd [list {}]]
+ }
+ set sysenc [encoding system]
+ $emenu add command \
+ -label [mc "System (%s)" $sysenc] \
+ -command [concat $cmd [list $sysenc]]
+
+ # Main encoding tree
+ set used_encodings [list identity]
+ $emenu add separator
+ foreach grp $encoding_groups {
+ build_encoding_submenu $emenu $grp $cmd
+ }
+
+ # Add unclassified encodings
+ set unused_grp [list [mc Other]]
+ foreach enc [encoding names] {
+ if {[lsearch -exact $used_encodings $enc] < 0} {
+ lappend unused_grp $enc
}
- if {$i >= 0} break
- }
- break
}
- }
- if {$i >= 0} {
- return [lindex $names $i]
- }
- return {}
+ build_encoding_submenu $emenu [list other [mc Other] $unused_grp] $cmd
}
diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl
index 3c1fce7..b045219 100644
--- a/git-gui/lib/index.tcl
+++ b/git-gui/lib/index.tcl
@@ -99,6 +99,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch after} {
switch -glob -- [lindex $s 0] {
A? {set new _O}
M? {set new _M}
+ T_ {set new _T}
D_ {set new _D}
D? {set new _?}
?? {continue}
@@ -162,6 +163,8 @@ proc write_update_index {fd pathList totalCnt batch after} {
?D {set new D_}
_O -
AM {set new A_}
+ _T {set new T_}
+ _U -
U? {
if {[file exists $path]} {
set new M_
@@ -231,6 +234,7 @@ proc write_checkout_index {fd pathList totalCnt batch after} {
switch -glob -- [lindex $file_states($path) 0] {
U? {continue}
?M -
+ ?T -
?D {
puts -nonewline $fd "[encoding convertto $path]\0"
display_file $path ?_
@@ -252,6 +256,7 @@ proc unstage_helper {txt paths} {
switch -glob -- [lindex $file_states($path) 0] {
A? -
M? -
+ T_ -
D? {
lappend pathList $path
if {$path eq $current_diff_path} {
@@ -296,6 +301,7 @@ proc add_helper {txt paths} {
_O -
?M -
?D -
+ ?T -
U? {
lappend pathList $path
if {$path eq $current_diff_path} {
@@ -336,6 +342,7 @@ proc do_add_all {} {
switch -glob -- [lindex $file_states($path) 0] {
U? {continue}
?M -
+ ?T -
?D {lappend paths $path}
}
}
@@ -353,6 +360,7 @@ proc revert_helper {txt paths} {
switch -glob -- [lindex $file_states($path) 0] {
U? {continue}
?M -
+ ?T -
?D {
lappend pathList $path
if {$path eq $current_diff_path} {
@@ -409,11 +417,11 @@ proc do_revert_selection {} {
if {[array size selected_paths] > 0} {
revert_helper \
- {Reverting selected files} \
+ [mc "Reverting selected files"] \
[array names selected_paths]
} elseif {$current_diff_path ne {}} {
revert_helper \
- "Reverting [short_path $current_diff_path]" \
+ [mc "Reverting %s" [short_path $current_diff_path]] \
[list $current_diff_path]
}
}
diff --git a/git-gui/lib/mergetool.tcl b/git-gui/lib/mergetool.tcl
new file mode 100644
index 0000000..6ab5701
--- /dev/null
+++ b/git-gui/lib/mergetool.tcl
@@ -0,0 +1,400 @@
+# git-gui merge conflict resolution
+# parts based on git-mergetool (c) 2006 Theodore Y. Ts'o
+
+proc merge_resolve_one {stage} {
+ global current_diff_path
+
+ switch -- $stage {
+ 1 { set targetquestion [mc "Force resolution to the base version?"] }
+ 2 { set targetquestion [mc "Force resolution to this branch?"] }
+ 3 { set targetquestion [mc "Force resolution to the other branch?"] }
+ }
+
+ set op_question [strcat $targetquestion "\n" \
+[mc "Note that the diff shows only conflicting changes.
+
+%s will be overwritten.
+
+This operation can be undone only by restarting the merge." \
+ [short_path $current_diff_path]]]
+
+ if {[ask_popup $op_question] eq {yes}} {
+ merge_load_stages $current_diff_path [list merge_force_stage $stage]
+ }
+}
+
+proc merge_stage_workdir {path w lno} {
+ global current_diff_path diff_active
+
+ if {$diff_active} return
+
+ if {$path ne $current_diff_path} {
+ show_diff $path $w $lno {} [list do_merge_stage_workdir $path]
+ } else {
+ do_merge_stage_workdir $path
+ }
+}
+
+proc do_merge_stage_workdir {path} {
+ global current_diff_path is_conflict_diff
+
+ if {$path ne $current_diff_path} return;
+
+ if {$is_conflict_diff} {
+ if {[ask_popup [mc "File %s seems to have unresolved conflicts, still stage?" \
+ [short_path $path]]] ne {yes}} {
+ return
+ }
+ }
+
+ merge_add_resolution $path
+}
+
+proc merge_add_resolution {path} {
+ global current_diff_path ui_workdir
+
+ set after [next_diff_after_action $ui_workdir $path {} {^_?U}]
+
+ update_index \
+ [mc "Adding resolution for %s" [short_path $path]] \
+ [list $path] \
+ [concat $after [list ui_ready]]
+}
+
+proc merge_force_stage {stage} {
+ global current_diff_path merge_stages
+
+ if {$merge_stages($stage) ne {}} {
+ git checkout-index -f --stage=$stage -- $current_diff_path
+ } else {
+ file delete -- $current_diff_path
+ }
+
+ merge_add_resolution $current_diff_path
+}
+
+proc merge_load_stages {path cont} {
+ global merge_stages_fd merge_stages merge_stages_buf
+
+ if {[info exists merge_stages_fd]} {
+ catch { kill_file_process $merge_stages_fd }
+ catch { close $merge_stages_fd }
+ }
+
+ set merge_stages(0) {}
+ set merge_stages(1) {}
+ set merge_stages(2) {}
+ set merge_stages(3) {}
+ set merge_stages_buf {}
+
+ set merge_stages_fd [eval git_read ls-files -u -z -- $path]
+
+ fconfigure $merge_stages_fd -blocking 0 -translation binary -encoding binary
+ fileevent $merge_stages_fd readable [list read_merge_stages $merge_stages_fd $cont]
+}
+
+proc read_merge_stages {fd cont} {
+ global merge_stages_buf merge_stages_fd merge_stages
+
+ append merge_stages_buf [read $fd]
+ set pck [split $merge_stages_buf "\0"]
+ set merge_stages_buf [lindex $pck end]
+
+ if {[eof $fd] && $merge_stages_buf ne {}} {
+ lappend pck {}
+ set merge_stages_buf {}
+ }
+
+ foreach p [lrange $pck 0 end-1] {
+ set fcols [split $p "\t"]
+ set cols [split [lindex $fcols 0] " "]
+ set stage [lindex $cols 2]
+
+ set merge_stages($stage) [lrange $cols 0 1]
+ }
+
+ if {[eof $fd]} {
+ close $fd
+ unset merge_stages_fd
+ eval $cont
+ }
+}
+
+proc merge_resolve_tool {} {
+ global current_diff_path
+
+ merge_load_stages $current_diff_path [list merge_resolve_tool2]
+}
+
+proc merge_resolve_tool2 {} {
+ global current_diff_path merge_stages
+
+ # Validate the stages
+ if {$merge_stages(2) eq {} ||
+ [lindex $merge_stages(2) 0] eq {120000} ||
+ [lindex $merge_stages(2) 0] eq {160000} ||
+ $merge_stages(3) eq {} ||
+ [lindex $merge_stages(3) 0] eq {120000} ||
+ [lindex $merge_stages(3) 0] eq {160000}
+ } {
+ error_popup [mc "Cannot resolve deletion or link conflicts using a tool"]
+ return
+ }
+
+ if {![file exists $current_diff_path]} {
+ error_popup [mc "Conflict file does not exist"]
+ return
+ }
+
+ # Determine the tool to use
+ set tool [get_config merge.tool]
+ if {$tool eq {}} { set tool meld }
+
+ set merge_tool_path [get_config "mergetool.$tool.path"]
+ if {$merge_tool_path eq {}} {
+ switch -- $tool {
+ emerge { set merge_tool_path "emacs" }
+ araxis { set merge_tool_path "compare" }
+ default { set merge_tool_path $tool }
+ }
+ }
+
+ # Make file names
+ set filebase [file rootname $current_diff_path]
+ set fileext [file extension $current_diff_path]
+ set basename [lindex [file split $current_diff_path] end]
+
+ set MERGED $current_diff_path
+ set BASE "./$MERGED.BASE$fileext"
+ set LOCAL "./$MERGED.LOCAL$fileext"
+ set REMOTE "./$MERGED.REMOTE$fileext"
+ set BACKUP "./$MERGED.BACKUP$fileext"
+
+ set base_stage $merge_stages(1)
+
+ # Build the command line
+ switch -- $tool {
+ kdiff3 {
+ if {$base_stage ne {}} {
+ set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Base)" \
+ --L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE"]
+ } else {
+ set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Local)" \
+ --L2 "$MERGED (Remote)" -o "$MERGED" "$LOCAL" "$REMOTE"]
+ }
+ }
+ tkdiff {
+ if {$base_stage ne {}} {
+ set cmdline [list "$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE"]
+ } else {
+ set cmdline [list "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"]
+ }
+ }
+ meld {
+ set cmdline [list "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"]
+ }
+ gvimdiff {
+ set cmdline [list "$merge_tool_path" -f "$LOCAL" "$MERGED" "$REMOTE"]
+ }
+ xxdiff {
+ if {$base_stage ne {}} {
+ set cmdline [list "$merge_tool_path" -X --show-merged-pane \
+ -R {Accel.SaveAsMerged: "Ctrl-S"} \
+ -R {Accel.Search: "Ctrl+F"} \
+ -R {Accel.SearchForward: "Ctrl-G"} \
+ --merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE"]
+ } else {
+ set cmdline [list "$merge_tool_path" -X --show-merged-pane \
+ -R {Accel.SaveAsMerged: "Ctrl-S"} \
+ -R {Accel.Search: "Ctrl+F"} \
+ -R {Accel.SearchForward: "Ctrl-G"} \
+ --merged-file "$MERGED" "$LOCAL" "$REMOTE"]
+ }
+ }
+ opendiff {
+ if {$base_stage ne {}} {
+ set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED"]
+ } else {
+ set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$MERGED"]
+ }
+ }
+ ecmerge {
+ if {$base_stage ne {}} {
+ set cmdline [list "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --default --mode=merge3 --to="$MERGED"]
+ } else {
+ set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" --default --mode=merge2 --to="$MERGED"]
+ }
+ }
+ emerge {
+ if {$base_stage ne {}} {
+ set cmdline [list "$merge_tool_path" -f emerge-files-with-ancestor-command \
+ "$LOCAL" "$REMOTE" "$BASE" "$basename"]
+ } else {
+ set cmdline [list "$merge_tool_path" -f emerge-files-command \
+ "$LOCAL" "$REMOTE" "$basename"]
+ }
+ }
+ winmerge {
+ if {$base_stage ne {}} {
+ # This tool does not support 3-way merges.
+ # Use the 'conflict file' resolution feature instead.
+ set cmdline [list "$merge_tool_path" -e -ub "$MERGED"]
+ } else {
+ set cmdline [list "$merge_tool_path" -e -ub -wl \
+ -dl "Theirs File" -dr "Mine File" "$REMOTE" "$LOCAL" "$MERGED"]
+ }
+ }
+ araxis {
+ if {$base_stage ne {}} {
+ set cmdline [list "$merge_tool_path" -wait -merge -3 -a1 \
+ -title1:"'$MERGED (Base)'" -title2:"'$MERGED (Local)'" \
+ -title3:"'$MERGED (Remote)'" \
+ "$BASE" "$LOCAL" "$REMOTE" "$MERGED"]
+ } else {
+ set cmdline [list "$merge_tool_path" -wait -2 \
+ -title1:"'$MERGED (Local)'" -title2:"'$MERGED (Remote)'" \
+ "$LOCAL" "$REMOTE" "$MERGED"]
+ }
+ }
+ p4merge {
+ set cmdline [list "$merge_tool_path" "$BASE" "$REMOTE" "$LOCAL" "$MERGED"]
+ }
+ vimdiff {
+ error_popup [mc "Not a GUI merge tool: '%s'" $tool]
+ return
+ }
+ default {
+ error_popup [mc "Unsupported merge tool '%s'" $tool]
+ return
+ }
+ }
+
+ merge_tool_start $cmdline $MERGED $BACKUP [list $BASE $LOCAL $REMOTE]
+}
+
+proc delete_temp_files {files} {
+ foreach fname $files {
+ file delete $fname
+ }
+}
+
+proc merge_tool_get_stages {target stages} {
+ global merge_stages
+
+ set i 1
+ foreach fname $stages {
+ if {$merge_stages($i) eq {}} {
+ file delete $fname
+ catch { close [open $fname w] }
+ } else {
+ # A hack to support autocrlf properly
+ git checkout-index -f --stage=$i -- $target
+ file rename -force -- $target $fname
+ }
+ incr i
+ }
+}
+
+proc merge_tool_start {cmdline target backup stages} {
+ global merge_stages mtool_target mtool_tmpfiles mtool_fd mtool_mtime
+
+ if {[info exists mtool_fd]} {
+ if {[ask_popup [mc "Merge tool is already running, terminate it?"]] eq {yes}} {
+ catch { kill_file_process $mtool_fd }
+ catch { close $mtool_fd }
+ unset mtool_fd
+
+ set old_backup [lindex $mtool_tmpfiles end]
+ file rename -force -- $old_backup $mtool_target
+ delete_temp_files $mtool_tmpfiles
+ } else {
+ return
+ }
+ }
+
+ # Save the original file
+ file rename -force -- $target $backup
+
+ # Get the blobs; it destroys $target
+ if {[catch {merge_tool_get_stages $target $stages} err]} {
+ file rename -force -- $backup $target
+ delete_temp_files $stages
+ error_popup [mc "Error retrieving versions:\n%s" $err]
+ return
+ }
+
+ # Restore the conflict file
+ file copy -force -- $backup $target
+
+ # Initialize global state
+ set mtool_target $target
+ set mtool_mtime [file mtime $target]
+ set mtool_tmpfiles $stages
+
+ lappend mtool_tmpfiles $backup
+
+ # Force redirection to avoid interpreting output on stderr
+ # as an error, and launch the tool
+ lappend cmdline {2>@1}
+
+ if {[catch { set mtool_fd [_open_stdout_stderr $cmdline] } err]} {
+ delete_temp_files $mtool_tmpfiles
+ error_popup [mc "Could not start the merge tool:\n\n%s" $err]
+ return
+ }
+
+ ui_status [mc "Running merge tool..."]
+
+ fconfigure $mtool_fd -blocking 0 -translation binary -encoding binary
+ fileevent $mtool_fd readable [list read_mtool_output $mtool_fd]
+}
+
+proc read_mtool_output {fd} {
+ global mtool_fd mtool_tmpfiles
+
+ read $fd
+ if {[eof $fd]} {
+ unset mtool_fd
+
+ fconfigure $fd -blocking 1
+ merge_tool_finish $fd
+ }
+}
+
+proc merge_tool_finish {fd} {
+ global mtool_tmpfiles mtool_target mtool_mtime
+
+ set backup [lindex $mtool_tmpfiles end]
+ set failed 0
+
+ # Check the return code
+ if {[catch {close $fd} err]} {
+ set failed 1
+ if {$err ne {child process exited abnormally}} {
+ error_popup [strcat [mc "Merge tool failed."] "\n\n$err"]
+ }
+ }
+
+ # Check the modification time of the target file
+ if {!$failed && [file mtime $mtool_target] eq $mtool_mtime} {
+ if {[ask_popup [mc "File %s unchanged, still accept as resolved?" \
+ [short_path $mtool_target]]] ne {yes}} {
+ set failed 1
+ }
+ }
+
+ # Finish
+ if {$failed} {
+ file rename -force -- $backup $mtool_target
+ delete_temp_files $mtool_tmpfiles
+ ui_status [mc "Merge tool failed."]
+ } else {
+ if {[is_config_true merge.keepbackup]} {
+ file rename -force -- $backup "$mtool_target.orig"
+ }
+
+ delete_temp_files $mtool_tmpfiles
+
+ merge_add_resolution $mtool_target
+ }
+}
diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl
index 5e1346e..c80c939 100644
--- a/git-gui/lib/option.tcl
+++ b/git-gui/lib/option.tcl
@@ -1,6 +1,28 @@
# git-gui options editor
# Copyright (C) 2006, 2007 Shawn Pearce
+proc config_check_encodings {} {
+ global repo_config_new global_config_new
+
+ set enc $global_config_new(gui.encoding)
+ if {$enc eq {}} {
+ set global_config_new(gui.encoding) [encoding system]
+ } elseif {[tcl_encoding $enc] eq {}} {
+ error_popup [mc "Invalid global encoding '%s'" $enc]
+ return 0
+ }
+
+ set enc $repo_config_new(gui.encoding)
+ if {$enc eq {}} {
+ set repo_config_new(gui.encoding) [encoding system]
+ } elseif {[tcl_encoding $enc] eq {}} {
+ error_popup [mc "Invalid repo encoding '%s'" $enc]
+ return 0
+ }
+
+ return 1
+}
+
proc save_config {} {
global default_config font_descs
global repo_config global_config
@@ -119,15 +141,18 @@ proc do_options {} {
{b merge.summary {mc "Summarize Merge Commits"}}
{i-1..5 merge.verbosity {mc "Merge Verbosity"}}
{b merge.diffstat {mc "Show Diffstat After Merge"}}
+ {t merge.tool {mc "Use Merge Tool"}}
{b gui.trustmtime {mc "Trust File Modification Timestamps"}}
{b gui.pruneduringfetch {mc "Prune Tracking Branches During Fetch"}}
{b gui.matchtrackingbranch {mc "Match Tracking Branches"}}
{b gui.fastcopyblame {mc "Blame Copy Only On Changed Files"}}
{i-20..200 gui.copyblamethreshold {mc "Minimum Letters To Blame Copy On"}}
+ {i-0..300 gui.blamehistoryctx {mc "Blame History Context Radius (days)"}}
{i-1..99 gui.diffcontext {mc "Number of Diff Context Lines"}}
{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"}}
} {
set type [lindex $option 0]
set name [lindex $option 1]
@@ -157,6 +182,7 @@ proc do_options {} {
pack $w.$f.$optid.v -side right -anchor e -padx 5
pack $w.$f.$optid -side top -anchor w -fill x
}
+ c -
t {
frame $w.$f.$optid
label $w.$f.$optid.l -text "$text:"
@@ -169,6 +195,16 @@ proc do_options {} {
pack $w.$f.$optid.v -side left -anchor w \
-fill x -expand 1 \
-padx 5
+ if {$type eq {c}} {
+ menu $w.$f.$optid.m
+ build_encoding_menu $w.$f.$optid.m \
+ [list set ${f}_config_new($name)] 1
+ button $w.$f.$optid.b \
+ -text [mc "Change"] \
+ -command [list popup_btn_menu \
+ $w.$f.$optid.m $w.$f.$optid.b]
+ pack $w.$f.$optid.b -side left -anchor w
+ }
pack $w.$f.$optid -side top -anchor w -fill x
}
}
@@ -273,6 +309,7 @@ proc do_restore_defaults {} {
}
proc do_save_config {w} {
+ if {![config_check_encodings]} return
if {[catch {save_config} err]} {
error_popup [strcat [mc "Failed to completely save options:"] "\n\n$err"]
}
diff --git a/git-gui/po/de.po b/git-gui/po/de.po
index fa43947..793cca1 100644
--- a/git-gui/po/de.po
+++ b/git-gui/po/de.po
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: git-gui\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-08-02 08:58+0200\n"
-"PO-Revision-Date: 2008-08-02 09:09+0200\n"
+"POT-Creation-Date: 2008-09-13 10:20+0200\n"
+"PO-Revision-Date: 2008-09-13 10:24+0200\n"
"Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
"Language-Team: German\n"
"MIME-Version: 1.0\n"
@@ -110,7 +110,15 @@ msgstr "Teilweise bereitgestellt zum Eintragen"
msgid "Staged for commit, missing"
msgstr "Bereitgestellt zum Eintragen, fehlend"
-#: git-gui.sh:1597
+#: git-gui.sh:1658
+msgid "File type changed, not staged"
+msgstr "Dateityp geändert, nicht bereitgestellt"
+
+#: git-gui.sh:1659
+msgid "File type changed, staged"
+msgstr "Dateityp geändert, bereitgestellt"
+
+#: git-gui.sh:1661
msgid "Untracked, not staged"
msgstr "Nicht unter Versionskontrolle, nicht bereitgestellt"
@@ -396,15 +404,7 @@ msgstr "Alle kopieren"
msgid "File:"
msgstr "Datei:"
-#: git-gui.sh:2589
-msgid "Apply/Reverse Hunk"
-msgstr "Kontext anwenden/umkehren"
-
-#: git-gui.sh:2696
-msgid "Apply/Reverse Line"
-msgstr "Zeile anwenden/umkehren"
-
-#: git-gui.sh:2711
+#: git-gui.sh:2834
msgid "Refresh"
msgstr "Aktualisieren"
@@ -416,7 +416,35 @@ msgstr "Schriftgröße verkleinern"
msgid "Increase Font Size"
msgstr "Schriftgröße vergrößern"
-#: git-gui.sh:2646
+#: git-gui.sh:2870
+msgid "Apply/Reverse Hunk"
+msgstr "Kontext anwenden/umkehren"
+
+#: git-gui.sh:2875
+msgid "Apply/Reverse Line"
+msgstr "Zeile anwenden/umkehren"
+
+#: git-gui.sh:2885
+msgid "Run Merge Tool"
+msgstr "Zusammenführungswerkzeug"
+
+#: git-gui.sh:2890
+msgid "Use Remote Version"
+msgstr "Entfernte Version benutzen"
+
+#: git-gui.sh:2894
+msgid "Use Local Version"
+msgstr "Lokale Version benutzen"
+
+#: git-gui.sh:2898
+msgid "Revert To Base"
+msgstr "Ursprüngliche Version benutzen"
+
+#: git-gui.sh:2906
+msgid "Stage Working Copy"
+msgstr "Arbeitskopie bereitstellen"
+
+#: git-gui.sh:2925
msgid "Unstage Hunk From Commit"
msgstr "Kontext aus Bereitstellung herausnehmen"
@@ -498,7 +526,15 @@ msgstr "Version kopieren"
msgid "Do Full Copy Detection"
msgstr "Volle Kopie-Erkennung"
-#: lib/blame.tcl:388
+#: lib/blame.tcl:263
+msgid "Show History Context"
+msgstr "Historien-Kontext anzeigen"
+
+#: lib/blame.tcl:266
+msgid "Blame Parent Commit"
+msgstr "Elternversion annotieren"
+
+#: lib/blame.tcl:394
#, tcl-format
msgid "Reading %s..."
msgstr "%s lesen..."
@@ -547,7 +583,19 @@ msgstr "Eintragender:"
msgid "Original File:"
msgstr "Ursprüngliche Datei:"
-#: lib/blame.tcl:925
+#: lib/blame.tcl:990
+msgid "Cannot find parent commit:"
+msgstr "Elternversion kann nicht gefunden werden:"
+
+#: lib/blame.tcl:1001
+msgid "Unable to display parent"
+msgstr "Elternversion kann nicht angezeigt werden"
+
+#: lib/blame.tcl:1002 lib/diff.tcl:191
+msgid "Error loading diff:"
+msgstr "Fehler beim Laden des Vergleichs:"
+
+#: lib/blame.tcl:1142
msgid "Originally By:"
msgstr "Ursprünglich von:"
@@ -1494,11 +1542,7 @@ msgstr "Git-Projektarchiv (Unterprojekt)"
msgid "* Binary file (not showing content)."
msgstr "* Binärdatei (Inhalt wird nicht angezeigt)"
-#: lib/diff.tcl:185
-msgid "Error loading diff:"
-msgstr "Fehler beim Laden des Vergleichs:"
-
-#: lib/diff.tcl:303
+#: lib/diff.tcl:313
msgid "Failed to unstage selected hunk."
msgstr ""
"Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
@@ -1586,6 +1630,15 @@ msgstr ""
msgid "Do Nothing"
msgstr "Nichts tun"
+#: lib/index.tcl:419
+msgid "Reverting selected files"
+msgstr "Änderungen in gewählten Dateien verwerfen"
+
+#: lib/index.tcl:423
+#, tcl-format
+msgid "Reverting %s"
+msgstr "Änderungen in %s verwerfen"
+
#: lib/merge.tcl:13
msgid ""
"Cannot merge while amending.\n"
@@ -1730,6 +1783,96 @@ msgstr "Abbruch fehlgeschlagen."
msgid "Abort completed. Ready."
msgstr "Abbruch durchgeführt. Bereit."
+#: lib/mergetool.tcl:14
+msgid "Force resolution to the base version?"
+msgstr "Konflikt durch Basisversion ersetzen?"
+
+#: lib/mergetool.tcl:15
+msgid "Force resolution to this branch?"
+msgstr "Konflikt durch diesen Zweig ersetzen?"
+
+#: lib/mergetool.tcl:16
+msgid "Force resolution to the other branch?"
+msgstr "Konflikt durch anderen Zweig ersetzen?"
+
+#: lib/mergetool.tcl:20
+#, tcl-format
+msgid ""
+"Note that the diff shows only conflicting changes.\n"
+"\n"
+"%s will be overwritten.\n"
+"\n"
+"This operation can be undone only by restarting the merge."
+msgstr ""
+"Hinweis: Der Vergleich zeigt nur konfliktverursachende Änderungen an.\n"
+"\n"
+"»%s« wird überschrieben.\n"
+"\n"
+"Diese Operation kann nur rückgängig gemacht werden, wenn die\n"
+"Zusammenführung erneut gestartet wird."
+
+#: lib/mergetool.tcl:32
+#, tcl-format
+msgid "Adding resolution for %s"
+msgstr "Auflösung hinzugefügt für %s"
+
+#: lib/mergetool.tcl:119
+msgid "Cannot resolve deletion or link conflicts using a tool"
+msgstr ""
+"Konflikte durch gelöschte Dateien oder symbolische Links können nicht durch "
+"das Zusamenführungswerkzeug gelöst werden."
+
+#: lib/mergetool.tcl:124
+msgid "Conflict file does not exist"
+msgstr "Konflikt-Datei existiert nicht"
+
+#: lib/mergetool.tcl:236
+#, tcl-format
+msgid "Not a GUI merge tool: '%s'"
+msgstr "Kein GUI Zusammenführungswerkzeug: »%s«"
+
+#: lib/mergetool.tcl:240
+#, tcl-format
+msgid "Unsupported merge tool '%s'"
+msgstr "Unbekanntes Zusammenführungswerkzeug: »%s«"
+
+#: lib/mergetool.tcl:275
+msgid "Merge tool is already running, terminate it?"
+msgstr "Zusammenführungswerkzeug läuft bereits. Soll es abgebrochen werden?"
+
+#: lib/mergetool.tcl:295
+#, tcl-format
+msgid ""
+"Error retrieving versions:\n"
+"%s"
+msgstr ""
+"Fehler beim Abrufen der Dateiversionen:\n"
+"%s"
+
+#: lib/mergetool.tcl:315
+#, tcl-format
+msgid ""
+"Could not start the merge tool:\n"
+"\n"
+"%s"
+msgstr ""
+"Zusammenführungswerkzeug konnte nicht gestartet werden:\n"
+"\n"
+"%s"
+
+#: lib/mergetool.tcl:319
+msgid "Running merge tool..."
+msgstr "Zusammenführungswerkzeug starten..."
+
+#: lib/mergetool.tcl:347 lib/mergetool.tcl:363
+msgid "Merge tool failed."
+msgstr "Zusammenführungswerkzeug fehlgeschlagen."
+
+#: lib/mergetool.tcl:353
+#, tcl-format
+msgid "File %s unchanged, still accept as resolved?"
+msgstr "Datei »%s« unverändert. Trotzdem Konflikt als gelöst akzeptieren?"
+
#: lib/option.tcl:95
msgid "Restore Defaults"
msgstr "Voreinstellungen wiederherstellen"
@@ -1767,7 +1910,11 @@ msgstr "Ausführlichkeit der Zusammenführen-Meldungen"
msgid "Show Diffstat After Merge"
msgstr "Vergleichsstatistik nach Zusammenführen anzeigen"
-#: lib/option.tcl:123
+#: lib/option.tcl:122
+msgid "Use Merge Tool"
+msgstr "Zusammenführungswerkzeug"
+
+#: lib/option.tcl:124
msgid "Trust File Modification Timestamps"
msgstr "Auf Dateiänderungsdatum verlassen"
@@ -1788,6 +1935,10 @@ msgid "Minimum Letters To Blame Copy On"
msgstr "Mindestzahl Zeichen für Kopie-Annotieren"
#: lib/option.tcl:128
+msgid "Blame History Context Radius (days)"
+msgstr "Anzahl Tage für Historien-Kontext"
+
+#: lib/option.tcl:129
msgid "Number of Diff Context Lines"
msgstr "Anzahl der Kontextzeilen beim Vergleich"
diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh
index 645e114..1dadbb4 100755
--- a/git-merge-octopus.sh
+++ b/git-merge-octopus.sh
@@ -61,7 +61,7 @@ do
exit 2
esac
- common=$(git merge-base --all $MRC $SHA1) ||
+ common=$(git merge-base --all $SHA1 $MRC) ||
die "Unable to find common commit with $SHA1"
case "$LF$common$LF" in
@@ -100,14 +100,7 @@ do
next=$(git write-tree 2>/dev/null)
fi
- # We have merged the other branch successfully. Ideally
- # we could implement OR'ed heads in merge-base, and keep
- # a list of commits we have merged so far in MRC to feed
- # them to merge-base, but we approximate it by keep using
- # the current MRC. We used to update it to $common, which
- # was incorrectly doing AND'ed merge-base here, which was
- # unneeded.
-
+ MRC="$MRC $SHA1"
MRT=$next
done
diff --git a/git-pull.sh b/git-pull.sh
index 75c3610..664fe34 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -124,7 +124,7 @@ orig_head=$(git rev-parse --verify HEAD 2>/dev/null)
git fetch --update-head-ok "$@" || exit 1
curr_head=$(git rev-parse --verify HEAD 2>/dev/null)
-if test "$curr_head" != "$orig_head"
+if test -n "$orig_head" && test "$curr_head" != "$orig_head"
then
# The fetch involved updating the current branch.
@@ -172,7 +172,7 @@ esac
if test -z "$orig_head"
then
- git update-ref -m "initial pull" HEAD $merge_head "" &&
+ git update-ref -m "initial pull" HEAD $merge_head "$curr_head" &&
git read-tree --reset -u HEAD || exit 1
exit
fi
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 124cb58..39f8d73 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -26,6 +26,7 @@ i,interactive always used (no-op)
continue continue rebasing process
abort abort rebasing process and restore original branch
skip skip current patch and continue rebasing process
+no-verify override pre-rebase hook from stopping the operation
"
. git-sh-setup
@@ -37,10 +38,12 @@ DONE="$DOTEST"/done
MSG="$DOTEST"/message
SQUASH_MSG="$DOTEST"/message-squash
REWRITTEN="$DOTEST"/rewritten
+DROPPED="$DOTEST"/dropped
PRESERVE_MERGES=
STRATEGY=
ONTO=
VERBOSE=
+OK_TO_SKIP_PRE_REBASE=
GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
@@ -66,7 +69,8 @@ output () {
}
run_pre_rebase_hook () {
- if test -x "$GIT_DIR/hooks/pre-rebase"
+ if test -z "$OK_TO_SKIP_PRE_REBASE" &&
+ test -x "$GIT_DIR/hooks/pre-rebase"
then
"$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || {
echo >&2 "The pre-rebase hook refused to rebase."
@@ -179,8 +183,12 @@ pick_one_preserving_merges () {
# rewrite parents; if none were rewritten, we can fast-forward.
new_parents=
- for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)
+ pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)"
+ while [ "$pend" != "" ]
do
+ p=$(expr "$pend" : ' \([^ ]*\)')
+ pend="${pend# $p}"
+
if test -f "$REWRITTEN"/$p
then
new_p=$(cat "$REWRITTEN"/$p)
@@ -193,7 +201,13 @@ pick_one_preserving_merges () {
;;
esac
else
- new_parents="$new_parents $p"
+ if test -f "$DROPPED"/$p
+ then
+ fast_forward=f
+ pend=" $(cat "$DROPPED"/$p)$pend"
+ else
+ new_parents="$new_parents $p"
+ fi
fi
done
case $fast_forward in
@@ -421,6 +435,11 @@ get_saved_options () {
while test $# != 0
do
case "$1" in
+ --no-verify)
+ OK_TO_SKIP_PRE_REBASE=yes
+ ;;
+ --verify)
+ ;;
--continue)
is_standalone "$@" || usage
get_saved_options
@@ -599,6 +618,28 @@ first and then run 'git rebase --continue' again."
#
EOF
+ # Watch for commits that been dropped by --cherry-pick
+ if test t = "$PRESERVE_MERGES"
+ then
+ mkdir "$DROPPED"
+ # drop the --cherry-pick parameter this time
+ git rev-list $MERGES_OPTION --abbrev-commit \
+ --abbrev=7 $UPSTREAM...$HEAD --left-right | \
+ sed -n "s/^>//p" | while read rev
+ do
+ grep --quiet "$rev" "$TODO"
+ if [ $? -ne 0 ]
+ then
+ # Use -f2 because if rev-list is telling this commit is not
+ # worthwhile, we don't want to track its multiple heads,
+ # just the history of its first-parent for others that will
+ # be rebasing on top of us
+ full=$(git rev-parse $rev)
+ git rev-list --parents -1 $rev | cut -d' ' -f2 > "$DROPPED"/$full
+ fi
+ done
+ fi
+
has_action "$TODO" ||
die_abort "Nothing to do"
diff --git a/git-rebase.sh b/git-rebase.sh
index a30d40c..023a6dc 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -34,6 +34,7 @@ set_reflog_action rebase
require_work_tree
cd_to_toplevel
+OK_TO_SKIP_PRE_REBASE=
RESOLVEMSG="
When you have resolved this problem run \"git rebase --continue\".
If you would prefer to skip this patch, instead run \"git rebase --skip\".
@@ -138,14 +139,31 @@ finish_rb_merge () {
}
is_interactive () {
- test -f "$dotest"/interactive ||
- while :; do case $#,"$1" in 0,|*,-i|*,--interactive) break ;; esac
+ while test $# != 0
+ do
+ case "$1" in
+ -i|--interactive)
+ interactive_rebase=explicit
+ break
+ ;;
+ -p|--preserve-merges)
+ interactive_rebase=implied
+ ;;
+ esac
shift
- done && test -n "$1"
+ done
+
+ if [ "$interactive_rebase" = implied ]; then
+ GIT_EDITOR=:
+ export GIT_EDITOR
+ fi
+
+ test -n "$interactive_rebase" || test -f "$dotest"/interactive
}
run_pre_rebase_hook () {
- if test -x "$GIT_DIR/hooks/pre-rebase"
+ if test -z "$OK_TO_SKIP_PRE_REBASE" &&
+ test -x "$GIT_DIR/hooks/pre-rebase"
then
"$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || {
echo >&2 "The pre-rebase hook refused to rebase."
@@ -170,6 +188,9 @@ fi
while test $# != 0
do
case "$1" in
+ --no-verify)
+ OK_TO_SKIP_PRE_REBASE=yes
+ ;;
--continue)
test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply ||
die "No rebase in progress?"
diff --git a/git-send-email.perl b/git-send-email.perl
index d2fd899..bdbfac6 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -39,75 +39,40 @@ package main;
sub usage {
print <<EOT;
git send-email [options] <file | directory>...
-Options:
- --from Specify the "From:" line of the email to be sent.
- --to Specify the primary "To:" line of the email.
-
- --cc Specify an initial "Cc:" list for the entire series
- of emails.
-
- --cc-cmd Specify a command to execute per file which adds
- per file specific cc address entries
-
- --bcc Specify a list of email addresses that should be Bcc:
- on all the emails.
-
- --compose Use \$GIT_EDITOR, core.editor, \$EDITOR, or \$VISUAL to edit
- an introductory message for the patch series.
-
- --subject Specify the initial "Subject:" line.
- Only necessary if --compose is also set. If --compose
- is not set, this will be prompted for.
-
- --in-reply-to Specify the first "In-Reply-To:" header line.
- Only used if --compose is also set. If --compose is not
- set, this will be prompted for.
-
- --chain-reply-to If set, the replies will all be to the previous
- email sent, rather than to the first email sent.
- Defaults to on.
-
- --signed-off-cc Automatically add email addresses that appear in
- Signed-off-by: or Cc: lines to the cc: list. Defaults to on.
-
- --identity The configuration identity, a subsection to prioritise over
- the default section.
-
- --smtp-server If set, specifies the outgoing SMTP server to use.
- Defaults to localhost. Port number can be specified here with
- hostname:port format or by using --smtp-server-port option.
-
- --smtp-server-port Specify a port on the outgoing SMTP server to connect to.
-
- --smtp-user The username for SMTP-AUTH.
-
- --smtp-pass The password for SMTP-AUTH.
-
- --smtp-encryption Specify 'tls' for STARTTLS encryption, or 'ssl' for SSL.
- Any other value disables the feature.
-
- --smtp-ssl Synonym for '--smtp-encryption=ssl'. Deprecated.
-
- --suppress-cc Suppress the specified category of auto-CC. The category
- can be one of 'author' for the patch author, 'self' to
- avoid copying yourself, 'sob' for Signed-off-by lines,
- 'cccmd' for the output of the cccmd, or 'all' to suppress
- all of these.
-
- --suppress-from Suppress sending emails to yourself. Defaults to off.
-
- --thread Specify that the "In-Reply-To:" header should be set on all
- emails. Defaults to on.
-
- --quiet Make git-send-email less verbose. One line per email
- should be all that is output.
-
- --dry-run Do everything except actually send the emails.
-
- --envelope-sender Specify the envelope sender used to send the emails.
-
- --no-validate Don't perform any sanity checks on patches.
+ Composing:
+ --from <str> * Email From:
+ --to <str> * Email To:
+ --cc <str> * Email Cc:
+ --bcc <str> * Email Bcc:
+ --subject <str> * Email "Subject:"
+ --in-reply-to <str> * Email "In-Reply-To:"
+ --compose * Open an editor for introduction.
+
+ Sending:
+ --envelope-sender <str> * Email envelope sender.
+ --smtp-server <str:int> * Outgoing SMTP server to use. The port
+ is optional. Default 'localhost'.
+ --smtp-server-port <int> * Outgoing SMTP server port.
+ --smtp-user <str> * Username for SMTP-AUTH.
+ --smtp-pass <str> * Password for SMTP-AUTH; not necessary.
+ --smtp-encryption <str> * tls or ssl; anything else disables.
+ --smtp-ssl * Deprecated. Use '--smtp-encryption ssl'.
+
+ Automating:
+ --identity <str> * Use the sendemail.<id> options.
+ --cc-cmd <str> * Email Cc: via `<str> \$patch_path`
+ --suppress-cc <str> * author, self, sob, cccmd, all.
+ --[no-]signed-off-by-cc * Send to Cc: and Signed-off-by:
+ addresses. Default on.
+ --[no-]suppress-from * Send to self. Default off.
+ --[no-]chain-reply-to * Chain In-Reply-To: fields. Default on.
+ --[no-]thread * Use In-Reply-To: field. Default on.
+
+ Administering:
+ --quiet * Output one line of info per email.
+ --dry-run * Don't actually send the emails.
+ --[no-]validate * Perform patch sanity checks. Default on.
EOT
exit(1);
@@ -186,17 +151,19 @@ if ($@) {
my ($quiet, $dry_run) = (0, 0);
# Variables with corresponding config settings
-my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc, $cc_cmd);
+my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd);
my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption);
my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts);
-my ($no_validate);
+my ($validate);
my (@suppress_cc);
my %config_bool_settings = (
"thread" => [\$thread, 1],
"chainreplyto" => [\$chain_reply_to, 1],
"suppressfrom" => [\$suppress_from, undef],
- "signedoffcc" => [\$signed_off_cc, undef],
+ "signedoffbycc" => [\$signed_off_by_cc, undef],
+ "signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated
+ "validate" => [\$validate, 1],
);
my %config_settings = (
@@ -259,11 +226,11 @@ my $rc = GetOptions("sender|from=s" => \$sender,
"cc-cmd=s" => \$cc_cmd,
"suppress-from!" => \$suppress_from,
"suppress-cc=s" => \@suppress_cc,
- "signed-off-cc|signed-off-by-cc!" => \$signed_off_cc,
+ "signed-off-cc|signed-off-by-cc!" => \$signed_off_by_cc,
"dry-run" => \$dry_run,
"envelope-sender=s" => \$envelope_sender,
"thread!" => \$thread,
- "no-validate" => \$no_validate,
+ "validate!" => \$validate,
);
unless ($rc) {
@@ -335,7 +302,7 @@ if ($suppress_cc{'all'}) {
# If explicit old-style ones are specified, they trump --suppress-cc.
$suppress_cc{'self'} = $suppress_from if defined $suppress_from;
-$suppress_cc{'sob'} = !$signed_off_cc if defined $signed_off_cc;
+$suppress_cc{'sob'} = !$signed_off_by_cc if defined $signed_off_by_cc;
# Debugging, print out the suppressions.
if (0) {
@@ -416,7 +383,7 @@ for my $f (@ARGV) {
}
}
-if (!$no_validate) {
+if ($validate) {
foreach my $f (@files) {
unless (-p $f) {
my $error = validate_patch($f);
diff --git a/git-submodule.sh b/git-submodule.sh
index 5888735..b63e5c3 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -6,9 +6,10 @@
USAGE="[--quiet] [--cached] \
[add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
-[--] [<path>...]"
+[--] [<path>...]|[foreach <command>]|[sync [--] [<path>...]]"
OPTIONS_SPEC=
. git-sh-setup
+. git-parse-remote
require_work_tree
command=
@@ -30,12 +31,11 @@ say()
# Resolve relative url by appending to parent's url
resolve_relative_url ()
{
- branch="$(git symbolic-ref HEAD 2>/dev/null)"
- remote="$(git config branch.${branch#refs/heads/}.remote)"
- remote="${remote:-origin}"
+ remote=$(get_default_remote)
remoteurl=$(git config "remote.$remote.url") ||
die "remote ($remote) does not have a url defined in .git/config"
url="$1"
+ remoteurl=${remoteurl%/}
while test -n "$url"
do
case "$url" in
@@ -50,7 +50,16 @@ resolve_relative_url ()
break;;
esac
done
- echo "$remoteurl/$url"
+ echo "$remoteurl/${url%/}"
+}
+
+#
+# Get submodule info for registered submodules
+# $@ = path to limit submodule list
+#
+module_list()
+{
+ git ls-files --stage -- "$@" | grep '^160000 '
}
#
@@ -199,6 +208,26 @@ cmd_add()
}
#
+# Execute an arbitrary command sequence in each checked out
+# submodule
+#
+# $@ = command to execute
+#
+cmd_foreach()
+{
+ module_list |
+ while read mode sha1 stage path
+ do
+ if test -e "$path"/.git
+ then
+ say "Entering '$path'"
+ (cd "$path" && eval "$@") ||
+ die "Stopping at '$path'; script returned non-zero status."
+ fi
+ done
+}
+
+#
# Register submodules in .git/config
#
# $@ = requested paths (default to all)
@@ -226,7 +255,7 @@ cmd_init()
shift
done
- git ls-files --stage -- "$@" | grep '^160000 ' |
+ module_list "$@" |
while read mode sha1 stage path
do
# Skip already registered paths
@@ -284,7 +313,7 @@ cmd_update()
esac
done
- git ls-files --stage -- "$@" | grep '^160000 ' |
+ module_list "$@" |
while read mode sha1 stage path
do
name=$(module_name "$path") || exit
@@ -395,7 +424,7 @@ cmd_summary() {
cd_to_toplevel
# Get modified modules cared by user
modules=$(git diff-index $cached --raw $head -- "$@" |
- grep -e '^:160000' -e '^:[0-7]* 160000' |
+ egrep '^:([0-7]* )?160000' |
while read mod_src mod_dst sha1_src sha1_dst status name
do
# Always show modules deleted or type-changed (blob<->module)
@@ -409,7 +438,7 @@ cmd_summary() {
test -z "$modules" && return
git diff-index $cached --raw $head -- $modules |
- grep -e '^:160000' -e '^:[0-7]* 160000' |
+ egrep '^:([0-7]* )?160000' |
cut -c2- |
while read mod_src mod_dst sha1_src sha1_dst status name
do
@@ -554,7 +583,7 @@ cmd_status()
shift
done
- git ls-files --stage -- "$@" | grep '^160000 ' |
+ module_list "$@" |
while read mode sha1 stage path
do
name=$(module_name "$path") || exit
@@ -578,6 +607,58 @@ cmd_status()
fi
done
}
+#
+# Sync remote urls for submodules
+# This makes the value for remote.$remote.url match the value
+# specified in .gitmodules.
+#
+cmd_sync()
+{
+ while test $# -ne 0
+ do
+ case "$1" in
+ -q|--quiet)
+ quiet=1
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+ cd_to_toplevel
+ module_list "$@" |
+ while read mode sha1 stage path
+ do
+ name=$(module_name "$path")
+ url=$(git config -f .gitmodules --get submodule."$name".url)
+
+ # Possibly a url relative to parent
+ case "$url" in
+ ./*|../*)
+ url=$(resolve_relative_url "$url") || exit
+ ;;
+ esac
+
+ if test -e "$path"/.git
+ then
+ (
+ unset GIT_DIR
+ cd "$path"
+ remote=$(get_default_remote)
+ say "Synchronizing submodule url for '$name'"
+ git config remote."$remote".url "$url"
+ )
+ fi
+ done
+}
# This loop parses the command line arguments to find the
# subcommand name to dispatch. Parsing of the subcommand specific
@@ -588,7 +669,7 @@ cmd_status()
while test $# != 0 && test -z "$command"
do
case "$1" in
- add | init | update | status | summary)
+ add | foreach | init | update | status | summary | sync)
command=$1
;;
-q|--quiet)
diff --git a/git-svn.perl b/git-svn.perl
index 2e68c68..f90ddac 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -66,7 +66,7 @@ my ($_stdin, $_help, $_edit,
$_version, $_fetch_all, $_no_rebase,
$_merge, $_strategy, $_dry_run, $_local,
$_prefix, $_no_checkout, $_url, $_verbose,
- $_git_format, $_commit_url);
+ $_git_format, $_commit_url, $_tag);
$Git::SVN::_follow_parent = 1;
my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
'config-dir=s' => \$Git::SVN::Ra::config_dir,
@@ -131,6 +131,15 @@ my %cmd = (
'revision|r=i' => \$_revision,
'no-rebase' => \$_no_rebase,
%cmt_opts, %fc_opts } ],
+ branch => [ \&cmd_branch,
+ 'Create a branch in the SVN repository',
+ { 'message|m=s' => \$_message,
+ 'dry-run|n' => \$_dry_run,
+ 'tag|t' => \$_tag } ],
+ tag => [ sub { $_tag = 1; cmd_branch(@_) },
+ 'Create a tag in the SVN repository',
+ { 'message|m=s' => \$_message,
+ 'dry-run|n' => \$_dry_run } ],
'set-tree' => [ \&cmd_set_tree,
"Set an SVN repository to a git tree-ish",
{ 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
@@ -421,15 +430,15 @@ sub cmd_dcommit {
$head ||= 'HEAD';
my @refs;
my ($url, $rev, $uuid, $gs) = working_head_info($head, \@refs);
+ unless ($gs) {
+ die "Unable to determine upstream SVN information from ",
+ "$head history.\nPerhaps the repository is empty.";
+ }
$url = defined $_commit_url ? $_commit_url : $gs->full_url;
my $last_rev = $_revision if defined $_revision;
if ($url) {
print "Committing to $url ...\n";
}
- unless ($gs) {
- die "Unable to determine upstream SVN information from ",
- "$head history.\nPerhaps the repository is empty.";
- }
my ($linear_refs, $parents) = linearize_history($gs, \@refs);
if ($_no_rebase && scalar(@$linear_refs) > 1) {
warn "Attempting to commit more than one change while ",
@@ -537,6 +546,42 @@ sub cmd_dcommit {
unlink $gs->{index};
}
+sub cmd_branch {
+ my ($branch_name, $head) = @_;
+
+ unless (defined $branch_name && length $branch_name) {
+ die(($_tag ? "tag" : "branch") . " name required\n");
+ }
+ $head ||= 'HEAD';
+
+ my ($src, $rev, undef, $gs) = working_head_info($head);
+
+ my $remote = Git::SVN::read_all_remotes()->{svn};
+ my $glob = $remote->{ $_tag ? 'tags' : 'branches' };
+ my ($lft, $rgt) = @{ $glob->{path} }{qw/left right/};
+ my $dst = join '/', $remote->{url}, $lft, $branch_name, ($rgt || ());
+
+ my $ctx = SVN::Client->new(
+ auth => Git::SVN::Ra::_auth_providers(),
+ log_msg => sub {
+ ${ $_[0] } = defined $_message
+ ? $_message
+ : 'Create ' . ($_tag ? 'tag ' : 'branch ' )
+ . $branch_name;
+ },
+ );
+
+ eval {
+ $ctx->ls($dst, 'HEAD', 0);
+ } and die "branch ${branch_name} already exists\n";
+
+ print "Copying ${src} at r${rev} to ${dst}...\n";
+ $ctx->copy($src, $rev, $dst)
+ unless $_dry_run;
+
+ $gs->fetch_all;
+}
+
sub cmd_find_rev {
my $revision_or_hash = shift or die "SVN or git revision required ",
"as a command-line argument\n";
@@ -803,8 +848,28 @@ sub cmd_commit_diff {
}
}
+sub escape_uri_only {
+ my ($uri) = @_;
+ my @tmp;
+ foreach (split m{/}, $uri) {
+ s/([^\w.%+-]|%(?![a-fA-F0-9]{2}))/sprintf("%%%02X",ord($1))/eg;
+ push @tmp, $_;
+ }
+ join('/', @tmp);
+}
+
+sub escape_url {
+ my ($url) = @_;
+ if ($url =~ m#^([^:]+)://([^/]*)(.*)$#) {
+ my ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));
+ $url = "$scheme://$domain$uri";
+ }
+ $url;
+}
+
sub cmd_info {
my $path = canonicalize_path(defined($_[0]) ? $_[0] : ".");
+ my $fullpath = canonicalize_path($cmd_dir_prefix . $path);
if (exists $_[1]) {
die "Too many arguments specified\n";
}
@@ -812,8 +877,8 @@ sub cmd_info {
my ($file_type, $diff_status) = find_file_type_and_diff_status($path);
if (!$file_type && !$diff_status) {
- print STDERR "$path: (Not a versioned resource)\n\n";
- return;
+ print STDERR "svn: '$path' is not under version control\n";
+ exit 1;
}
my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
@@ -825,21 +890,21 @@ sub cmd_info {
# canonicalize_path() will return "" to make libsvn 1.5.x happy,
$path = "." if $path eq "";
- my $full_url = $url . ($path eq "." ? "" : "/$path");
+ my $full_url = $url . ($fullpath eq "" ? "" : "/$fullpath");
if ($_url) {
- print $full_url, "\n";
+ print escape_url($full_url), "\n";
return;
}
my $result = "Path: $path\n";
$result .= "Name: " . basename($path) . "\n" if $file_type ne "dir";
- $result .= "URL: " . $full_url . "\n";
+ $result .= "URL: " . escape_url($full_url) . "\n";
eval {
my $repos_root = $gs->repos_root;
Git::SVN::remove_username($repos_root);
- $result .= "Repository Root: $repos_root\n";
+ $result .= "Repository Root: " . escape_url($repos_root) . "\n";
};
if ($@) {
$result .= "Repository Root: (offline)\n";
@@ -861,7 +926,7 @@ sub cmd_info {
}
my ($lc_author, $lc_rev, $lc_date_utc);
- my @args = Git::SVN::Log::git_svn_log_cmd($rev, $rev, "--", $path);
+ my @args = Git::SVN::Log::git_svn_log_cmd($rev, $rev, "--", $fullpath);
my $log = command_output_pipe(@args);
my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
while (<$log>) {
@@ -2606,9 +2671,9 @@ sub rebuild_from_rev_db {
sub rebuild {
my ($self) = @_;
my $map_path = $self->map_path;
- return if (-e $map_path && ! -z $map_path);
+ my $partial = (-e $map_path && ! -z $map_path);
return unless ::verify_ref($self->refname.'^0');
- if ($self->use_svm_props || $self->no_metadata) {
+ if (!$partial && ($self->use_svm_props || $self->no_metadata)) {
my $rev_db = $self->rev_db_path;
$self->rebuild_from_rev_db($rev_db);
if ($self->use_svm_props) {
@@ -2618,10 +2683,13 @@ sub rebuild {
$self->unlink_rev_db_symlink;
return;
}
- print "Rebuilding $map_path ...\n";
+ print "Rebuilding $map_path ...\n" if (!$partial);
+ my ($base_rev, $head) = ($partial ? $self->rev_map_max_norebuild(1) :
+ (undef, undef));
my ($log, $ctx) =
command_output_pipe(qw/rev-list --pretty=raw --no-color --reverse/,
- $self->refname, '--');
+ ($head ? "$head.." : "") . $self->refname,
+ '--');
my $metadata_url = $self->metadata_url;
remove_username($metadata_url);
my $svn_uuid = $self->ra_uuid;
@@ -2644,12 +2712,17 @@ sub rebuild {
($metadata_url && $url && ($url ne $metadata_url))) {
next;
}
+ if ($partial && $head) {
+ print "Partial-rebuilding $map_path ...\n";
+ print "Currently at $base_rev = $head\n";
+ $head = undef;
+ }
$self->rev_map_set($rev, $c);
print "r$rev = $c\n";
}
command_close_pipe($log, $ctx);
- print "Done rebuilding $map_path\n";
+ print "Done rebuilding $map_path\n" if (!$partial || !$head);
my $rev_db_path = $self->rev_db_path;
if (-f $self->rev_db_path) {
unlink $self->rev_db_path or croak "unlink: $!";
@@ -2789,6 +2862,12 @@ sub rev_map_set {
sub rev_map_max {
my ($self, $want_commit) = @_;
$self->rebuild;
+ my ($r, $c) = $self->rev_map_max_norebuild($want_commit);
+ $want_commit ? ($r, $c) : $r;
+}
+
+sub rev_map_max_norebuild {
+ my ($self, $want_commit) = @_;
my $map_path = $self->map_path;
stat $map_path or return $want_commit ? (0, undef) : 0;
sysopen(my $fh, $map_path, O_RDONLY) or croak "open: $!";
@@ -3380,11 +3459,12 @@ sub generate_diff {
while (<$diff_fh>) {
chomp $_; # this gets rid of the trailing "\0"
if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
- $::sha1\s($::sha1)\s
+ ($::sha1)\s($::sha1)\s
([MTCRAD])\d*$/xo) {
push @mods, { mode_a => $1, mode_b => $2,
- sha1_b => $3, chg => $4 };
- if ($4 =~ /^(?:C|R)$/) {
+ sha1_a => $3, sha1_b => $4,
+ chg => $5 };
+ if ($5 =~ /^(?:C|R)$/) {
$state = 'file_a';
} else {
$state = 'file_b';
@@ -3636,6 +3716,7 @@ sub R {
my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
$self->url_path($m->{file_a}), $self->{r});
print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
+ $self->apply_autoprops($file, $fbat);
$self->chg_file($fbat, $m);
$self->close_file($fbat,undef,$self->{pool});
@@ -3662,33 +3743,52 @@ sub change_file_prop {
$self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
}
-sub chg_file {
- my ($self, $fbat, $m) = @_;
- if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
- $self->change_file_prop($fbat,'svn:executable','*');
- } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
- $self->change_file_prop($fbat,'svn:executable',undef);
- }
- my $fh = Git::temp_acquire('git_blob');
- if ($m->{mode_b} =~ /^120/) {
+sub _chg_file_get_blob ($$$$) {
+ my ($self, $fbat, $m, $which) = @_;
+ my $fh = Git::temp_acquire("git_blob_$which");
+ if ($m->{"mode_$which"} =~ /^120/) {
print $fh 'link ' or croak $!;
$self->change_file_prop($fbat,'svn:special','*');
- } elsif ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
+ } elsif ($m->{mode_a} =~ /^120/ && $m->{"mode_$which"} !~ /^120/) {
$self->change_file_prop($fbat,'svn:special',undef);
}
- my $size = $::_repository->cat_blob($m->{sha1_b}, $fh);
- croak "Failed to read object $m->{sha1_b}" if ($size < 0);
+ my $blob = $m->{"sha1_$which"};
+ return ($fh,) if ($blob =~ /^0{40}$/);
+ my $size = $::_repository->cat_blob($blob, $fh);
+ croak "Failed to read object $blob" if ($size < 0);
$fh->flush == 0 or croak $!;
seek $fh, 0, 0 or croak $!;
my $exp = ::md5sum($fh);
seek $fh, 0, 0 or croak $!;
+ return ($fh, $exp);
+}
+sub chg_file {
+ my ($self, $fbat, $m) = @_;
+ if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
+ $self->change_file_prop($fbat,'svn:executable','*');
+ } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
+ $self->change_file_prop($fbat,'svn:executable',undef);
+ }
+ my ($fh_a, $exp_a) = _chg_file_get_blob $self, $fbat, $m, 'a';
+ my ($fh_b, $exp_b) = _chg_file_get_blob $self, $fbat, $m, 'b';
my $pool = SVN::Pool->new;
- my $atd = $self->apply_textdelta($fbat, undef, $pool);
- my $got = SVN::TxDelta::send_stream($fh, @$atd, $pool);
- die "Checksum mismatch\nexpected: $exp\ngot: $got\n" if ($got ne $exp);
- Git::temp_release($fh, 1);
+ my $atd = $self->apply_textdelta($fbat, $exp_a, $pool);
+ if (-s $fh_a) {
+ my $txstream = SVN::TxDelta::new ($fh_a, $fh_b, $pool);
+ my $res = SVN::TxDelta::send_txstream($txstream, @$atd, $pool);
+ if (defined $res) {
+ die "Unexpected result from send_txstream: $res\n",
+ "(SVN::Core::VERSION: $SVN::Core::VERSION)\n";
+ }
+ } else {
+ my $got = SVN::TxDelta::send_stream($fh_b, @$atd, $pool);
+ die "Checksum mismatch\nexpected: $exp_b\ngot: $got\n"
+ if ($got ne $exp_b);
+ }
+ Git::temp_release($fh_b, 1);
+ Git::temp_release($fh_a, 1);
$pool->clear;
}
diff --git a/git-web--browse.sh b/git-web--browse.sh
index 384148a..78d236b 100755
--- a/git-web--browse.sh
+++ b/git-web--browse.sh
@@ -31,7 +31,7 @@ valid_custom_tool()
valid_tool() {
case "$1" in
- firefox | iceweasel | konqueror | w3m | links | lynx | dillo | open)
+ firefox | iceweasel | konqueror | w3m | links | lynx | dillo | open | start)
;; # happy
*)
valid_custom_tool "$1" || return 1
@@ -114,6 +114,10 @@ if test -z "$browser" ; then
if test -n "$SECURITYSESSIONID"; then
browser_candidates="open $browser_candidates"
fi
+ # /bin/start indicates MinGW
+ if test -n /bin/start; then
+ browser_candidates="start $browser_candidates"
+ fi
for i in $browser_candidates; do
init_browser_path $i
@@ -157,7 +161,7 @@ case "$browser" in
;;
esac
;;
- w3m|links|lynx|open)
+ w3m|links|lynx|open|start)
eval "$browser_path" "$@"
;;
dillo)
diff --git a/git.c b/git.c
index 5582c51..89feb0b 100644
--- a/git.c
+++ b/git.c
@@ -330,6 +330,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "prune-packed", cmd_prune_packed, RUN_SETUP },
{ "push", cmd_push, RUN_SETUP },
{ "read-tree", cmd_read_tree, RUN_SETUP },
+ { "receive-pack", cmd_receive_pack },
{ "reflog", cmd_reflog, RUN_SETUP },
{ "remote", cmd_remote, RUN_SETUP },
{ "repo-config", cmd_config },
@@ -366,7 +367,7 @@ static void handle_internal_command(int argc, const char **argv)
if (sizeof(ext) > 1) {
i = strlen(argv[0]) - strlen(ext);
if (i > 0 && !strcmp(argv[0] + i, ext)) {
- char *argv0 = strdup(argv[0]);
+ char *argv0 = xstrdup(argv[0]);
argv[0] = cmd = argv0;
argv0[i] = '\0';
}
@@ -388,10 +389,9 @@ static void handle_internal_command(int argc, const char **argv)
static void execv_dashed_external(const char **argv)
{
- struct strbuf cmd;
+ struct strbuf cmd = STRBUF_INIT;
const char *tmp;
- strbuf_init(&cmd, 0);
strbuf_addf(&cmd, "git-%s", argv[0]);
/*
@@ -501,7 +501,9 @@ int main(int argc, const char **argv)
cmd, argv[0]);
exit(1);
}
- help_unknown_cmd(cmd);
+ argv[0] = help_unknown_cmd(cmd);
+ handle_internal_command(argc, argv);
+ execv_dashed_external(argv);
}
fprintf(stderr, "Failed to run command '%s': %s\n",
diff --git a/git.spec.in b/git.spec.in
index c6492e5..6733b6f 100644
--- a/git.spec.in
+++ b/git.spec.in
@@ -145,6 +145,7 @@ rm -rf $RPM_BUILD_ROOT
%files cvs
%defattr(-,root,root)
%doc Documentation/*git-cvs*.txt
+%{_bindir}/git-cvsserver
%{_libexecdir}/git-core/*cvs*
%{!?_without_docs: %{_mandir}/man1/*cvs*.1*}
%{!?_without_docs: %doc Documentation/*git-cvs*.html }
@@ -188,6 +189,9 @@ rm -rf $RPM_BUILD_ROOT
# No files for you!
%changelog
+* Fri Sep 12 2008 Quy Tonthat <qtonthat@gmail.com>
+- move git-cvsserver to bindir.
+
* Sun Jun 15 2008 Junio C Hamano <gitster@pobox.com>
- Remove curl from Requires list.
diff --git a/gitk-git/gitk b/gitk-git/gitk
index 087c4ac..bcebc87 100644
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -269,7 +269,7 @@ proc parseviewrevs {view revs} {
lappend badrev $line
}
}
- error_popup "Error parsing revisions: $err"
+ error_popup "[mc "Error parsing revisions:"] $err"
return {}
}
set ret {}
@@ -307,7 +307,7 @@ proc start_rev_list {view} {
global startmsecs commitidx viewcomplete curview
global tclencoding
global viewargs viewargscmd viewfiles vfilelimit
- global showlocalchanges commitinterest
+ global showlocalchanges
global viewactive viewinstances vmergeonly
global mainheadid
global vcanopt vflags vrevs vorigargs
@@ -324,7 +324,7 @@ proc start_rev_list {view} {
if {[catch {
set str [exec sh -c $viewargscmd($view)]
} err]} {
- error_popup "Error executing --argscmd command: $err"
+ error_popup "[mc "Error executing --argscmd command:"] $err"
return 0
}
set args [concat $args [split $str "\n"]]
@@ -368,7 +368,7 @@ proc start_rev_list {view} {
set i [reg_instance $fd]
set viewinstances($view) [list $i]
if {$showlocalchanges && $mainheadid ne {}} {
- lappend commitinterest($mainheadid) {dodiffindex}
+ interestedin $mainheadid dodiffindex
}
fconfigure $fd -blocking 0 -translation lf -eofchar {}
if {$tclencoding != {}} {
@@ -418,10 +418,12 @@ proc stop_rev_list {view} {
}
proc reset_pending_select {selid} {
- global pending_select mainheadid
+ global pending_select mainheadid selectheadid
if {$selid ne {}} {
set pending_select $selid
+ } elseif {$selectheadid ne {}} {
+ set pending_select $selectheadid
} else {
set pending_select $mainheadid
}
@@ -498,7 +500,7 @@ proc updatecommits {} {
set fd [open [concat | git log --no-color -z --pretty=raw --parents \
--boundary $args "--" $vfilelimit($view)] r]
} err]} {
- error_popup "Error executing git log: $err"
+ error_popup "[mc "Error executing git log:"] $err"
return
}
if {$viewactive($view) == 0} {
@@ -1229,7 +1231,7 @@ proc commitonrow {row} {
proc closevarcs {v} {
global varctok varccommits varcid parents children
- global cmitlisted commitidx commitinterest vtokmod
+ global cmitlisted commitidx vtokmod
set missing_parents 0
set scripts {}
@@ -1254,12 +1256,7 @@ proc closevarcs {v} {
}
lappend varccommits($v,$b) $p
incr commitidx($v)
- if {[info exists commitinterest($p)]} {
- foreach script $commitinterest($p) {
- lappend scripts [string map [list "%I" $p] $script]
- }
- unset commitinterest($id)
- }
+ set scripts [check_interest $p $scripts]
}
}
if {$missing_parents > 0} {
@@ -1295,8 +1292,41 @@ proc rewrite_commit {v id rwid} {
}
}
+# Mechanism for registering a command to be executed when we come
+# across a particular commit. To handle the case when only the
+# prefix of the commit is known, the commitinterest array is now
+# indexed by the first 4 characters of the ID. Each element is a
+# list of id, cmd pairs.
+proc interestedin {id cmd} {
+ global commitinterest
+
+ lappend commitinterest([string range $id 0 3]) $id $cmd
+}
+
+proc check_interest {id scripts} {
+ global commitinterest
+
+ set prefix [string range $id 0 3]
+ if {[info exists commitinterest($prefix)]} {
+ set newlist {}
+ foreach {i script} $commitinterest($prefix) {
+ if {[string match "$i*" $id]} {
+ lappend scripts [string map [list "%I" $id "%P" $i] $script]
+ } else {
+ lappend newlist $i $script
+ }
+ }
+ if {$newlist ne {}} {
+ set commitinterest($prefix) $newlist
+ } else {
+ unset commitinterest($prefix)
+ }
+ }
+ return $scripts
+}
+
proc getcommitlines {fd inst view updating} {
- global cmitlisted commitinterest leftover
+ global cmitlisted leftover
global commitidx commitdata vdatemode
global parents children curview hlview
global idpending ordertok
@@ -1472,12 +1502,7 @@ proc getcommitlines {fd inst view updating} {
incr i
}
- if {[info exists commitinterest($id)]} {
- foreach script $commitinterest($id) {
- lappend scripts [string map [list "%I" $id] $script]
- }
- unset commitinterest($id)
- }
+ set scripts [check_interest $id $scripts]
set gotsome 1
}
if {$gotsome} {
@@ -1606,9 +1631,23 @@ proc getcommit {id} {
return 1
}
+# Expand an abbreviated commit ID to a list of full 40-char IDs that match
+# and are present in the current view.
+# This is fairly slow...
+proc longid {prefix} {
+ global varcid curview
+
+ set ids {}
+ foreach match [array names varcid "$curview,$prefix*"] {
+ lappend ids [lindex [split $match ","] 1]
+ }
+ return $ids
+}
+
proc readrefs {} {
global tagids idtags headids idheads tagobjid
global otherrefids idotherrefs mainhead mainheadid
+ global selecthead selectheadid
foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
catch {unset $v}
@@ -1655,6 +1694,12 @@ proc readrefs {} {
set mainhead [string range $thehead 11 end]
}
}
+ set selectheadid {}
+ if {$selecthead ne {}} {
+ catch {
+ set selectheadid [exec git rev-parse --verify $selecthead]
+ }
+ }
}
# skip over fake commits
@@ -1741,6 +1786,53 @@ proc setoptions {} {
option add *Entry.font uifont startupFile
}
+# Make a menu and submenus.
+# m is the window name for the menu, items is the list of menu items to add.
+# Each item is a list {mc label type description options...}
+# mc is ignored; it's so we can put mc there to alert xgettext
+# label is the string that appears in the menu
+# type is cascade, command or radiobutton (should add checkbutton)
+# description depends on type; it's the sublist for cascade, the
+# command to invoke for command, or {variable value} for radiobutton
+proc makemenu {m items} {
+ menu $m
+ foreach i $items {
+ set name [mc [lindex $i 1]]
+ set type [lindex $i 2]
+ set thing [lindex $i 3]
+ set params [list $type]
+ if {$name ne {}} {
+ set u [string first "&" [string map {&& x} $name]]
+ lappend params -label [string map {&& & & {}} $name]
+ if {$u >= 0} {
+ lappend params -underline $u
+ }
+ }
+ switch -- $type {
+ "cascade" {
+ set submenu [string tolower [string map {& ""} [lindex $i 1]]]
+ lappend params -menu $m.$submenu
+ }
+ "command" {
+ lappend params -command $thing
+ }
+ "radiobutton" {
+ lappend params -variable [lindex $thing 0] \
+ -value [lindex $thing 1]
+ }
+ }
+ eval $m add $params [lrange $i 4 end]
+ if {$type eq "cascade"} {
+ makemenu $m.$submenu $thing
+ }
+ }
+}
+
+# translate string and remove ampersands
+proc mca {str} {
+ return [string map {&& & & {}} [mc $str]]
+}
+
proc makewindow {} {
global canv canv2 canv3 linespc charspc ctext cflist cscroll
global tabstop
@@ -1758,33 +1850,31 @@ proc makewindow {} {
global rprogitem rprogcoord rownumsel numcommits
global have_tk85
- menu .bar
- .bar add cascade -label [mc "File"] -menu .bar.file
- menu .bar.file
- .bar.file add command -label [mc "Update"] -command updatecommits
- .bar.file add command -label [mc "Reload"] -command reloadcommits
- .bar.file add command -label [mc "Reread references"] -command rereadrefs
- .bar.file add command -label [mc "List references"] -command showrefs
- .bar.file add command -label [mc "Quit"] -command doquit
- menu .bar.edit
- .bar add cascade -label [mc "Edit"] -menu .bar.edit
- .bar.edit add command -label [mc "Preferences"] -command doprefs
-
- menu .bar.view
- .bar add cascade -label [mc "View"] -menu .bar.view
- .bar.view add command -label [mc "New view..."] -command {newview 0}
- .bar.view add command -label [mc "Edit view..."] -command editview \
- -state disabled
- .bar.view add command -label [mc "Delete view"] -command delview -state disabled
- .bar.view add separator
- .bar.view add radiobutton -label [mc "All files"] -command {showview 0} \
- -variable selectedview -value 0
-
- menu .bar.help
- .bar add cascade -label [mc "Help"] -menu .bar.help
- .bar.help add command -label [mc "About gitk"] -command about
- .bar.help add command -label [mc "Key bindings"] -command keys
- .bar.help configure
+ # The "mc" arguments here are purely so that xgettext
+ # sees the following string as needing to be translated
+ makemenu .bar {
+ {mc "File" cascade {
+ {mc "Update" command updatecommits -accelerator F5}
+ {mc "Reload" command reloadcommits}
+ {mc "Reread references" command rereadrefs}
+ {mc "List references" command showrefs}
+ {mc "Quit" command doquit}
+ }}
+ {mc "Edit" cascade {
+ {mc "Preferences" command doprefs}
+ }}
+ {mc "View" cascade {
+ {mc "New view..." command {newview 0}}
+ {mc "Edit view..." command editview -state disabled}
+ {mc "Delete view" command delview -state disabled}
+ {xx "" separator}
+ {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
+ }}
+ {mc "Help" cascade {
+ {mc "About gitk" command about}
+ {mc "Key bindings" command keys}
+ }}
+ }
. configure -menu .bar
# the gui has upper and lower half, parts of a paned window.
@@ -2152,59 +2242,55 @@ proc makewindow {} {
bind . <Destroy> {stop_backends}
bind . <Button-1> "click %W"
bind $fstring <Key-Return> {dofind 1 1}
- bind $sha1entry <Key-Return> gotocommit
+ bind $sha1entry <Key-Return> {gotocommit; break}
bind $sha1entry <<PasteSelection>> clearsha1
bind $cflist <1> {sel_flist %W %x %y; break}
bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
- bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
+ global ctxbut
+ bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
set maincursor [. cget -cursor]
set textcursor [$ctext cget -cursor]
set curtextcursor $textcursor
set rowctxmenu .rowctxmenu
- menu $rowctxmenu -tearoff 0
- $rowctxmenu add command -label [mc "Diff this -> selected"] \
- -command {diffvssel 0}
- $rowctxmenu add command -label [mc "Diff selected -> this"] \
- -command {diffvssel 1}
- $rowctxmenu add command -label [mc "Make patch"] -command mkpatch
- $rowctxmenu add command -label [mc "Create tag"] -command mktag
- $rowctxmenu add command -label [mc "Write commit to file"] -command writecommit
- $rowctxmenu add command -label [mc "Create new branch"] -command mkbranch
- $rowctxmenu add command -label [mc "Cherry-pick this commit"] \
- -command cherrypick
- $rowctxmenu add command -label [mc "Reset HEAD branch to here"] \
- -command resethead
+ makemenu $rowctxmenu {
+ {mc "Diff this -> selected" command {diffvssel 0}}
+ {mc "Diff selected -> this" command {diffvssel 1}}
+ {mc "Make patch" command mkpatch}
+ {mc "Create tag" command mktag}
+ {mc "Write commit to file" command writecommit}
+ {mc "Create new branch" command mkbranch}
+ {mc "Cherry-pick this commit" command cherrypick}
+ {mc "Reset HEAD branch to here" command resethead}
+ }
+ $rowctxmenu configure -tearoff 0
set fakerowmenu .fakerowmenu
- menu $fakerowmenu -tearoff 0
- $fakerowmenu add command -label [mc "Diff this -> selected"] \
- -command {diffvssel 0}
- $fakerowmenu add command -label [mc "Diff selected -> this"] \
- -command {diffvssel 1}
- $fakerowmenu add command -label [mc "Make patch"] -command mkpatch
-# $fakerowmenu add command -label [mc "Commit"] -command {mkcommit 0}
-# $fakerowmenu add command -label [mc "Commit all"] -command {mkcommit 1}
-# $fakerowmenu add command -label [mc "Revert local changes"] -command revertlocal
+ makemenu $fakerowmenu {
+ {mc "Diff this -> selected" command {diffvssel 0}}
+ {mc "Diff selected -> this" command {diffvssel 1}}
+ {mc "Make patch" command mkpatch}
+ }
+ $fakerowmenu configure -tearoff 0
set headctxmenu .headctxmenu
- menu $headctxmenu -tearoff 0
- $headctxmenu add command -label [mc "Check out this branch"] \
- -command cobranch
- $headctxmenu add command -label [mc "Remove this branch"] \
- -command rmbranch
+ makemenu $headctxmenu {
+ {mc "Check out this branch" command cobranch}
+ {mc "Remove this branch" command rmbranch}
+ }
+ $headctxmenu configure -tearoff 0
global flist_menu
set flist_menu .flistctxmenu
- menu $flist_menu -tearoff 0
- $flist_menu add command -label [mc "Highlight this too"] \
- -command {flist_hl 0}
- $flist_menu add command -label [mc "Highlight this only"] \
- -command {flist_hl 1}
- $flist_menu add command -label [mc "External diff"] \
- -command {external_diff}
+ makemenu $flist_menu {
+ {mc "Highlight this too" command {flist_hl 0}}
+ {mc "Highlight this only" command {flist_hl 1}}
+ {mc "External diff" command {external_diff}}
+ {mc "Blame parent commit" command {external_blame 1}}
+ }
+ $flist_menu configure -tearoff 0
}
# Windows sends all mouse wheel events to the current focused window, not
@@ -2320,7 +2406,7 @@ proc savestuff {w} {
global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
global cmitmode wrapcomment datetimeformat limitdiffs
global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
- global autoselect extdifftool
+ global autoselect extdifftool perfile_attrs
if {$stuffsaved} return
if {![winfo viewable .]} return
@@ -2347,6 +2433,7 @@ proc savestuff {w} {
puts $f [list set diffcontext $diffcontext]
puts $f [list set selectbgcolor $selectbgcolor]
puts $f [list set extdifftool $extdifftool]
+ puts $f [list set perfile_attrs $perfile_attrs]
puts $f "set geometry(main) [wm geometry .]"
puts $f "set geometry(topwidth) [winfo width .tf]"
@@ -2694,7 +2781,7 @@ proc treeopendir {w dir} {
$w insert e:$ix $e [highlight_tag $de]
}
}
- $w mark gravity e:$ix left
+ $w mark gravity e:$ix right
$w conf -state disabled
set treediropen($dir) 1
set top [lindex [split [$w index @0,0] .] 0]
@@ -2925,7 +3012,7 @@ proc save_file_from_commit {filename output what} {
if {[string match "fatal: bad revision *" $err]} {
return $nullfile
}
- error_popup "Error getting \"$filename\" from $what: $err"
+ error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
return {}
}
return $output
@@ -2982,7 +3069,7 @@ proc external_diff {} {