Merge branch 'maint'
authorShawn O. Pearce <spearce@spearce.org>
Fri, 26 Sep 2008 15:31:56 +0000 (08:31 -0700)
committerShawn O. Pearce <spearce@spearce.org>
Fri, 26 Sep 2008 15:31:56 +0000 (08:31 -0700)
* maint:
Remove empty directories in recursive merge
Documentation: clarify the details of overriding LESS via core.pager

Conflicts:
builtin-merge-recursive.c

228 files changed:
.gitignore
Documentation/Makefile
Documentation/RelNotes-1.6.1.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-apply.txt
Documentation/git-checkout.txt
Documentation/git-commit.txt
Documentation/git-count-objects.txt
Documentation/git-daemon.txt
Documentation/git-diff-tree.txt
Documentation/git-for-each-ref.txt
Documentation/git-hash-object.txt
Documentation/git-help.txt
Documentation/git-imap-send.txt
Documentation/git-merge-base.txt
Documentation/git-merge.txt
Documentation/git-rebase.txt
Documentation/git-submodule.txt
Documentation/git-web--browse.txt
Documentation/gitattributes.txt
Documentation/pretty-formats.txt
Documentation/rev-list-options.txt
INSTALL
Makefile
RelNotes
abspath.c
builtin-add.c
builtin-apply.c
builtin-blame.c
builtin-checkout.c
builtin-clone.c
builtin-commit-tree.c
builtin-commit.c
builtin-count-objects.c
builtin-diff-tree.c
builtin-diff.c
builtin-fetch-pack.c
builtin-for-each-ref.c
builtin-help.c [new file with mode: 0644]
builtin-http-fetch.c
builtin-log.c
builtin-merge-base.c
builtin-merge-recursive.c
builtin-merge.c
builtin-pack-objects.c
builtin-push.c
builtin-receive-pack.c [new file with mode: 0644]
builtin-reflog.c
builtin-revert.c
builtin-send-pack.c
builtin-update-index.c
builtin.h
cache.h
combine-diff.c
commit.h
compat/mingw.c
compat/mingw.h
config.mak.in
configure.ac
connect.c
contrib/completion/git-completion.bash
contrib/fast-import/git-p4
csum-file.c
ctype.c
daemon.c
diff-lib.c
diff-no-index.c
diff.c
diff.h
dir.c
fast-import.c
git-bisect.sh
git-compat-util.h
git-filter-branch.sh
git-gui/.gitattributes [new file with mode: 0644]
git-gui/git-gui.sh
git-gui/lib/blame.tcl
git-gui/lib/browser.tcl
git-gui/lib/commit.tcl
git-gui/lib/diff.tcl
git-gui/lib/encoding.tcl
git-gui/lib/index.tcl
git-gui/lib/mergetool.tcl [new file with mode: 0644]
git-gui/lib/option.tcl
git-gui/po/de.po
git-merge-octopus.sh
git-submodule.sh
git-svn.perl
git-web--browse.sh
git.c
git.spec.in
gitk-git/gitk
gitweb/gitweb.css
gitweb/gitweb.perl
graph.c
graph.h
hash-object.c
help.c
help.h [new file with mode: 0644]
http-push.c
http.c
imap-send.c
levenshtein.c [new file with mode: 0644]
levenshtein.h [new file with mode: 0644]
log-tree.c
log-tree.h
merge-recursive.c [new file with mode: 0644]
merge-recursive.h
object.h
pager.c
pretty.c
read-cache.c
receive-pack.c [deleted file]
refs.c
remote.c
remote.h
rerere.c
revision.c
revision.h
run-command.c
run-command.h
sha1_file.c
sha1_name.c
t/.gitignore
t/Makefile
t/lib-git-svn.sh
t/lib-httpd.sh
t/t0022-crlf-rename.sh
t/t0055-beyond-symlinks.sh [new file with mode: 0755]
t/t1000-read-tree-m-3way.sh
t/t1007-hash-object.sh
t/t3504-cherry-pick-rerere.sh [new file with mode: 0755]
t/t3900-i18n-commit.sh
t/t3901-i18n-patch.sh
t/t4000-diff-format.sh
t/t4001-diff-rename.sh
t/t4002-diff-basic.sh
t/t4003-diff-rename-1.sh
t/t4004-diff-rename-symlink.sh
t/t4005-diff-rename-2.sh
t/t4007-rename-3.sh
t/t4008-diff-break-rewrite.sh
t/t4009-diff-rename-4.sh
t/t4010-diff-pathspec.sh
t/t4011-diff-symlink.sh
t/t4012-diff-binary.sh
t/t4013-diff-various.sh
t/t4014-format-patch.sh
t/t4015-diff-whitespace.sh
t/t4020-diff-external.sh
t/t4022-diff-rewrite.sh
t/t4023-diff-rename-typechange.sh
t/t4027-diff-submodule.sh
t/t4029-diff-trailing-space.sh [new file with mode: 0755]
t/t4100-apply-stat.sh
t/t4101-apply-nonl.sh
t/t5100-mailinfo.sh
t/t5300-pack-object.sh
t/t5500-fetch-pack.sh
t/t5510-fetch.sh
t/t5515-fetch-merge-logic.sh
t/t5540-http-push.sh
t/t6002-rev-list-bisect.sh
t/t6003-rev-list-topo-order.sh
t/t6010-merge-base.sh
t/t6012-rev-list-simplify.sh [new file with mode: 0755]
t/t6013-rev-list-reverse-parents.sh [new file with mode: 0755]
t/t6023-merge-file.sh
t/t6026-merge-attr.sh
t/t6027-merge-binary.sh
t/t6030-bisect-porcelain.sh
t/t6101-rev-parse-parents.sh
t/t6200-fmt-merge-msg.sh
t/t6300-for-each-ref.sh
t/t7001-mv.sh
t/t7003-filter-branch.sh
t/t7004-tag.sh
t/t7101-reset.sh
t/t7201-co.sh
t/t7500-commit.sh
t/t7502-status.sh
t/t7600-merge.sh
t/t7603-merge-reduce-heads.sh
t/t7606-merge-custom.sh [new file with mode: 0755]
t/t8001-annotate.sh
t/t8002-blame.sh
t/t9100-git-svn-basic.sh
t/t9101-git-svn-props.sh
t/t9102-git-svn-deep-rmdir.sh
t/t9103-git-svn-tracked-directory-removed.sh
t/t9104-git-svn-follow-parent.sh
t/t9105-git-svn-commit-diff.sh
t/t9106-git-svn-commit-diff-clobber.sh
t/t9106-git-svn-dcommit-clobber-series.sh
t/t9107-git-svn-migrate.sh
t/t9108-git-svn-glob.sh
t/t9108-git-svn-multi-glob.sh
t/t9110-git-svn-use-svm-props.sh
t/t9111-git-svn-use-svnsync-props.sh
t/t9112-git-svn-md5less-file.sh
t/t9113-git-svn-dcommit-new-file.sh
t/t9114-git-svn-dcommit-merge.sh
t/t9115-git-svn-dcommit-funky-renames.sh
t/t9116-git-svn-log.sh
t/t9117-git-svn-init-clone.sh
t/t9118-git-svn-funky-branch-names.sh
t/t9119-git-svn-info.sh
t/t9120-git-svn-clone-with-percent-escapes.sh
t/t9121-git-svn-fetch-renamed-dir.sh
t/t9122-git-svn-author.sh
t/t9123-git-svn-rebuild-with-rewriteroot.sh
t/t9124-git-svn-dcommit-auto-props.sh
t/t9125-git-svn-multi-glob-branch-names.sh
t/t9127-git-svn-partial-rebuild.sh [new file with mode: 0755]
t/t9200-git-cvsexportcommit.sh
t/t9300-fast-import.sh
t/t9301-fast-export.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9600-cvsimport.sh
t/t9700-perl-git.sh
t/t9700/test.pl
t/test-lib.sh
transport.c
wt-status.c
xdiff-interface.c
xdiff-interface.h
index a213e8e25bb2442326e86cbfb9ef56319f482869..bbaf9de0bca797508b9c2a011d0d18eac8907157 100644 (file)
@@ -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
index 62269e39c4edf95b2cf2e6a60bff2a33b239b07e..ded0e40b978e6f4b85530541c45782f2b383ea44 100644 (file)
@@ -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 (file)
index 0000000..421e569
--- /dev/null
@@ -0,0 +1,140 @@
+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.
+
+(portability)
+
+* ...
+
+(documentation)
+
+* ...
+
+(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.
+
+(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 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 cherry-pick" can also utilize rerere for conflict resolution.
+
+* "git commit --author=$name" can look up author name from existing
+  commits.
+
+* "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 mimick --suppress-blank-empty from GNU diff via a
+  configuration option.
+
+* "git diff" learned to put more sensible hunk headers for Python and
+  HTML contents.
+
+* "git diff" learned to vary the a/ vs b/ prefix depending on what are
+  being compared, controlled by diff.mnemonicprefix configuration.
+
+* "git for-each-ref" learned "refname:short" token that gives an
+  unambiguously abbreviated refname.
+
+* "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.
+
+(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.
+
+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 droppped support for such a use case long time ago.  This has
+  been resurrected.
+
+* "git filter-branch" failed to rewrite a tag name with slashes in it.
+
+* "git 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-295-g34a5d35
+echo O=$(git describe master)
+git shortlog --no-merges $O..master ^maint
index 841bead9db18a025638570c10cac72bcf4791f68..a1e9100f9e3ccb8466ec603e154cb230dc2cb33b 100644 (file)
@@ -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 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.
 
index 87b028fbc1817c9c641e5fe03aab641e3bd5eb63..1f805b2ecac4dad9e066b3392fdafc62caaa5b96 100644 (file)
@@ -581,6 +581,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 +594,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'.
@@ -795,6 +815,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
index 746646bb3d09a16a5880fa6bff286389aae98870..7788d4fa4a1209cbed564a20f882f0946ba400cf 100644 (file)
@@ -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 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
index feb51f124ac8a806e65d41f6274c58de64d2991f..e726510ab158a2d8c58782bfbb7f0c7adf4b8c6c 100644 (file)
@@ -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
index 5aa69c0e12a6756fd6f79c117008a373f65ba5f5..be54a0299fb2b6849993aa27d18c829ce91e7e00 100644 (file)
@@ -8,7 +8,7 @@ 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' [-q] [-f] [--track | --no-track] [-b <new_branch> [-l]] [-m] [<branch>]
 'git checkout' [<tree-ish>] [--] <paths>...
 
 DESCRIPTION
@@ -21,6 +21,10 @@ 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
@@ -59,6 +63,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
+exlicitly give a name with '-b' in such a case.
 
 --no-track::
        Ignore the branch.autosetupmerge configuration variable.
index 0e25bb862704eee4a22fe5349c04823d14ea9cba..eb05b0f49b0c513581150415d1a4e679d7bfbea6 100644 (file)
@@ -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>::
index 75a8da1ca906aee4cc6a7d0c3ff19862b8e0fc2f..6bc1c21e6283284e2eae16d7ec4cb8183d8e0851 100644 (file)
@@ -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
index 4ba4b75c1126d87c48935e7697e05f0d5210ad2d..b08a08cd95b6da192a008c58d7973769dfe3fc8c 100644 (file)
@@ -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.
index 1fdf20dcc9169be2c7a51b32f94893442e160436..5d48664e624f59e0df16121ed67d84939f6d2a31 100644 (file)
@@ -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
index ebd7c5fbb34576fd2af98b00d9045340ff77ee2b..5061d3e4e7b8a888093c5e8c7b4cb03509391756 100644 (file)
@@ -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`).
index ac928e198e75595a6fcd4e83b89aaf68987bd420..0af40cfb85ca6e0eb6e540f0beb47e449ef25afd 100644 (file)
@@ -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>
index f414583fc48e85e4785fbf5f9431bb81a96ccd9d..d9b9c34b3a60f09bea4f9e53b5127f33f356e042 100644 (file)
@@ -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
 ~~~~~~~~~~~~~~~
index b3d8da33ee64730794821440c287f30c4bb85789..bd49a0aee8881f983077d74bd8d0fbe5764dfea5 100644 (file)
@@ -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
 ..........................
 
 
index 1a7ecbf8f39381b0c7b2da513dfa26eacec15cf6..2f0c5259e01917e456954de5eb59597a2c829b0c 100644 (file)
@@ -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
 ------
index 17a15acb07df2d8beed4a41cdcf820010f95b35b..685e1fed586cb74a50b95d10fff6c8773cc7c8e7 100644 (file)
@@ -126,13 +126,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
 --------
index 59c1b021a6c410e1097df21d6d47365aec6689e2..32f0f122e937aab6b248d0ed86fa09854dbe253d 100644 (file)
@@ -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
index bf33b0cba05e8858e28275661c731aae6c8372ee..babaa9bc46a404c4610abc4e4c4281b6c5c5ea6c 100644 (file)
@@ -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::
index 7f7a45b2eaa3997cbf5a250fb78387e2a0959a11..278cf735279c13ab129ee28f1ef8e638ccf61dee 100644 (file)
@@ -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.
 
index 89627688b81761771f17865a67d10ad85a830ea2..e848c94397dedc93652e8027df1ad081f08d2d03 100644 (file)
@@ -311,10 +311,16 @@ 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.
 
 - `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.
index 388d4925e6bc4bacb708f75437e9aaa216fcb9cc..f18d33e00b7166104a200fbeaa854fb911273931 100644 (file)
@@ -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
index 735cf07b20e17e29d96f701d97768ae610aea590..0ce916a1887b0846bfc5a6e2233242601e0dde79 100644 (file)
@@ -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/INSTALL b/INSTALL
index 2bae53fcbb990eded5626c2c47ebce8684d081cf..a4fd8624bc6550d0553f2271343f4e6b610be103 100644 (file)
--- 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
index 8d81095765b1e73fa79e2950c47fd06f3c475ac7..e0c03c3eecc26d995f8829429092e887df17489f 100644 (file)
--- 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
@@ -354,6 +357,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
@@ -430,6 +435,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 +443,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
@@ -518,6 +525,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 +546,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 +584,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 +688,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 +698,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
@@ -756,6 +769,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 +802,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 +845,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 +885,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 +1116,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 +1243,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 +1272,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 +1381,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 +1414,9 @@ install-info:
 quick-install-doc:
        $(MAKE) -C Documentation quick-install
 
+quick-install-html:
+       $(MAKE) -C Documentation quick-install-html
+
 
 
 ### Maintainer's dist rules
index a677737b90d7f1cc550d632a8e983e6e7df109fb..3d420845b117b3f3eb82d1f0948a61c7989d8af9 120000 (symlink)
--- 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
index 0d561246e0a958d9a7284409b1900a82876eebf3..8194ce1256525105d07eedf2f964ad51d9162d95 100644 (file)
--- 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';
index fc3f96eaefff91e4e85adb92162716939f0ecd72..7c874e31154a4c3f96b3403db1bdcb0b36fdec3e 100644 (file)
@@ -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;
 }
 
@@ -258,7 +233,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 +246,30 @@ 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));
+                (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);
index 20bef1f21d393b0ddf36b8336af85a70c9b8c39c..2ab4aba5a07727fa6368c6acc9b851830a9c24f3 100644 (file)
@@ -2994,29 +2994,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;
@@ -3157,10 +3173,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")) {
index 9bc901c2922403dcf73c8275da73d6d37d220729..6b7b9f4466989ca02d3a5e53caac9ff5c7e8a922 100644 (file)
@@ -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;
index c4fc2b2c562725018789dead3b86ccfbc4e2925f..075667c9cc8aabe4157900635b2aabc4f55f448c 100644 (file)
@@ -184,7 +184,7 @@ struct checkout_opts {
        int force;
        int writeout_error;
 
-       char *new_branch;
+       const char *new_branch;
        int new_branch_log;
        enum branch_track track;
 };
@@ -293,6 +293,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 +312,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;
@@ -464,13 +469,28 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
        git_config(git_default_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 (opts.force && opts.merge)
                die("git checkout: -f and -m are incompatible");
@@ -565,6 +585,18 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                return checkout_paths(source_tree, pathspec);
        }
 
+       if (opts.new_branch) {
+               struct strbuf buf;
+               strbuf_init(&buf, 0);
+               strbuf_addstr(&buf, "refs/heads/");
+               strbuf_addstr(&buf, opts.new_branch);
+               if (!get_sha1(buf.buf, rev))
+                       die("git checkout: branch %s already exists", opts.new_branch);
+               if (check_ref_format(buf.buf))
+                       die("git checkout: we do not like '%s' as a branch name.", opts.new_branch);
+               strbuf_release(&buf);
+       }
+
        if (new.name && !new.commit) {
                die("Cannot switch branch to a non-commit.");
        }
index 5b40e07ba7f13950078d36a48c2cb3f6e6c3c2c4..49d2eb9c2ba574f2c1484717f0755208e7ed8147 100644 (file)
@@ -77,7 +77,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 +140,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);
index 9b84c48dce0938f4f2884565dc25308d51c7a423..0453425c471f1d6793bc7f5f59d17e9d285ddfc6 100644 (file)
@@ -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;
        }
index c870037b07ca00aeeeb369fdae98c9b828be0af2..55e1087d2736a2b28c606664a04c7bc31d763fde 100644 (file)
@@ -320,7 +320,7 @@ 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()), 1);
 
        create_base_index();
        add_remove_files(&partial);
@@ -667,14 +667,14 @@ 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;
        const char *nl;
-       int eol, i;
+       int eol, i, start = 0;
 
        if (cleanup_mode == CLEANUP_NONE && sb->len)
                return 0;
@@ -710,6 +710,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 +745,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)
@@ -901,34 +929,14 @@ 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;
        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;
 
        git_config(git_commit_config, NULL);
 
@@ -943,13 +951,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,13 +964,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;
                FILE *fp;
 
                reflog_msg = "commit (merge)";
-               add_parent(&sb, head_sha1);
+               pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
                strbuf_init(&m, 0);
                fp = fopen(git_path("MERGE_HEAD"), "r");
                if (fp == NULL)
@@ -979,24 +980,18 @@ 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);
        } 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');
+       parents = reduce_heads(parents);
 
        /* Finally, get the commit message */
-       header_len = sb.len;
+       strbuf_init(&sb, 0);
        if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
                rollback_index_files();
                die("could not read commit message");
@@ -1009,16 +1004,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 +1021,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);
 
index 91b5487478998e39bb8ae4a5cb667360cff82c9a..ab35b65b073e9bc089fcc96f295ca3bc457c992a 100644 (file)
@@ -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;
 }
index 415cb1612f5322da89850874ba81885e41808678..8ecefd4f0f99117534deb9ca7d4446ade5c00223 100644 (file)
@@ -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"
index d5fe775fc135c0905cabec6731104009ca3bfef3..35da366f46009eab8eab693f7f56403d91d5d677 100644 (file)
@@ -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);
 
index 85509f5ee5884589980c9a1c5f96b66a3d49d5dd..fa3c936493cc0b139edf3e4e8154569f453afc02 100644 (file)
@@ -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]);
index be9dc9e3f0dbbd9bdda20db7fd5c5fad8e427bd2..e59bd8075e44a8de23503a8a59c5fdd2575a9243 100644 (file)
@@ -545,6 +545,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.
  */
@@ -570,13 +671,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-help.c b/builtin-help.c
new file mode 100644 (file)
index 0000000..64207cb
--- /dev/null
@@ -0,0 +1,465 @@
+/*
+ * 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;
+       const char *old_path = getenv("MANPATH");
+
+       strbuf_init(&new_path, 0);
+
+       /* 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;
+}
index 03f34d767ddfc3c7bc9b18d28ec7350d47564679..f3e63d7206604029504aaf85b3d2e7731d054d2c 100644 (file)
@@ -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;
index 2efe5937346ee7c0a4d85e4087a4ea49a9806005..fc5e4da822c470fdb2fb4738874a56e0103a2013 100644 (file)
@@ -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)
@@ -923,7 +905,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)
index 3382b1382a7dcbd525126a35209072da4b4d8041..b08da516e491e7089b1bb178e9a4b05c2ab36539 100644 (file)
@@ -2,9 +2,11 @@
 #include "cache.h"
 #include "commit.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;
@@ -20,7 +22,7 @@ static int show_merge_base(struct commit *rev1, struct commit *rev2, int show_al
 }
 
 static const char merge_base_usage[] =
-"git merge-base [--all] <commit-id> <commit-id>";
+"git merge-base [--all] <commit-id> <commit-id>...";
 
 static struct commit *get_commit_reference(const char *arg)
 {
@@ -38,7 +40,8 @@ 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);
@@ -51,10 +54,15 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix)
                        usage(merge_base_usage);
                argc--; argv++;
        }
-       if (argc != 3)
+       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);
+       rev = xmalloc((argc - 1) * sizeof(*rev));
+
+       do {
+               rev[rev_nr++] = get_commit_reference(argv[1]);
+               argc--; argv++;
+       } while (argc > 1);
+
+       return show_merge_base(rev, rev_nr, show_all);
 }
index f628a62751d25d394c722bbc03f9367d3973fdd0..6b534c1a66bf7a4abbe0f38add7585dee65a6a44 100644 (file)
-/*
- * 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 "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_path(const char *name)
-{
-       int ret;
-       char *slash, *dirs;
-
-       ret = unlink(name);
-       if (ret)
-               return ret;
-       dirs = xstrdup(name);
-       while ((slash = strrchr(name, '/'))) {
-               *slash = '\0';
-               if (rmdir(name) != 0)
-                       break;
-       }
-       free(dirs);
-       return ret;
-}
-
-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) && errno != ENOENT)
-                       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];
@@ -1312,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;
 }
index dcaf3681dc4433a0a258010f2b0a06fc26dc2212..5c65a5869900ad1a1014fb3aad88c874a5410bf7 100644 (file)
@@ -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,43 @@ 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 = xmalloc(sizeof(struct strategy));
+       memset(ret, 0, sizeof(struct strategy));
+       ret->name = xstrdup(name);
+       return ret;
 }
 
 static void append_strategy(struct strategy *s)
@@ -513,28 +548,65 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
        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;
+       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_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);
+               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 +731,7 @@ static int merge_trivial(void)
        parent->next = xmalloc(sizeof(struct commit_list *));
        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 +760,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 +817,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);
@@ -836,6 +904,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]);
@@ -877,12 +950,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);
@@ -1076,7 +1151,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;
index 4004e73e40db210334fa3ef69b49d07b929f2c99..1158e42cba81e2bdb8640cd6c5a3835453d17e7f 100644 (file)
@@ -1725,6 +1725,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;
index c1ed68d938f67343c6938cfef54d5ff69a522a63..cc6666f75e7db8a546d4a1589335589190e414fe 100644 (file)
@@ -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/builtin-receive-pack.c b/builtin-receive-pack.c
new file mode 100644 (file)
index 0000000..45e3cd9
--- /dev/null
@@ -0,0 +1,558 @@
+#include "cache.h"
+#include "pack.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+#include "commit.h"
+#include "object.h"
+#include "remote.h"
+#include "transport.h"
+
+static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
+
+static int deny_non_fast_forwards = 0;
+static int receive_fsck_objects;
+static int receive_unpack_limit = -1;
+static int transfer_unpack_limit = -1;
+static int unpack_limit = 100;
+static int report_status;
+
+static char capabilities[] = " report-status delete-refs ";
+static int capabilities_sent;
+
+static int receive_pack_config(const char *var, const char *value, void *cb)
+{
+       if (strcmp(var, "receive.denynonfastforwards") == 0) {
+               deny_non_fast_forwards = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "receive.unpacklimit") == 0) {
+               receive_unpack_limit = git_config_int(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "transfer.unpacklimit") == 0) {
+               transfer_unpack_limit = git_config_int(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "receive.fsckobjects") == 0) {
+               receive_fsck_objects = git_config_bool(var, value);
+               return 0;
+       }
+
+       return git_default_config(var, value, cb);
+}
+
+static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+       if (capabilities_sent)
+               packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
+       else
+               packet_write(1, "%s %s%c%s\n",
+                            sha1_to_hex(sha1), path, 0, capabilities);
+       capabilities_sent = 1;
+       return 0;
+}
+
+static void write_head_info(void)
+{
+       for_each_ref(show_ref, NULL);
+       if (!capabilities_sent)
+               show_ref("capabilities^{}", null_sha1, 0, NULL);
+
+}
+
+struct command {
+       struct command *next;
+       const char *error_string;
+       unsigned char old_sha1[20];
+       unsigned char new_sha1[20];
+       char ref_name[FLEX_ARRAY]; /* more */
+};
+
+static struct command *commands;
+
+static const char pre_receive_hook[] = "hooks/pre-receive";
+static const char post_receive_hook[] = "hooks/post-receive";
+
+static int hook_status(int code, const char *hook_name)
+{
+       switch (code) {
+       case 0:
+               return 0;
+       case -ERR_RUN_COMMAND_FORK:
+               return error("hook fork failed");
+       case -ERR_RUN_COMMAND_EXEC:
+               return error("hook execute failed");
+       case -ERR_RUN_COMMAND_PIPE:
+               return error("hook pipe failed");
+       case -ERR_RUN_COMMAND_WAITPID:
+               return error("waitpid failed");
+       case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+               return error("waitpid is confused");
+       case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+               return error("%s died of signal", hook_name);
+       case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+               return error("%s died strangely", hook_name);
+       default:
+               error("%s exited with error code %d", hook_name, -code);
+               return -code;
+       }
+}
+
+static int run_hook(const char *hook_name)
+{
+       static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
+       struct command *cmd;
+       struct child_process proc;
+       const char *argv[2];
+       int have_input = 0, code;
+
+       for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
+               if (!cmd->error_string)
+                       have_input = 1;
+       }
+
+       if (!have_input || access(hook_name, X_OK) < 0)
+               return 0;
+
+       argv[0] = hook_name;
+       argv[1] = NULL;
+
+       memset(&proc, 0, sizeof(proc));
+       proc.argv = argv;
+       proc.in = -1;
+       proc.stdout_to_stderr = 1;
+
+       code = start_command(&proc);
+       if (code)
+               return hook_status(code, hook_name);
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (!cmd->error_string) {
+                       size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
+                               sha1_to_hex(cmd->old_sha1),
+                               sha1_to_hex(cmd->new_sha1),
+                               cmd->ref_name);
+                       if (write_in_full(proc.in, buf, n) != n)
+                               break;
+               }
+       }
+       close(proc.in);
+       return hook_status(finish_command(&proc), hook_name);
+}
+
+static int run_update_hook(struct command *cmd)
+{
+       static const char update_hook[] = "hooks/update";
+       struct child_process proc;
+       const char *argv[5];
+
+       if (access(update_hook, X_OK) < 0)
+               return 0;
+
+       argv[0] = update_hook;
+       argv[1] = cmd->ref_name;
+       argv[2] = sha1_to_hex(cmd->old_sha1);
+       argv[3] = sha1_to_hex(cmd->new_sha1);
+       argv[4] = NULL;
+
+       memset(&proc, 0, sizeof(proc));
+       proc.argv = argv;
+       proc.no_stdin = 1;
+       proc.stdout_to_stderr = 1;
+
+       return hook_status(run_command(&proc), update_hook);
+}
+
+static const char *update(struct command *cmd)
+{
+       const char *name = cmd->ref_name;
+       unsigned char *old_sha1 = cmd->old_sha1;
+       unsigned char *new_sha1 = cmd->new_sha1;
+       struct ref_lock *lock;
+
+       /* only refs/... are allowed */
+       if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
+               error("refusing to create funny ref '%s' remotely", name);
+               return "funny refname";
+       }
+
+       if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
+               error("unpack should have generated %s, "
+                     "but I can't find it!", sha1_to_hex(new_sha1));
+               return "bad pack";
+       }
+       if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
+           !is_null_sha1(old_sha1) &&
+           !prefixcmp(name, "refs/heads/")) {
+               struct object *old_object, *new_object;
+               struct commit *old_commit, *new_commit;
+               struct commit_list *bases, *ent;
+
+               old_object = parse_object(old_sha1);
+               new_object = parse_object(new_sha1);
+
+               if (!old_object || !new_object ||
+                   old_object->type != OBJ_COMMIT ||
+                   new_object->type != OBJ_COMMIT) {
+                       error("bad sha1 objects for %s", name);
+                       return "bad ref";
+               }
+               old_commit = (struct commit *)old_object;
+               new_commit = (struct commit *)new_object;
+               bases = get_merge_bases(old_commit, new_commit, 1);
+               for (ent = bases; ent; ent = ent->next)
+                       if (!hashcmp(old_sha1, ent->item->object.sha1))
+                               break;
+               free_commit_list(bases);
+               if (!ent) {
+                       error("denying non-fast forward %s"
+                             " (you should pull first)", name);
+                       return "non-fast forward";
+               }
+       }
+       if (run_update_hook(cmd)) {
+               error("hook declined to update %s", name);
+               return "hook declined";
+       }
+
+       if (is_null_sha1(new_sha1)) {
+               if (!parse_object(old_sha1)) {
+                       warning ("Allowing deletion of corrupt ref.");
+                       old_sha1 = NULL;
+               }
+               if (delete_ref(name, old_sha1)) {
+                       error("failed to delete %s", name);
+                       return "failed to delete";
+               }
+               return NULL; /* good */
+       }
+       else {
+               lock = lock_any_ref_for_update(name, old_sha1, 0);
+               if (!lock) {
+                       error("failed to lock %s", name);
+                       return "failed to lock";
+               }
+               if (write_ref_sha1(lock, new_sha1, "push")) {
+                       return "failed to write"; /* error() already called */
+               }
+               return NULL; /* good */
+       }
+}
+
+static char update_post_hook[] = "hooks/post-update";
+
+static void run_update_post_hook(struct command *cmd)
+{
+       struct command *cmd_p;
+       int argc;
+       const char **argv;
+
+       for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+               if (cmd_p->error_string)
+                       continue;
+               argc++;
+       }
+       if (!argc || access(update_post_hook, X_OK) < 0)
+               return;
+       argv = xmalloc(sizeof(*argv) * (2 + argc));
+       argv[0] = update_post_hook;
+
+       for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+               char *p;
+               if (cmd_p->error_string)
+                       continue;
+               p = xmalloc(strlen(cmd_p->ref_name) + 1);
+               strcpy(p, cmd_p->ref_name);
+               argv[argc] = p;
+               argc++;
+       }
+       argv[argc] = NULL;
+       run_command_v_opt(argv, RUN_COMMAND_NO_STDIN
+               | RUN_COMMAND_STDOUT_TO_STDERR);
+}
+
+static void execute_commands(const char *unpacker_error)
+{
+       struct command *cmd = commands;
+
+       if (unpacker_error) {
+               while (cmd) {
+                       cmd->error_string = "n/a (unpacker error)";
+                       cmd = cmd->next;
+               }
+               return;
+       }
+
+       if (run_hook(pre_receive_hook)) {
+               while (cmd) {
+                       cmd->error_string = "pre-receive hook declined";
+                       cmd = cmd->next;
+               }
+               return;
+       }
+
+       while (cmd) {
+               cmd->error_string = update(cmd);
+               cmd = cmd->next;
+       }
+}
+
+static void read_head_info(void)
+{
+       struct command **p = &commands;
+       for (;;) {
+               static char line[1000];
+               unsigned char old_sha1[20], new_sha1[20];
+               struct command *cmd;
+               char *refname;
+               int len, reflen;
+
+               len = packet_read_line(0, line, sizeof(line));
+               if (!len)
+                       break;
+               if (line[len-1] == '\n')
+                       line[--len] = 0;
+               if (len < 83 ||
+                   line[40] != ' ' ||
+                   line[81] != ' ' ||
+                   get_sha1_hex(line, old_sha1) ||
+                   get_sha1_hex(line + 41, new_sha1))
+                       die("protocol error: expected old/new/ref, got '%s'",
+                           line);
+
+               refname = line + 82;
+               reflen = strlen(refname);
+               if (reflen + 82 < len) {
+                       if (strstr(refname + reflen + 1, "report-status"))
+                               report_status = 1;
+               }
+               cmd = xmalloc(sizeof(struct command) + len - 80);
+               hashcpy(cmd->old_sha1, old_sha1);
+               hashcpy(cmd->new_sha1, new_sha1);
+               memcpy(cmd->ref_name, line + 82, len - 81);
+               cmd->error_string = NULL;
+               cmd->next = NULL;
+               *p = cmd;
+               p = &cmd->next;
+       }
+}
+
+static const char *parse_pack_header(struct pack_header *hdr)
+{
+       switch (read_pack_header(0, hdr)) {
+       case PH_ERROR_EOF:
+               return "eof before pack header was fully read";
+
+       case PH_ERROR_PACK_SIGNATURE:
+               return "protocol error (pack signature mismatch detected)";
+
+       case PH_ERROR_PROTOCOL:
+               return "protocol error (pack version unsupported)";
+
+       default:
+               return "unknown error in parse_pack_header";
+
+       case 0:
+               return NULL;
+       }
+}
+
+static const char *pack_lockfile;
+
+static const char *unpack(void)
+{
+       struct pack_header hdr;
+       const char *hdr_err;
+       char hdr_arg[38];
+
+       hdr_err = parse_pack_header(&hdr);
+       if (hdr_err)
+               return hdr_err;
+       snprintf(hdr_arg, sizeof(hdr_arg),
+                       "--pack_header=%"PRIu32",%"PRIu32,
+                       ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
+
+       if (ntohl(hdr.hdr_entries) < unpack_limit) {
+               int code, i = 0;
+               const char *unpacker[4];
+               unpacker[i++] = "unpack-objects";
+               if (receive_fsck_objects)
+                       unpacker[i++] = "--strict";
+               unpacker[i++] = hdr_arg;
+               unpacker[i++] = NULL;
+               code = run_command_v_opt(unpacker, RUN_GIT_CMD);
+               switch (code) {
+               case 0:
+                       return NULL;
+               case -ERR_RUN_COMMAND_FORK:
+                       return "unpack fork failed";
+               case -ERR_RUN_COMMAND_EXEC:
+                       return "unpack execute failed";
+               case -ERR_RUN_COMMAND_WAITPID:
+                       return "waitpid failed";
+               case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+                       return "waitpid is confused";
+               case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+                       return "unpacker died of signal";
+               case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+                       return "unpacker died strangely";
+               default:
+                       return "unpacker exited with error code";
+               }
+       } else {
+               const char *keeper[7];
+               int s, status, i = 0;
+               char keep_arg[256];
+               struct child_process ip;
+
+               s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid());
+               if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
+                       strcpy(keep_arg + s, "localhost");
+
+               keeper[i++] = "index-pack";
+               keeper[i++] = "--stdin";
+               if (receive_fsck_objects)
+                       keeper[i++] = "--strict";
+               keeper[i++] = "--fix-thin";
+               keeper[i++] = hdr_arg;
+               keeper[i++] = keep_arg;
+               keeper[i++] = NULL;
+               memset(&ip, 0, sizeof(ip));
+               ip.argv = keeper;
+               ip.out = -1;
+               ip.git_cmd = 1;
+               if (start_command(&ip))
+                       return "index-pack fork failed";
+               pack_lockfile = index_pack_lockfile(ip.out);
+               close(ip.out);
+               status = finish_command(&ip);
+               if (!status) {
+                       reprepare_packed_git();
+                       return NULL;
+               }
+               return "index-pack abnormal exit";
+       }
+}
+
+static void report(const char *unpack_status)
+{
+       struct command *cmd;
+       packet_write(1, "unpack %s\n",
+                    unpack_status ? unpack_status : "ok");
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (!cmd->error_string)
+                       packet_write(1, "ok %s\n",
+                                    cmd->ref_name);
+               else
+                       packet_write(1, "ng %s %s\n",
+                                    cmd->ref_name, cmd->error_string);
+       }
+       packet_flush(1);
+}
+
+static int delete_only(struct command *cmd)
+{
+       while (cmd) {
+               if (!is_null_sha1(cmd->new_sha1))
+                       return 0;
+               cmd = cmd->next;
+       }
+       return 1;
+}
+
+static int add_refs_from_alternate(struct alternate_object_database *e, void *unused)
+{
+       char *other = xstrdup(make_absolute_path(e->base));
+       size_t len = strlen(other);
+       struct remote *remote;
+       struct transport *transport;
+       const struct ref *extra;
+
+       while (other[len-1] == '/')
+               other[--len] = '\0';
+       if (len < 8 || memcmp(other + len - 8, "/objects", 8))
+               return 0;
+       /* Is this a git repository with refs? */
+       memcpy(other + len - 8, "/refs", 6);
+       if (!is_directory(other))
+               return 0;
+       other[len - 8] = '\0';
+       remote = remote_get(other);
+       transport = transport_get(remote, other);
+       for (extra = transport_get_remote_refs(transport);
+            extra;
+            extra = extra->next) {
+               add_extra_ref(".have", extra->old_sha1, 0);
+       }
+       transport_disconnect(transport);
+       free(other);
+       return 0;
+}
+
+static void add_alternate_refs(void)
+{
+       foreach_alt_odb(add_refs_from_alternate, NULL);
+}
+
+int cmd_receive_pack(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       char *dir = NULL;
+
+       argv++;
+       for (i = 1; i < argc; i++) {
+               const char *arg = *argv++;
+
+               if (*arg == '-') {
+                       /* Do flag handling here */
+                       usage(receive_pack_usage);
+               }
+               if (dir)
+                       usage(receive_pack_usage);
+               dir = xstrdup(arg);
+       }
+       if (!dir)
+               usage(receive_pack_usage);
+
+       setup_path();
+
+       if (!enter_repo(dir, 0))
+               die("'%s': unable to chdir or not a git archive", dir);
+
+       if (is_repository_shallow())
+               die("attempt to push into a shallow repository");
+
+       git_config(receive_pack_config, NULL);
+
+       if (0 <= transfer_unpack_limit)
+               unpack_limit = transfer_unpack_limit;
+       else if (0 <= receive_unpack_limit)
+               unpack_limit = receive_unpack_limit;
+
+       add_alternate_refs();
+       write_head_info();
+       clear_extra_refs();
+
+       /* EOF */
+       packet_flush(1);
+
+       read_head_info();
+       if (commands) {
+               const char *unpack_status = NULL;
+
+               if (!delete_only(commands))
+                       unpack_status = unpack();
+               execute_commands(unpack_status);
+               if (pack_lockfile)
+                       unlink(pack_lockfile);
+               if (report_status)
+                       report(unpack_status);
+               run_hook(post_receive_hook);
+               run_update_post_hook(commands);
+       }
+       return 0;
+}
index 196fa03b7fac795475a0e12f0fa3b443cb34bd1d..6b3667ef0ebdfc2b8b70b24e474b22989fbf0cb7 100644 (file)
@@ -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);
index 27881e94937dd79b9a0524eb2d9770427ec4f4fb..84865397405d96aa4fdbfca801e8ecd47e3dea36 100644 (file)
@@ -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"
@@ -373,13 +364,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 +399,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)
index 2af9f2934142f55858f4b5c76deb6397ac74b9f8..910db92b62eb6dd91a4002b2643fef4a76ec8f83 100644 (file)
@@ -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
index 9d19c51e8e68d5d4c92465699c58ef0acec70209..417f9724abdce3c49df316505eff1019126a5058 100644 (file)
@@ -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);
 
index f3502d305e4f65e9707fe8b738f64be6e49f7f84..1495cf6a20128ccffb981c3ac4d1da5469da1940 100644 (file)
--- 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/cache.h b/cache.h
index 884fae826cb8c296520aecbc8c23d9848dacb606..99af83a0479ef472078afcd288b1f6ba6283a2f0 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -126,6 +126,7 @@ struct cache_entry {
 
 #define CE_NAMEMASK  (0x0fff)
 #define CE_STAGEMASK (0x3000)
+#define CE_EXTENDED  (0x4000)
 #define CE_VALID     (0x8000)
 #define CE_STAGESHIFT 12
 
@@ -378,6 +379,7 @@ 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
 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);
@@ -392,7 +394,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);
 
@@ -452,6 +453,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,
@@ -531,6 +533,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);
@@ -638,6 +641,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;
@@ -706,7 +711,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);
index aa9d79ea0bf011ea45ef629ef713e6c977781431..de83c6972e9dffbd634e5cc09b5e455ec22c6c48 100644 (file)
@@ -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);
@@ -865,13 +865,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));
index ecdd5733f989f5931a0af05b387321da7068e9f9..de15f4d715ded4d8fd4f882b44fd9a320e7cdb2e 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -122,6 +122,7 @@ 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);
index 772cad510d5d260fdf33b4f7d6ff79f9f3367b05..ccfa2a0a3d3263862beb0d2796be1aba78574986 100644 (file)
@@ -31,11 +31,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
@@ -57,10 +52,10 @@ static int do_lstat(const char *file_name, struct stat *buf)
                buf->st_ino = 0;
                buf->st_gid = 0;
                buf->st_uid = 0;
+               buf->st_nlink = 1;
                buf->st_mode = fMode;
                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 = (_getdrive() - 1);
                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));
@@ -94,7 +89,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 +117,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,22 +127,8 @@ 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;
@@ -162,10 +142,10 @@ int mingw_fstat(int fd, struct mingw_stat *buf)
                buf->st_ino = 0;
                buf->st_gid = 0;
                buf->st_uid = 0;
+               buf->st_nlink = 1;
                buf->st_mode = fMode;
                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 = (_getdrive() - 1);
                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));
index a52e657c51916a0032908a87b4d4df259547f4e8..4f275cb8e6a67515292a9dfc60bd1343065067a9 100644 (file)
@@ -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
index b776149531025c85f5665d971e6e072f0cc64893..17e9861c0634133ec5e07af99191cff99f8a40ad 100644 (file)
@@ -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
index 7c2856efc92ca55e3cf03fcf1c72ffb70318f7c3..27bab00a454c1a29946bbbc0a573ae83f83ee07f 100644 (file)
@@ -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])
index dd96f8e0435ded892001783b69fba6a6fb3be288..67d2cd86a89a92f8f75baa7ae5e820ac3150f3b4 100644 (file)
--- 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,6 +80,12 @@ 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))
index 3bc45f6b47837489f36e34b7af43f2be52838272..93f088189e6b65e03eddea9013273f0559837d26 100755 (executable)
@@ -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
@@ -1493,7 +1479,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
index 46136d49bf99f61a5c85a548bec055742ed140f8..2216cacba79ad7171b7b5abbd1ff27e7b1ef84a1 100755 (executable)
@@ -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."
index 28389541a32454f2d988bb02e4268ffe12755bbc..bb70c75ee1ed54a11ce31d7092ef6e35cae37c04 100644 (file)
 #include "progress.h"
 #include "csum-file.h"
 
-static void sha1flush(struct sha1file *f, unsigned int count)
+static void sha1flush(struct sha1file *f, void *buf, unsigned int count)
 {
-       void *buf = f->buffer;
-
        for (;;) {
                int ret = xwrite(f->fd, buf, count);
                if (ret > 0) {
@@ -39,7 +37,7 @@ int sha1close(struct sha1file *f, unsigned char *result, unsigned int flags)
 
        if (offset) {
                SHA1_Update(&f->ctx, f->buffer, offset);
-               sha1flush(f, offset);
+               sha1flush(f, f->buffer, offset);
                f->offset = 0;
        }
        SHA1_Final(f->buffer, &f->ctx);
@@ -47,7 +45,7 @@ int sha1close(struct sha1file *f, unsigned char *result, unsigned int flags)
                hashcpy(result, f->buffer);
        if (flags & (CSUM_CLOSE | CSUM_FSYNC)) {
                /* write checksum and close fd */
-               sha1flush(f, 20);
+               sha1flush(f, f->buffer, 20);
                if (flags & CSUM_FSYNC)
                        fsync_or_die(f->fd, f->name);
                if (close(f->fd))
@@ -62,21 +60,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);
-                       sha1flush(f, offset);
+                       SHA1_Update(&f->ctx, data, offset);
+                       sha1flush(f, data, offset);
                        offset = 0;
                }
                f->offset = offset;
diff --git a/ctype.c b/ctype.c
index d2bd38e9013cdf5c4ab15dbb1770e94e5d6ed2cb..9208d674dbc081878532f08d2eddfc66c6a2e327 100644 (file)
--- 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 */
index 8dcde73200d1ddbc0dec0dbfdc2f4ff15047abd9..3e5582d28921af22357f2d6068ef55f847bd016a 100644 (file)
--- a/daemon.c
+++ b/daemon.c
 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, ...)
@@ -604,169 +584,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;
-
-       while (deleted < reaped) {
-               pid_t pid = dead_child[deleted % MAX_CHILDREN];
-               const char *dead = pid < 0 ? " (with error)" : "";
+       const struct child *blanket, *next;
 
-               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 +697,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 +744,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 +844,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 +923,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 +956,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 +1001,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 +1080,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 +1116,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 +1133,10 @@ int main(int argc, char **argv)
                return execute(peer);
        }
 
-       if (detach)
+       if (detach) {
                daemonize();
+               loginfo("Ready to rumble");
+       }
        else
                sanitize_stdfds();
 
index e7eaff9a68ccbcc692522c9956f0dae9af45f3f1..ae96c64ca209f4df9008198e8a04b160bed618c7 100644 (file)
@@ -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;
index 7d68b7f1bef1039b4996e662fb17968c4e3e3e79..b60d3455dae14a7a2cf2daeec8eb47fc7dcd9a09 100644 (file)
@@ -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 9385a36f1efb1f52250dfa42dbf994f5fd03856d..a2f4850d4fdf3758b75cc086e47e18736075c999 100644 (file)
--- a/diff.c
+++ b/diff.c
 
 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 */
@@ -149,6 +151,10 @@ 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.")) {
@@ -176,6 +182,12 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       /* 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;
+       }
+
        if (!prefixcmp(var, "diff.")) {
                const char *ep = strrchr(var, '.');
                if (ep != var + 4) {
@@ -305,6 +317,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 == '/');
@@ -313,8 +334,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);
@@ -369,7 +390,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;
 };
@@ -459,11 +479,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;
@@ -477,7 +494,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;
@@ -587,6 +603,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.
         */
@@ -715,8 +737,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 {
@@ -1111,9 +1131,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;
@@ -1146,7 +1170,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;
@@ -1391,6 +1414,8 @@ static struct builtin_funcname_pattern {
        const char *name;
        const char *pattern;
 } builtin_funcname_pattern[] = {
+       { "bibtex", "\\(@[a-zA-Z]\\{1,\\}[ \t]*{\\{0,1\\}[ \t]*[^ \t\"@',\\#}{~%]*\\).*$" },
+       { "html", "^\\s*\\(<[Hh][1-6]\\s.*>.*\\)$" },
        { "java", "!^[  ]*\\(catch\\|do\\|for\\|if\\|instanceof\\|"
                        "new\\|return\\|switch\\|throw\\|while\\)\n"
                        "^[     ]*\\(\\([       ]*"
@@ -1402,9 +1427,10 @@ static struct builtin_funcname_pattern {
                        "\\|"
                        "^\\(.*=[ \t]*\\(class\\|record\\).*\\)$"
                        },
-       { "bibtex", "\\(@[a-zA-Z]\\{1,\\}[ \t]*{\\{0,1\\}[ \t]*[^ \t\"@',\\#}{~%]*\\).*$" },
-       { "tex", "^\\(\\\\\\(\\(sub\\)*section\\|chapter\\|part\\)\\*\\{0,1\\}{.*\\)$" },
+       { "php", "^[\t ]*\\(\\(function\\|class\\).*\\)" },
+       { "python", "^\\s*\\(\\(class\\|def\\)\\s.*\\)$" },
        { "ruby", "^\\s*\\(\\(class\\|module\\|def\\)\\s.*\\)$" },
+       { "tex", "^\\(\\\\\\(\\(sub\\)*section\\|chapter\\|part\\)\\*\\{0,1\\}{.*\\)$" },
 };
 
 static const char *diff_funcname_pattern(struct diff_filespec *one)
@@ -1439,6 +1465,14 @@ static const char *diff_funcname_pattern(struct diff_filespec *one)
        return NULL;
 }
 
+void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b)
+{
+       if (!options->a_prefix)
+               options->a_prefix = a;
+       if (!options->b_prefix)
+               options->b_prefix = b;
+}
+
 static void builtin_diff(const char *name_a,
                         const char *name_b,
                         struct diff_filespec *one,
@@ -1452,9 +1486,19 @@ 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;
+       }
 
-       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);
@@ -1536,15 +1580,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);
        }
@@ -1595,9 +1637,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:
@@ -1618,7 +1659,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;
@@ -1644,9 +1684,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) {
@@ -2316,8 +2355,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)
@@ -2474,6 +2515,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;
@@ -3030,7 +3075,6 @@ static void diff_summary(FILE *file, struct diff_filepair *p)
 }
 
 struct patch_id_t {
-       struct xdiff_emit_state xm;
        SHA_CTX *ctx;
        int patchlen;
 };
@@ -3075,7 +3119,6 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
        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;
@@ -3140,9 +3183,8 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
                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);
@@ -3219,7 +3261,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))
diff --git a/diff.h b/diff.h
index 7f53bebf335148a5f623b5fcbe6142e6c4b53282..a49d865bd9cb0fa5ff27ccad7049074afb0002e9 100644 (file)
--- 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/dir.c b/dir.c
index 109e05b01346ac13296dfbcfa2355a43d97731cd..acf1001c4f9dd51587e3b07e52d4101ff3f1cb66 100644 (file)
--- 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;
@@ -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);
index 5473cd4d626c1260cf1a192ab05cc63c37a8fbcb..ab6689a64d69bdb741f662df2038ae1daae99837 100644 (file)
@@ -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);
 
index 97ac600873ebd0dff6310071343403a41a867b8a..79de7017e88b746971f5a730b8615290bba2890d 100755 (executable)
@@ -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" &&
index cf89cdf4598b3796724a85aa707f740245155cdc..db2836fbdee6c1e069e6f5955031087d778cddf5 100644 (file)
 #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).
  */
@@ -192,6 +197,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 +329,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)
 
index a324cf0596ee0f05831190ce724fe9134bc7f568..81392add0b852f51f63a470727c33e0c306260d8 100755 (executable)
@@ -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 (file)
index 0000000..f96112d
--- /dev/null
@@ -0,0 +1,3 @@
+*           encoding=US-ASCII
+git-gui.sh  encoding=UTF-8
+/po/*.po    encoding=UTF-8
index 86402d49f72c56d793f2f1958a9e3d3f8300f367..4085e8fea59f2b819812f20185df89379d851c0b 100755 (executable)
@@ -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}
+}
index b6e42cbc8fe0a49c301335f78cc2941bd9d59870..eb61374d2db79a1199d455dfdda85e2d063bf1f6 100644 (file)
@@ -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
index ab470d12648c1dd3560b411e70ea210cc4381e3e..0410cc68df186d6e8e0080058be94126243fd4d3 100644 (file)
@@ -151,7 +151,7 @@ method _enter {} {
                                append p [lindex $n 1]
                        }
                        append p $name
-                       blame::new $browser_commit $p
+                       blame::new $browser_commit $p {}
                }
                }
        }
index 40a710355751836e78b65101592b753266f507ca..334514996a9e900d124b5fdc54f83dc9924517b5 100644 (file)
@@ -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)}
index 1970b601e1ce34af120cfc3ee16e1b2c26c6b19d..abe502d97104cb62bed308b0f5025c9d014834fb 100644 (file)
@@ -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]} {
index 7f06b0d47f0fa214c98644757f99f8a036b9689e..32668fc9c6debee6de9882719c305392c1e4791a 100644 (file)
@@ -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
 }
index 3c1fce7475d362d1880d915ff4bdf168fda28593..b045219a1cca6bf46218bf211c0c88b8d699e144 100644 (file)
@@ -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 (file)
index 0000000..6ab5701
--- /dev/null
@@ -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
+       }
+}
index 5e1346e601faf90114e9c62f11144f812835e872..c80c9398786baa63a5023b3e0123a228d148ce34 100644 (file)
@@ -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"]
        }
index fa43947ad0cb43867545316d9a978bc1d67a88a2..793cca1e798640400453e74e2a4b64863f76a540 100644 (file)
@@ -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"
 
index 645e1147dc886f2b1ca6d2020b44db746b082bf0..1dadbb49666c6d796df76babbfd291a2de4357e4 100755 (executable)
@@ -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
 
index b40f876a2ca9fe985cedc622ab28a9f461edc5ab..92be0feb5840c055061f680a45eef6c8f4ebda2d 100755 (executable)
@@ -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 '
 }
 
 #
@@ -198,6 +207,26 @@ cmd_add()
        die "Failed to register submodule '$path'"
 }
 
+#
+# 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
 #
@@ -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
@@ -549,7 +578,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
@@ -573,6 +602,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
@@ -583,7 +664,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)
index 7c7fc39483e2713674a8cf3526651e34bdb9e9f7..80a5728371bdcf3ac90716df5705c9bec3edcf29 100755 (executable)
@@ -421,15 +421,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 ",
@@ -803,8 +803,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 +832,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 +845,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 +881,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 +2626,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 +2638,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 +2667,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 +2817,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 +3414,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 +3671,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 +3698,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;
 }
 
index 384148a59fc492d8fb1d6ea4fc4532aa1e5ffc22..78d236b77f6e2b52d89bf7b579762723ede86d15 100755 (executable)
@@ -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 5582c515ac04609a338de1d2d5e510e7e7c4914d..f4b0cf611b8a281d51970fa088c7271f0aacf21c 100644 (file)
--- 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';
                }
@@ -501,7 +502,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",
index c6492e5be2763eab81358424ff625a34a5ff2fba..6733b6f5551e43835fcf46793142d9d7ffbe27f9 100644 (file)
@@ -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.
 
index 087c4ac733be4b788751d0bae5b7aad22ce0dd99..2eaa2ae7d6f692f6063ebbd211eaab30212c2eae 100644 (file)
@@ -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
     }
@@ -1609,6 +1611,7 @@ proc getcommit {id} {
 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 +1658,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
@@ -2205,6 +2214,8 @@ proc makewindow {} {
        -command {flist_hl 1}
     $flist_menu add command -label [mc "External diff"] \
         -command {external_diff}
+    $flist_menu add command -label [mc "Blame parent commit"] \
+        -command {external_blame 1}
 }
 
 # Windows sends all mouse wheel events to the current focused window, not
@@ -3012,6 +3023,27 @@ proc external_diff {} {
     }
 }
 
+proc external_blame {parent_idx} {
+    global flist_menu_file
+    global nullid nullid2
+    global parentlist selectedline currentid
+
+    if {$parent_idx > 0} {
+       set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
+    } else {
+       set base_commit $currentid
+    }
+
+    if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
+       error_popup [mc "No such commit"]
+       return
+    }
+
+    if {[catch {exec git gui blame $base_commit $flist_menu_file &} err]} {
+       error_popup [mc "git gui blame: command failed: $err"]
+    }
+}
+
 # delete $dir when we see eof on $f (presumably because the child has exited)
 proc delete_at_eof {f dir} {
     while {[gets $f line] >= 0} {}
@@ -9865,6 +9897,9 @@ if {![file isdirectory $gitdir]} {
     exit 1
 }
 
+set selecthead {}
+set selectheadid {}
+
 set revtreeargs {}
 set cmdline_files {}
 set i 0
@@ -9876,6 +9911,9 @@ foreach arg $argv {
            set cmdline_files [lrange $argv [expr {$i + 1}] end]
            break
        }
+       "--select-commit=*" {
+           set selecthead [string range $arg 16 end]
+       }
        "--argscmd=*" {
            set revtreeargscmd [string range $arg 10 end]
        }
@@ -9886,6 +9924,10 @@ foreach arg $argv {
     incr i
 }
 
+if {$selecthead eq "HEAD"} {
+    set selecthead {}
+}
+
 if {$i >= [llength $argv] && $revtreeargs ne {}} {
     # no -- on command line, but some arguments (other than --argscmd)
     if {[catch {
index aa0eeca24786dbd5143354fc3bb5e8cdb3ef831f..07f5b5378805a520e22b0f1b598aed7af4235538 100644 (file)
@@ -481,6 +481,19 @@ span.refs span {
        border-color: #ffccff #ff00ee #ff00ee #ffccff;
 }
 
+span.refs span a {
+       text-decoration: none;
+       color: inherit;
+}
+
+span.refs span a:hover {
+       text-decoration: underline;
+}
+
+span.refs span.indirect {
+       font-style: italic;
+}
+
 span.refs span.ref {
        background-color: #aaaaff;
        border-color: #ccccff #0033cc #0033cc #ccccff;
index 269f1125d9442cb4e0eaecb9069af1d32b4d947b..18e70a366333fffbadd37a7fbe509b4c33f9421f 100755 (executable)
@@ -1090,13 +1090,23 @@ sub format_log_line_html {
 }
 
 # format marker of refs pointing to given object
+
+# the destination action is chosen based on object type and current context:
+# - for annotated tags, we choose the tag view unless it's the current view
+#   already, in which case we go to shortlog view
+# - for other refs, we keep the current view if we're in history, shortlog or
+#   log view, and select shortlog otherwise
 sub format_ref_marker {
        my ($refs, $id) = @_;
        my $markers = '';
 
        if (defined $refs->{$id}) {
                foreach my $ref (@{$refs->{$id}}) {
+                       # this code exploits the fact that non-lightweight tags are the
+                       # only indirect objects, and that they are the only objects for which
+                       # we want to use tag instead of shortlog as action
                        my ($type, $name) = qw();
+                       my $indirect = ($ref =~ s/\^\{\}$//);
                        # e.g. tags/v2.6.11 or heads/next
                        if ($ref =~ m!^(.*?)s?/(.*)$!) {
                                $type = $1;
@@ -1106,8 +1116,29 @@ sub format_ref_marker {
                                $name = $ref;
                        }
 
-                       $markers .= " <span class=\"$type\" title=\"$ref\">" .
-                                   esc_html($name) . "</span>";
+                       my $class = $type;
+                       $class .= " indirect" if $indirect;
+
+                       my $dest_action = "shortlog";
+
+                       if ($indirect) {
+                               $dest_action = "tag" unless $action eq "tag";
+                       } elsif ($action =~ /^(history|(short)?log)$/) {
+                               $dest_action = $action;
+                       }
+
+                       my $dest = "";
+                       $dest .= "refs/" unless $ref =~ m!^refs/!;
+                       $dest .= $ref;
+
+                       my $link = $cgi->a({
+                               -href => href(
+                                       action=>$dest_action,
+                                       hash=>$dest
+                               )}, $name);
+
+                       $markers .= " <span class=\"$class\" title=\"$ref\">" .
+                               $link . "</span>";
                }
        }
 
@@ -1918,7 +1949,7 @@ sub git_get_references {
 
        while (my $line = <$fd>) {
                chomp $line;
-               if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type/?[^^]+)!) {
+               if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) {
                        if (defined $refs{$1}) {
                                push @{$refs{$1}}, $2;
                        } else {
@@ -5467,7 +5498,11 @@ sub git_shortlog {
        }
        my $refs = git_get_references();
 
-       my @commitlist = parse_commits($hash, 101, (100 * $page));
+       my $commit_hash = $hash;
+       if (defined $hash_parent) {
+               $commit_hash = "$hash_parent..$hash";
+       }
+       my @commitlist = parse_commits($commit_hash, 101, (100 * $page));
 
        my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#commitlist >= 100);
        my $next_link = '';
diff --git a/graph.c b/graph.c
index e2633f8376eb7b12706dcd4c698e2b3f6be2b433..5f821706c63778e8b6b53a10894edfc2fb21a8b0 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -4,6 +4,43 @@
 #include "diff.h"
 #include "revision.h"
 
+/* Internal API */
+
+/*
+ * Output the next line for a graph.
+ * This formats the next graph line into the specified strbuf.  It is not
+ * terminated with a newline.
+ *
+ * Returns 1 if the line includes the current commit, and 0 otherwise.
+ * graph_next_line() will return 1 exactly once for each time
+ * graph_update() is called.
+ */
+static int graph_next_line(struct git_graph *graph, struct strbuf *sb);
+
+/*
+ * Output a padding line in the graph.
+ * This is similar to graph_next_line().  However, it is guaranteed to
+ * never print the current commit line.  Instead, if the commit line is
+ * next, it will simply output a line of vertical padding, extending the
+ * branch lines downwards, but leaving them otherwise unchanged.
+ */
+static void graph_padding_line(struct git_graph *graph, struct strbuf *sb);
+
+/*
+ * Print a strbuf to stdout.  If the graph is non-NULL, all lines but the
+ * first will be prefixed with the graph output.
+ *
+ * If the strbuf ends with a newline, the output will end after this
+ * newline.  A new graph line will not be printed after the final newline.
+ * If the strbuf is empty, no output will be printed.
+ *
+ * Since the first line will not include the graph ouput, the caller is
+ * responsible for printing this line's graph (perhaps via
+ * graph_show_commit() or graph_show_oneline()) before calling
+ * graph_show_strbuf().
+ */
+static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);
+
 /*
  * TODO:
  * - Add colors to the graph.
@@ -180,14 +217,6 @@ struct git_graph *graph_init(struct rev_info *opt)
        return graph;
 }
 
-void graph_release(struct git_graph *graph)
-{
-       free(graph->columns);
-       free(graph->new_columns);
-       free(graph->mapping);
-       free(graph);
-}
-
 static void graph_update_state(struct git_graph *graph, enum graph_state s)
 {
        graph->prev_state = graph->state;
@@ -685,7 +714,7 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)
        strbuf_addch(sb, '*');
 }
 
-void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
+static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
 {
        int seen_this = 0;
        int i, j;
@@ -760,7 +789,7 @@ void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
                graph_update_state(graph, GRAPH_COLLAPSING);
 }
 
-void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)
+static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)
 {
        int seen_this = 0;
        int i, j;
@@ -801,7 +830,7 @@ void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)
                graph_update_state(graph, GRAPH_COLLAPSING);
 }
 
-void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb)
+static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb)
 {
        int i;
        int *tmp_mapping;
@@ -906,7 +935,7 @@ void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb)
                graph_update_state(graph, GRAPH_PADDING);
 }
 
-int graph_next_line(struct git_graph *graph, struct strbuf *sb)
+static int graph_next_line(struct git_graph *graph, struct strbuf *sb)
 {
        switch (graph->state) {
        case GRAPH_PADDING:
@@ -933,7 +962,7 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)
        return 0;
 }
 
-void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
+static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
 {
        int i, j;
 
@@ -1055,7 +1084,7 @@ int graph_show_remainder(struct git_graph *graph)
 }
 
 
-void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb)
+static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb)
 {
        char *p;
 
diff --git a/graph.h b/graph.h
index eab4e3daba9812293d4e005c3ebe28f9a97744ce..bc30d687c0dbfc06d459948788784b3d8ab1b5ff 100644 (file)
--- a/graph.h
+++ b/graph.h
@@ -10,11 +10,6 @@ struct git_graph;
  */
 struct git_graph *graph_init(struct rev_info *opt);
 
-/*
- * Destroy a struct git_graph and free associated memory.
- */
-void graph_release(struct git_graph *graph);
-
 /*
  * Update a git_graph with a new commit.
  * This will cause the graph to begin outputting lines for the new commit
@@ -26,26 +21,6 @@ void graph_release(struct git_graph *graph);
  */
 void graph_update(struct git_graph *graph, struct commit *commit);
 
-/*
- * Output the next line for a graph.
- * This formats the next graph line into the specified strbuf.  It is not
- * terminated with a newline.
- *
- * Returns 1 if the line includes the current commit, and 0 otherwise.
- * graph_next_line() will return 1 exactly once for each time
- * graph_update() is called.
- */
-int graph_next_line(struct git_graph *graph, struct strbuf *sb);
-
-/*
- * Output a padding line in the graph.
- * This is similar to graph_next_line().  However, it is guaranteed to
- * never print the current commit line.  Instead, if the commit line is
- * next, it will simply output a line of vertical padding, extending the
- * branch lines downwards, but leaving them otherwise unchanged.
- */
-void graph_padding_line(struct git_graph *graph, struct strbuf *sb);
-
 /*
  * Determine if a graph has finished outputting lines for the current
  * commit.
@@ -89,21 +64,6 @@ void graph_show_padding(struct git_graph *graph);
  */
 int graph_show_remainder(struct git_graph *graph);
 
-/*
- * Print a strbuf to stdout.  If the graph is non-NULL, all lines but the
- * first will be prefixed with the graph output.
- *
- * If the strbuf ends with a newline, the output will end after this
- * newline.  A new graph line will not be printed after the final newline.
- * If the strbuf is empty, no output will be printed.
- *
- * Since the first line will not include the graph ouput, the caller is
- * responsible for printing this line's graph (perhaps via
- * graph_show_commit() or graph_show_oneline()) before calling
- * graph_show_strbuf().
- */
-void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);
-
 /*
  * Print a commit message strbuf and the remainder of the graph to stdout.
  *
index 46c06a9552dac5475afc607c3fe2bf00801eb055..a4d127cf78013ca43339c5493b7f7ef90b2145a8 100644 (file)
@@ -7,16 +7,14 @@
 #include "cache.h"
 #include "blob.h"
 #include "quote.h"
+#include "parse-options.h"
 
-static void hash_object(const char *path, enum object_type type, int write_object)
+static void hash_fd(int fd, const char *type, int write_object, const char *path)
 {
-       int fd;
        struct stat st;
        unsigned char sha1[20];
-       fd = open(path, O_RDONLY);
-       if (fd < 0 ||
-           fstat(fd, &st) < 0 ||
-           index_fd(sha1, fd, &st, write_object, type, path))
+       if (fstat(fd, &st) < 0 ||
+           index_fd(sha1, fd, &st, write_object, type_from_string(type), path))
                die(write_object
                    ? "Unable to add %s to database"
                    : "Unable to hash %s", path);
@@ -24,12 +22,14 @@ static void hash_object(const char *path, enum object_type type, int write_objec
        maybe_flush_or_die(stdout, "hash to stdout");
 }
 
-static void hash_stdin(const char *type, int write_object)
+static void hash_object(const char *path, const char *type, int write_object,
+                       const char *vpath)
 {
-       unsigned char sha1[20];
-       if (index_pipe(sha1, 0, type, write_object))
-               die("Unable to add stdin to database");
-       printf("%s\n", sha1_to_hex(sha1));
+       int fd;
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               die("Cannot open %s", path);
+       hash_fd(fd, type, write_object, vpath);
 }
 
 static void hash_stdin_paths(const char *type, int write_objects)
@@ -45,92 +45,91 @@ static void hash_stdin_paths(const char *type, int write_objects)
                                die("line is badly quoted");
                        strbuf_swap(&buf, &nbuf);
                }
-               hash_object(buf.buf, type_from_string(type), write_objects);
+               hash_object(buf.buf, type, write_objects, buf.buf);
        }
        strbuf_release(&buf);
        strbuf_release(&nbuf);
 }
 
-static const char hash_object_usage[] =
-"git hash-object [ [-t <type>] [-w] [--stdin] <file>... | --stdin-paths < <list-of-paths> ]";
+static const char * const hash_object_usage[] = {
+       "git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...",
+       "git hash-object  --stdin-paths < <list-of-paths>",
+       NULL
+};
 
-int main(int argc, char **argv)
+static const char *type;
+static int write_object;
+static int hashstdin;
+static int stdin_paths;
+static int no_filters;
+static const char *vpath;
+
+static const struct option hash_object_options[] = {
+       OPT_STRING('t', NULL, &type, "type", "object type"),
+       OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"),
+       OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"),
+       OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"),
+       OPT_BOOLEAN( 0 , "no-filters", &no_filters, "store file as is without filters"),
+       OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"),
+       OPT_END()
+};
+
+int main(int argc, const char **argv)
 {
        int i;
-       const char *type = blob_type;
-       int write_object = 0;
        const char *prefix = NULL;
        int prefix_length = -1;
-       int no_more_flags = 0;
-       int hashstdin = 0;
-       int stdin_paths = 0;
+       const char *errstr = NULL;
+
+       type = blob_type;
 
        git_config(git_default_config, NULL);
 
-       for (i = 1 ; i < argc; i++) {
-               if (!no_more_flags && argv[i][0] == '-') {
-                       if (!strcmp(argv[i], "-t")) {
-                               if (argc <= ++i)
-                                       usage(hash_object_usage);
-                               type = argv[i];
-                       }
-                       else if (!strcmp(argv[i], "-w")) {
-                               if (prefix_length < 0) {
-                                       prefix = setup_git_directory();
-                                       prefix_length =
-                                               prefix ? strlen(prefix) : 0;
-                               }
-                               write_object = 1;
-                       }
-                       else if (!strcmp(argv[i], "--")) {
-                               no_more_flags = 1;
-                       }
-                       else if (!strcmp(argv[i], "--help"))
-                               usage(hash_object_usage);
-                       else if (!strcmp(argv[i], "--stdin-paths")) {
-                               if (hashstdin) {
-                                       error("Can't use --stdin-paths with --stdin");
-                                       usage(hash_object_usage);
-                               }
-                               stdin_paths = 1;
-
-                       }
-                       else if (!strcmp(argv[i], "--stdin")) {
-                               if (stdin_paths) {
-                                       error("Can't use %s with --stdin-paths", argv[i]);
-                                       usage(hash_object_usage);
-                               }
-                               if (hashstdin)
-                                       die("Multiple --stdin arguments are not supported");
-                               hashstdin = 1;
-                       }
-                       else
-                               usage(hash_object_usage);
-               }
-               else {
-                       const char *arg = argv[i];
-
-                       if (stdin_paths) {
-                               error("Can't specify files (such as \"%s\") with --stdin-paths", arg);
-                               usage(hash_object_usage);
-                       }
-
-                       if (hashstdin) {
-                               hash_stdin(type, write_object);
-                               hashstdin = 0;
-                       }
-                       if (0 <= prefix_length)
-                               arg = prefix_filename(prefix, prefix_length,
-                                                     arg);
-                       hash_object(arg, type_from_string(type), write_object);
-                       no_more_flags = 1;
-               }
+       argc = parse_options(argc, argv, hash_object_options, hash_object_usage, 0);
+
+       if (write_object) {
+               prefix = setup_git_directory();
+               prefix_length = prefix ? strlen(prefix) : 0;
+               if (vpath && prefix)
+                       vpath = prefix_filename(prefix, prefix_length, vpath);
+       }
+
+       if (stdin_paths) {
+               if (hashstdin)
+                       errstr = "Can't use --stdin-paths with --stdin";
+               else if (argc)
+                       errstr = "Can't specify files with --stdin-paths";
+               else if (vpath)
+                       errstr = "Can't use --stdin-paths with --path";
+               else if (no_filters)
+                       errstr = "Can't use --stdin-paths with --no-filters";
+       }
+       else {
+               if (hashstdin > 1)
+                       errstr = "Multiple --stdin arguments are not supported";
+               if (vpath && no_filters)
+                       errstr = "Can't use --path with --no-filters";
+       }
+
+       if (errstr) {
+               error (errstr);
+               usage_with_options(hash_object_usage, hash_object_options);
+       }
+
+       if (hashstdin)
+               hash_fd(0, type, write_object, vpath);
+
+       for (i = 0 ; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (0 <= prefix_length)
+                       arg = prefix_filename(prefix, prefix_length, arg);
+               hash_object(arg, type, write_object,
+                           no_filters ? NULL : vpath ? vpath : arg);
        }
 
        if (stdin_paths)
                hash_stdin_paths(type, write_object);
 
-       if (hashstdin)
-               hash_stdin(type, write_object);
        return 0;
 }
diff --git a/help.c b/help.c
index dc0786d8002ef633cae4485eb5b25cdb6cf17df4..fd87bb5aeec82beec600be46248b19b13bb33804 100644 (file)
--- a/help.c
+++ b/help.c
@@ -1,276 +1,8 @@
-/*
- * builtin-help.c
- *
- * Builtin help-related commands (help, usage, version)
- */
 #include "cache.h"
 #include "builtin.h"
 #include "exec_cmd.h"
-#include "common-cmds.h"
-#include "parse-options.h"
-#include "run-command.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);
-}
+#include "levenshtein.h"
+#include "help.h"
 
 /* most GUI terminals set COLUMNS (although some don't export it) */
 static int term_columns(void)
@@ -294,24 +26,9 @@ static int term_columns(void)
        return 80;
 }
 
-static inline void mput_char(char c, unsigned int num)
+void add_cmdname(struct cmdnames *cmds, const char *name, int len)
 {
-       while(num--)
-               putchar(c);
-}
-
-static struct cmdnames {
-       int alloc;
-       int cnt;
-       struct cmdname {
-               size_t len;
-               char name[1];
-       } **names;
-} main_cmds, other_cmds;
-
-static void add_cmdname(struct cmdnames *cmds, const char *name, int len)
-{
-       struct cmdname *ent = xmalloc(sizeof(*ent) + len);
+       struct cmdname *ent = xmalloc(sizeof(*ent) + len + 1);
 
        ent->len = len;
        memcpy(ent->name, name, len);
@@ -321,6 +38,16 @@ static void add_cmdname(struct cmdnames *cmds, const char *name, int len)
        cmds->names[cmds->cnt++] = ent;
 }
 
+static void clean_cmdnames(struct cmdnames *cmds)
+{
+       int i;
+       for (i = 0; i < cmds->cnt; ++i)
+               free(cmds->names[i]);
+       free(cmds->names);
+       cmds->cnt = 0;
+       cmds->alloc = 0;
+}
+
 static int cmdname_compare(const void *a_, const void *b_)
 {
        struct cmdname *a = *(struct cmdname **)a_;
@@ -342,7 +69,7 @@ static void uniq(struct cmdnames *cmds)
        cmds->cnt = j;
 }
 
-static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
+void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
 {
        int ci, cj, ei;
        int cmp;
@@ -417,19 +144,21 @@ static int is_executable(const char *name)
        return st.st_mode & S_IXUSR;
 }
 
-static unsigned int list_commands_in_dir(struct cmdnames *cmds,
-                                        const char *path)
+static void list_commands_in_dir(struct cmdnames *cmds,
+                                        const char *path,
+                                        const char *prefix)
 {
-       unsigned int longest = 0;
-       const char *prefix = "git-";
-       int prefix_len = strlen(prefix);
+       int prefix_len;
        DIR *dir = opendir(path);
        struct dirent *de;
        struct strbuf buf = STRBUF_INIT;
        int len;
 
        if (!dir)
-               return 0;
+               return;
+       if (!prefix)
+               prefix = "git-";
+       prefix_len = strlen(prefix);
 
        strbuf_addf(&buf, "%s/", path);
        len = buf.len;
@@ -449,100 +178,81 @@ static unsigned int list_commands_in_dir(struct cmdnames *cmds,
                if (has_extension(de->d_name, ".exe"))
                        entlen -= 4;
 
-               if (longest < entlen)
-                       longest = entlen;
-
                add_cmdname(cmds, de->d_name + prefix_len, entlen);
        }
        closedir(dir);
        strbuf_release(&buf);
-
-       return longest;
 }
 
-static unsigned int load_command_list(void)
+void load_command_list(const char *prefix,
+               struct cmdnames *main_cmds,
+               struct cmdnames *other_cmds)
 {
-       unsigned int longest = 0;
-       unsigned int len;
        const char *env_path = getenv("PATH");
-       char *paths, *path, *colon;
        const char *exec_path = git_exec_path();
 
-       if (exec_path)
-               longest = list_commands_in_dir(&main_cmds, exec_path);
-
-       if (!env_path) {
-               fprintf(stderr, "PATH not set\n");
-               exit(1);
+       if (exec_path) {
+               list_commands_in_dir(main_cmds, exec_path, prefix);
+               qsort(main_cmds->names, main_cmds->cnt,
+                     sizeof(*main_cmds->names), cmdname_compare);
+               uniq(main_cmds);
        }
 
-       path = paths = xstrdup(env_path);
-       while (1) {
-               if ((colon = strchr(path, PATH_SEP)))
-                       *colon = 0;
+       if (env_path) {
+               char *paths, *path, *colon;
+               path = paths = xstrdup(env_path);
+               while (1) {
+                       if ((colon = strchr(path, PATH_SEP)))
+                               *colon = 0;
+                       if (!exec_path || strcmp(path, exec_path))
+                               list_commands_in_dir(other_cmds, path, prefix);
 
-               len = list_commands_in_dir(&other_cmds, path);
-               if (len > longest)
-                       longest = len;
+                       if (!colon)
+                               break;
+                       path = colon + 1;
+               }
+               free(paths);
 
-               if (!colon)
-                       break;
-               path = colon + 1;
+               qsort(other_cmds->names, other_cmds->cnt,
+                     sizeof(*other_cmds->names), cmdname_compare);
+               uniq(other_cmds);
        }
-       free(paths);
-
-       qsort(main_cmds.names, main_cmds.cnt,
-             sizeof(*main_cmds.names), cmdname_compare);
-       uniq(&main_cmds);
-
-       qsort(other_cmds.names, other_cmds.cnt,
-             sizeof(*other_cmds.names), cmdname_compare);
-       uniq(&other_cmds);
-       exclude_cmds(&other_cmds, &main_cmds);
-
-       return longest;
+       exclude_cmds(other_cmds, main_cmds);
 }
 
-static void list_commands(void)
+void list_commands(const char *title, struct cmdnames *main_cmds,
+                  struct cmdnames *other_cmds)
 {
-       unsigned int longest = load_command_list();
-       const char *exec_path = git_exec_path();
+       int i, longest = 0;
 
-       if (main_cmds.cnt) {
-               printf("available git commands in '%s'\n", exec_path);
-               printf("----------------------------");
-               mput_char('-', strlen(exec_path));
+       for (i = 0; i < main_cmds->cnt; i++)
+               if (longest < main_cmds->names[i]->len)
+                       longest = main_cmds->names[i]->len;
+       for (i = 0; i < other_cmds->cnt; i++)
+               if (longest < other_cmds->names[i]->len)
+                       longest = other_cmds->names[i]->len;
+
+       if (main_cmds->cnt) {
+               const char *exec_path = git_exec_path();
+               printf("available %s in '%s'\n", title, exec_path);
+               printf("----------------");
+               mput_char('-', strlen(title) + strlen(exec_path));
                putchar('\n');
-               pretty_print_string_list(&main_cmds, longest);
+               pretty_print_string_list(main_cmds, longest);
                putchar('\n');
        }
 
-       if (other_cmds.cnt) {
-               printf("git commands available from elsewhere on your $PATH\n");
-               printf("---------------------------------------------------\n");
-               pretty_print_string_list(&other_cmds, longest);
+       if (other_cmds->cnt) {
+               printf("%s available from elsewhere on your $PATH\n", title);
+               printf("---------------------------------------");
+               mput_char('-', strlen(title));
+               putchar('\n');
+               pretty_print_string_list(other_cmds, longest);
                putchar('\n');
        }
 }
 
-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_in_cmdlist(struct cmdnames *c, const char *s)
+int is_in_cmdlist(struct cmdnames *c, const char *s)
 {
        int i;
        for (i = 0; i < c->cnt; i++)
@@ -551,133 +261,101 @@ static int is_in_cmdlist(struct cmdnames *c, const char *s)
        return 0;
 }
 
-static int is_git_command(const char *s)
-{
-       load_command_list();
-       return is_in_cmdlist(&main_cmds, s) ||
-               is_in_cmdlist(&other_cmds, s) ||
-               !strcmp(s, "help");
-}
+static int autocorrect;
+static struct cmdnames aliases;
 
-static const char *prepend(const char *prefix, const char *cmd)
+static int git_unknown_cmd_config(const char *var, const char *value, void *cb)
 {
-       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;
-}
+       if (!strcmp(var, "help.autocorrect"))
+               autocorrect = git_config_int(var,value);
+       /* Also use aliases for command lookup */
+       if (!prefixcmp(var, "alias."))
+               add_cmdname(&aliases, var + 6, strlen(var + 6));
 
-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);
+       return git_default_config(var, value, cb);
 }
 
-static void setup_man_path(void)
+static int levenshtein_compare(const void *p1, const void *p2)
 {
-       struct strbuf new_path;
-       const char *old_path = getenv("MANPATH");
-
-       strbuf_init(&new_path, 0);
-
-       /* 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);
+       const struct cmdname *const *c1 = p1, *const *c2 = p2;
+       const char *s1 = (*c1)->name, *s2 = (*c2)->name;
+       int l1 = (*c1)->len;
+       int l2 = (*c2)->len;
+       return l1 != l2 ? l1 - l2 : strcmp(s1, s2);
 }
 
-static void exec_viewer(const char *name, const char *page)
+static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old)
 {
-       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);
+       int i;
+       ALLOC_GROW(cmds->names, cmds->cnt + old->cnt, cmds->alloc);
+
+       for (i = 0; i < old->cnt; i++)
+               cmds->names[cmds->cnt++] = old->names[i];
+       free(old->names);
+       old->cnt = 0;
+       old->names = NULL;
 }
 
-static void show_man_page(const char *git_cmd)
+const char *help_unknown_cmd(const char *cmd)
 {
-       struct man_viewer_list *viewer;
-       const char *page = cmd_to_page(git_cmd);
+       int i, n, best_similarity = 0;
+       struct cmdnames main_cmds, other_cmds;
 
-       setup_man_path();
-       for (viewer = man_viewer_list; viewer; viewer = viewer->next)
-       {
-               exec_viewer(viewer->name, page); /* will return when unable */
-       }
-       exec_viewer("man", page);
-       die("no man viewer handled the request");
-}
+       memset(&main_cmds, 0, sizeof(main_cmds));
+       memset(&other_cmds, 0, sizeof(main_cmds));
+       memset(&aliases, 0, sizeof(aliases));
 
-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);
-}
+       git_config(git_unknown_cmd_config, 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);
+       load_command_list("git-", &main_cmds, &other_cmds);
 
-       /* 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);
+       add_cmd_list(&main_cmds, &aliases);
+       add_cmd_list(&main_cmds, &other_cmds);
+       qsort(main_cmds.names, main_cmds.cnt,
+             sizeof(main_cmds.names), cmdname_compare);
+       uniq(&main_cmds);
 
-       strbuf_init(page_path, 0);
-       strbuf_addf(page_path, "%s/%s.html", html_path, page);
-}
+       /* This reuses cmdname->len for similarity index */
+       for (i = 0; i < main_cmds.cnt; ++i)
+               main_cmds.names[i]->len =
+                       levenshtein(cmd, main_cmds.names[i]->name, 0, 2, 1, 4);
 
-/*
- * 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
+       qsort(main_cmds.names, main_cmds.cnt,
+             sizeof(*main_cmds.names), levenshtein_compare);
+
+       if (!main_cmds.cnt)
+               die ("Uh oh. Your system reports no Git commands at all.");
+
+       best_similarity = main_cmds.names[0]->len;
+       n = 1;
+       while (n < main_cmds.cnt && best_similarity == main_cmds.names[n]->len)
+               ++n;
+       if (autocorrect && n == 1) {
+               const char *assumed = main_cmds.names[0]->name;
+               main_cmds.names[0] = NULL;
+               clean_cmdnames(&main_cmds);
+               fprintf(stderr, "WARNING: You called a Git program named '%s', "
+                       "which does not exist.\n"
+                       "Continuing under the assumption that you meant '%s'\n",
+                       cmd, assumed);
+               if (autocorrect > 0) {
+                       fprintf(stderr, "in %0.1f seconds automatically...\n",
+                               (float)autocorrect/10.0);
+                       poll(NULL, 0, autocorrect * 100);
+               }
+               return assumed;
+       }
 
-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 */
+       fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
 
-       get_html_page_path(&page_path, page);
+       if (best_similarity < 6) {
+               fprintf(stderr, "\nDid you mean %s?\n",
+                       n < 2 ? "this": "one of these");
 
-       open_html(page_path.buf);
-}
+               for (i = 0; i < n; i++)
+                       fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
+       }
 
-void help_unknown_cmd(const char *cmd)
-{
-       fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
        exit(1);
 }
 
@@ -686,49 +364,3 @@ int cmd_version(int argc, const char **argv, const char *prefix)
        printf("git version %s\n", git_version_string);
        return 0;
 }
-
-int cmd_help(int argc, const char **argv, const char *prefix)
-{
-       int nongit;
-       const char *alias;
-
-       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();
-               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/help.h b/help.h
new file mode 100644 (file)
index 0000000..56bc154
--- /dev/null
+++ b/help.h
@@ -0,0 +1,29 @@
+#ifndef HELP_H
+#define HELP_H
+
+struct cmdnames {
+       int alloc;
+       int cnt;
+       struct cmdname {
+               size_t len; /* also used for similarity index in help.c */
+               char name[FLEX_ARRAY];
+       } **names;
+};
+
+static inline void mput_char(char c, unsigned int num)
+{
+       while(num--)
+               putchar(c);
+}
+
+void load_command_list(const char *prefix,
+               struct cmdnames *main_cmds,
+               struct cmdnames *other_cmds);
+void add_cmdname(struct cmdnames *cmds, const char *name, int len);
+/* Here we require that excludes is a sorted list. */
+void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
+int is_in_cmdlist(struct cmdnames *c, const char *s);
+void list_commands(const char *title, struct cmdnames *main_cmds,
+                  struct cmdnames *other_cmds);
+
+#endif /* HELP_H */
index 68052888570af7d09535db8831b8cf3ef2881589..c9dd9a1f6475cc8772b1e0f528a9f54751d6445a 100644 (file)
@@ -2237,7 +2237,7 @@ int main(int argc, char **argv)
        no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
 
        if (remote->url && remote->url[strlen(remote->url)-1] != '/') {
-               rewritten_url = malloc(strlen(remote->url)+2);
+               rewritten_url = xmalloc(strlen(remote->url)+2);
                strcpy(rewritten_url, remote->url);
                strcat(rewritten_url, "/");
                remote->url = rewritten_url;
diff --git a/http.c b/http.c
index a97fdf51173cf73d883c024d85109cbd4e607db9..ed59b79709b11dc6f6d85e86d75a1a8883799f21 100644 (file)
--- a/http.c
+++ b/http.c
@@ -411,7 +411,7 @@ static struct fill_chain *fill_cfg = NULL;
 
 void add_fill_function(void *data, int (*fill)(void *))
 {
-       struct fill_chain *new = malloc(sizeof(*new));
+       struct fill_chain *new = xmalloc(sizeof(*new));
        struct fill_chain **linkp = &fill_cfg;
        new->data = data;
        new->fill = fill;
index 1ec131092109aa3fbed3cd20f10b56a864584a94..af7e08c0943ada9a650f80fd5b5fc1df23c04b88 100644 (file)
  */
 
 #include "cache.h"
+#ifdef NO_OPENSSL
+typedef void *SSL;
+#endif
 
-typedef struct store_conf {
+struct store_conf {
        char *name;
        const char *path; /* should this be here? its interpretation is driver-specific */
        char *map_inbox;
        char *trash;
        unsigned max_size; /* off_t is overkill */
        unsigned trash_remote_new:1, trash_only_new:1;
-} store_conf_t;
+};
 
-typedef struct string_list {
+struct string_list {
        struct string_list *next;
        char string[1];
-} string_list_t;
+};
 
-typedef struct channel_conf {
+struct channel_conf {
        struct channel_conf *next;
        char *name;
-       store_conf_t *master, *slave;
+       struct store_conf *master, *slave;
        char *master_name, *slave_name;
        char *sync_state;
-       string_list_t *patterns;
+       struct string_list *patterns;
        int mops, sops;
        unsigned max_messages; /* for slave only */
-} channel_conf_t;
+};
 
-typedef struct group_conf {
+struct group_conf {
        struct group_conf *next;
        char *name;
-       string_list_t *channels;
-} group_conf_t;
+       struct string_list *channels;
+};
 
 /* For message->status */
 #define M_RECENT       (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */
 #define M_DEAD         (1<<1) /* expunged */
 #define M_FLAGS        (1<<2) /* flags fetched */
 
-typedef struct message {
+struct message {
        struct message *next;
-       /* string_list_t *keywords; */
+       /* struct string_list *keywords; */
        size_t size; /* zero implies "not fetched" */
        int uid;
        unsigned char flags, status;
-} message_t;
+};
 
-typedef struct store {
-       store_conf_t *conf; /* foreign */
+struct store {
+       struct store_conf *conf; /* foreign */
 
        /* currently open mailbox */
        const char *name; /* foreign! maybe preset? */
        char *path; /* own */
-       message_t *msgs; /* own */
+       struct message *msgs; /* own */
        int uidvalidity;
        unsigned char opts; /* maybe preset? */
        /* note that the following do _not_ reflect stats from msgs, but mailbox totals */
        int count; /* # of messages */
        int recent; /* # of recent messages - don't trust this beyond the initial read */
-} store_t;
+};
 
-typedef struct {
+struct msg_data {
        char *data;
        int len;
        unsigned char flags;
        unsigned int crlf:1;
-} msg_data_t;
+};
 
 #define DRV_OK          0
 #define DRV_MSG_BAD     -1
@@ -96,14 +99,14 @@ typedef struct {
 
 static int Verbose, Quiet;
 
-static void imap_info( const char *, ... );
-static void imap_warn( const char *, ... );
+static void imap_info(const char *, ...);
+static void imap_warn(const char *, ...);
 
-static char *next_arg( char ** );
+static char *next_arg(char **);
 
-static void free_generic_messages( message_t * );
+static void free_generic_messages(struct message *);
 
-static int nfsnprintf( char *buf, int blen, const char *fmt, ... );
+static int nfsnprintf(char *buf, int blen, const char *fmt, ...);
 
 static int nfvasprintf(char **strp, const char *fmt, va_list ap)
 {
@@ -119,67 +122,70 @@ static int nfvasprintf(char **strp, const char *fmt, va_list ap)
        return len;
 }
 
-static void arc4_init( void );
-static unsigned char arc4_getbyte( void );
+static void arc4_init(void);
+static unsigned char arc4_getbyte(void);
 
-typedef struct imap_server_conf {
+struct imap_server_conf {
        char *name;
        char *tunnel;
        char *host;
        int port;
        char *user;
        char *pass;
-} imap_server_conf_t;
+       int use_ssl;
+       int ssl_verify;
+};
 
-typedef struct imap_store_conf {
-       store_conf_t gen;
-       imap_server_conf_t *server;
+struct imap_store_conf {
+       struct store_conf gen;
+       struct imap_server_conf *server;
        unsigned use_namespace:1;
-} imap_store_conf_t;
+};
 
-#define NIL    (void*)0x1
-#define LIST   (void*)0x2
+#define NIL    (void *)0x1
+#define LIST   (void *)0x2
 
-typedef struct _list {
-       struct _list *next, *child;
+struct imap_list {
+       struct imap_list *next, *child;
        char *val;
        int len;
-} list_t;
+};
 
-typedef struct {
+struct imap_socket {
        int fd;
-} Socket_t;
+       SSL *ssl;
+};
 
-typedef struct {
-       Socket_t sock;
+struct imap_buffer {
+       struct imap_socket sock;
        int bytes;
        int offset;
        char buf[1024];
-} buffer_t;
+};
 
 struct imap_cmd;
 
-typedef struct imap {
+struct imap {
        int uidnext; /* from SELECT responses */
-       list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
+       struct imap_list *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
        unsigned caps, rcaps; /* CAPABILITY results */
        /* command queue */
        int nexttag, num_in_progress, literal_pending;
        struct imap_cmd *in_progress, **in_progress_append;
-       buffer_t buf; /* this is BIG, so put it last */
-} imap_t;
+       struct imap_buffer buf; /* this is BIG, so put it last */
+};
 
-typedef struct imap_store {
-       store_t gen;
+struct imap_store {
+       struct store gen;
        int uidvalidity;
-       imap_t *imap;
+       struct imap *imap;
        const char *prefix;
        unsigned /*currentnc:1,*/ trashnc:1;
-} imap_store_t;
+};
 
 struct imap_cmd_cb {
-       int (*cont)( imap_store_t *ctx, struct imap_cmd *cmd, const char *prompt );
-       void (*done)( imap_store_t *ctx, struct imap_cmd *cmd, int response);
+       int (*cont)(struct imap_store *ctx, struct imap_cmd *cmd, const char *prompt);
+       void (*done)(struct imap_store *ctx, struct imap_cmd *cmd, int response);
        void *ctx;
        char *data;
        int dlen;
@@ -201,6 +207,7 @@ enum CAPABILITY {
        UIDPLUS,
        LITERALPLUS,
        NAMESPACE,
+       STARTTLS,
 };
 
 static const char *cap_list[] = {
@@ -208,13 +215,14 @@ static const char *cap_list[] = {
        "UIDPLUS",
        "LITERAL+",
        "NAMESPACE",
+       "STARTTLS",
 };
 
 #define RESP_OK    0
 #define RESP_NO    1
 #define RESP_BAD   2
 
-static int get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd );
+static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd);
 
 
 static const char *Flags[] = {
@@ -225,42 +233,137 @@ static const char *Flags[] = {
        "Deleted",
 };
 
-static void
-socket_perror( const char *func, Socket_t *sock, int ret )
+#ifndef NO_OPENSSL
+static void ssl_socket_perror(const char *func)
+{
+       fprintf(stderr, "%s: %s\n", func, ERR_error_string(ERR_get_error(), 0));
+}
+#endif
+
+static void socket_perror(const char *func, struct imap_socket *sock, int ret)
+{
+#ifndef NO_OPENSSL
+       if (sock->ssl) {
+               int sslerr = SSL_get_error(sock->ssl, ret);
+               switch (sslerr) {
+               case SSL_ERROR_NONE:
+                       break;
+               case SSL_ERROR_SYSCALL:
+                       perror("SSL_connect");
+                       break;
+               default:
+                       ssl_socket_perror("SSL_connect");
+                       break;
+               }
+       } else
+#endif
+       {
+               if (ret < 0)
+                       perror(func);
+               else
+                       fprintf(stderr, "%s: unexpected EOF\n", func);
+       }
+}
+
+static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
 {
-       if (ret < 0)
-               perror( func );
+#ifdef NO_OPENSSL
+       fprintf(stderr, "SSL requested but SSL support not compiled in\n");
+       return -1;
+#else
+       SSL_METHOD *meth;
+       SSL_CTX *ctx;
+       int ret;
+
+       SSL_library_init();
+       SSL_load_error_strings();
+
+       if (use_tls_only)
+               meth = TLSv1_method();
        else
-               fprintf( stderr, "%s: unexpected EOF\n", func );
+               meth = SSLv23_method();
+
+       if (!meth) {
+               ssl_socket_perror("SSLv23_method");
+               return -1;
+       }
+
+       ctx = SSL_CTX_new(meth);
+
+       if (verify)
+               SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
+
+       if (!SSL_CTX_set_default_verify_paths(ctx)) {
+               ssl_socket_perror("SSL_CTX_set_default_verify_paths");
+               return -1;
+       }
+       sock->ssl = SSL_new(ctx);
+       if (!sock->ssl) {
+               ssl_socket_perror("SSL_new");
+               return -1;
+       }
+       if (!SSL_set_fd(sock->ssl, sock->fd)) {
+               ssl_socket_perror("SSL_set_fd");
+               return -1;
+       }
+
+       ret = SSL_connect(sock->ssl);
+       if (ret <= 0) {
+               socket_perror("SSL_connect", sock, ret);
+               return -1;
+       }
+
+       return 0;
+#endif
 }
 
-static int
-socket_read( Socket_t *sock, char *buf, int len )
+static int socket_read(struct imap_socket *sock, char *buf, int len)
 {
-       ssize_t n = xread( sock->fd, buf, len );
+       ssize_t n;
+#ifndef NO_OPENSSL
+       if (sock->ssl)
+               n = SSL_read(sock->ssl, buf, len);
+       else
+#endif
+               n = xread(sock->fd, buf, len);
        if (n <= 0) {
-               socket_perror( "read", sock, n );
-               close( sock->fd );
+               socket_perror("read", sock, n);
+               close(sock->fd);
                sock->fd = -1;
        }
        return n;
 }
 
-static int
-socket_write( Socket_t *sock, const char *buf, int len )
+static int socket_write(struct imap_socket *sock, const char *buf, int len)
 {
-       int n = write_in_full( sock->fd, buf, len );
+       int n;
+#ifndef NO_OPENSSL
+       if (sock->ssl)
+               n = SSL_write(sock->ssl, buf, len);
+       else
+#endif
+               n = write_in_full(sock->fd, buf, len);
        if (n != len) {
-               socket_perror( "write", sock, n );
-               close( sock->fd );
+               socket_perror("write", sock, n);
+               close(sock->fd);
                sock->fd = -1;
        }
        return n;
 }
 
+static void socket_shutdown(struct imap_socket *sock)
+{
+#ifndef NO_OPENSSL
+       if (sock->ssl) {
+               SSL_shutdown(sock->ssl);
+               SSL_free(sock->ssl);
+       }
+#endif
+       close(sock->fd);
+}
+
 /* simple line buffering */
-static int
-buffer_gets( buffer_t * b, char **s )
+static int buffer_gets(struct imap_buffer *b, char **s)
 {
        int n;
        int start = b->offset;
@@ -274,7 +377,7 @@ buffer_gets( buffer_t * b, char **s )
                                /* shift down used bytes */
                                *s = b->buf;
 
-                               assert( start <= b->bytes );
+                               assert(start <= b->bytes);
                                n = b->bytes - start;
 
                                if (n)
@@ -284,8 +387,8 @@ buffer_gets( buffer_t * b, char **s )
                                start = 0;
                        }
 
-                       n = socket_read( &b->sock, b->buf + b->bytes,
-                                        sizeof(b->buf) - b->bytes );
+                       n = socket_read(&b->sock, b->buf + b->bytes,
+                                        sizeof(b->buf) - b->bytes);
 
                        if (n <= 0)
                                return -1;
@@ -294,12 +397,12 @@ buffer_gets( buffer_t * b, char **s )
                }
 
                if (b->buf[b->offset] == '\r') {
-                       assert( b->offset + 1 < b->bytes );
+                       assert(b->offset + 1 < b->bytes);
                        if (b->buf[b->offset + 1] == '\n') {
                                b->buf[b->offset] = 0;  /* terminate the string */
                                b->offset += 2; /* next line */
                                if (Verbose)
-                                       puts( *s );
+                                       puts(*s);
                                return 0;
                        }
                }
@@ -309,39 +412,36 @@ buffer_gets( buffer_t * b, char **s )
        /* not reached */
 }
 
-static void
-imap_info( const char *msg, ... )
+static void imap_info(const char *msg, ...)
 {
        va_list va;
 
        if (!Quiet) {
-               va_start( va, msg );
-               vprintf( msg, va );
-               va_end( va );
-               fflush( stdout );
+               va_start(va, msg);
+               vprintf(msg, va);
+               va_end(va);
+               fflush(stdout);
        }
 }
 
-static void
-imap_warn( const char *msg, ... )
+static void imap_warn(const char *msg, ...)
 {
        va_list va;
 
        if (Quiet < 2) {
-               va_start( va, msg );
-               vfprintf( stderr, msg, va );
-               va_end( va );
+               va_start(va, msg);
+               vfprintf(stderr, msg, va);
+               va_end(va);
        }
 }
 
-static char *
-next_arg( char **s )
+static char *next_arg(char **s)
 {
        char *ret;
 
        if (!s || !*s)
                return NULL;
-       while (isspace( (unsigned char) **s ))
+       while (isspace((unsigned char) **s))
                (*s)++;
        if (!**s) {
                *s = NULL;
@@ -350,10 +450,10 @@ next_arg( char **s )
        if (**s == '"') {
                ++*s;
                ret = *s;
-               *s = strchr( *s, '"' );
+               *s = strchr(*s, '"');
        } else {
                ret = *s;
-               while (**s && !isspace( (unsigned char) **s ))
+               while (**s && !isspace((unsigned char) **s))
                        (*s)++;
        }
        if (*s) {
@@ -365,27 +465,25 @@ next_arg( char **s )
        return ret;
 }
 
-static void
-free_generic_messages( message_t *msgs )
+static void free_generic_messages(struct message *msgs)
 {
-       message_t *tmsg;
+       struct message *tmsg;
 
        for (; msgs; msgs = tmsg) {
                tmsg = msgs->next;
-               free( msgs );
+               free(msgs);
        }
 }
 
-static int
-nfsnprintf( char *buf, int blen, const char *fmt, ... )
+static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
 {
        int ret;
        va_list va;
 
-       va_start( va, fmt );
-       if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen)
-               die( "Fatal: buffer too small. Please report a bug.\n");
-       va_end( va );
+       va_start(va, fmt);
+       if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
+               die("Fatal: buffer too small. Please report a bug.\n");
+       va_end(va);
        return ret;
 }
 
@@ -393,21 +491,20 @@ static struct {
        unsigned char i, j, s[256];
 } rs;
 
-static void
-arc4_init( void )
+static void arc4_init(void)
 {
        int i, fd;
        unsigned char j, si, dat[128];
 
-       if ((fd = open( "/dev/urandom", O_RDONLY )) < 0 && (fd = open( "/dev/random", O_RDONLY )) < 0) {
-               fprintf( stderr, "Fatal: no random number source available.\n" );
-               exit( 3 );
+       if ((fd = open("/dev/urandom", O_RDONLY)) < 0 && (fd = open("/dev/random", O_RDONLY)) < 0) {
+               fprintf(stderr, "Fatal: no random number source available.\n");
+               exit(3);
        }
-       if (read_in_full( fd, dat, 128 ) != 128) {
-               fprintf( stderr, "Fatal: cannot read random number source.\n" );
-               exit( 3 );
+       if (read_in_full(fd, dat, 128) != 128) {
+               fprintf(stderr, "Fatal: cannot read random number source.\n");
+               exit(3);
        }
-       close( fd );
+       close(fd);
 
        for (i = 0; i < 256; i++)
                rs.s[i] = i;
@@ -423,8 +520,7 @@ arc4_init( void )
                arc4_getbyte();
 }
 
-static unsigned char
-arc4_getbyte( void )
+static unsigned char arc4_getbyte(void)
 {
        unsigned char si, sj;
 
@@ -437,54 +533,53 @@ arc4_getbyte( void )
        return rs.s[(si + sj) & 0xff];
 }
 
-static struct imap_cmd *
-v_issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb,
-                 const char *fmt, va_list ap )
+static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx,
+                                        struct imap_cmd_cb *cb,
+                                        const char *fmt, va_list ap)
 {
-       imap_t *imap = ctx->imap;
+       struct imap *imap = ctx->imap;
        struct imap_cmd *cmd;
        int n, bufl;
        char buf[1024];
 
-       cmd = xmalloc( sizeof(struct imap_cmd) );
-       nfvasprintf( &cmd->cmd, fmt, ap );
+       cmd = xmalloc(sizeof(struct imap_cmd));
+       nfvasprintf(&cmd->cmd, fmt, ap);
        cmd->tag = ++imap->nexttag;
 
        if (cb)
                cmd->cb = *cb;
        else
-               memset( &cmd->cb, 0, sizeof(cmd->cb) );
+               memset(&cmd->cb, 0, sizeof(cmd->cb));
 
        while (imap->literal_pending)
-               get_cmd_result( ctx, NULL );
+               get_cmd_result(ctx, NULL);
 
-       bufl = nfsnprintf( buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ?
+       bufl = nfsnprintf(buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ?
                           "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n",
-                          cmd->tag, cmd->cmd, cmd->cb.dlen );
+                          cmd->tag, cmd->cmd, cmd->cb.dlen);
        if (Verbose) {
                if (imap->num_in_progress)
-                       printf( "(%d in progress) ", imap->num_in_progress );
-               if (memcmp( cmd->cmd, "LOGIN", 5 ))
-                       printf( ">>> %s", buf );
+                       printf("(%d in progress) ", imap->num_in_progress);
+               if (memcmp(cmd->cmd, "LOGIN", 5))
+                       printf(">>> %s", buf);
                else
-                       printf( ">>> %d LOGIN <user> <pass>\n", cmd->tag );
+                       printf(">>> %d LOGIN <user> <pass>\n", cmd->tag);
        }
-       if (socket_write( &imap->buf.sock, buf, bufl ) != bufl) {
-               free( cmd->cmd );
-               free( cmd );
+       if (socket_write(&imap->buf.sock, buf, bufl) != bufl) {
+               free(cmd->cmd);
+               free(cmd);
                if (cb)
-                       free( cb->data );
+                       free(cb->data);
                return NULL;
        }
        if (cmd->cb.data) {
                if (CAP(LITERALPLUS)) {
-                       n = socket_write( &imap->buf.sock, cmd->cb.data, cmd->cb.dlen );
-                       free( cmd->cb.data );
+                       n = socket_write(&imap->buf.sock, cmd->cb.data, cmd->cb.dlen);
+                       free(cmd->cb.data);
                        if (n != cmd->cb.dlen ||
-                           (n = socket_write( &imap->buf.sock, "\r\n", 2 )) != 2)
-                       {
-                               free( cmd->cmd );
-                               free( cmd );
+                           (n = socket_write(&imap->buf.sock, "\r\n", 2)) != 2) {
+                               free(cmd->cmd);
+                               free(cmd);
                                return NULL;
                        }
                        cmd->cb.data = NULL;
@@ -499,109 +594,106 @@ v_issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb,
        return cmd;
 }
 
-static struct imap_cmd *
-issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+static struct imap_cmd *issue_imap_cmd(struct imap_store *ctx,
+                                      struct imap_cmd_cb *cb,
+                                      const char *fmt, ...)
 {
        struct imap_cmd *ret;
        va_list ap;
 
-       va_start( ap, fmt );
-       ret = v_issue_imap_cmd( ctx, cb, fmt, ap );
-       va_end( ap );
+       va_start(ap, fmt);
+       ret = v_issue_imap_cmd(ctx, cb, fmt, ap);
+       va_end(ap);
        return ret;
 }
 
-static int
-imap_exec( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+static int imap_exec(struct imap_store *ctx, struct imap_cmd_cb *cb,
+                    const char *fmt, ...)
 {
        va_list ap;
        struct imap_cmd *cmdp;
 
-       va_start( ap, fmt );
-       cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap );
-       va_end( ap );
+       va_start(ap, fmt);
+       cmdp = v_issue_imap_cmd(ctx, cb, fmt, ap);
+       va_end(ap);
        if (!cmdp)
                return RESP_BAD;
 
-       return get_cmd_result( ctx, cmdp );
+       return get_cmd_result(ctx, cmdp);
 }
 
-static int
-imap_exec_m( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+static int imap_exec_m(struct imap_store *ctx, struct imap_cmd_cb *cb,
+                      const char *fmt, ...)
 {
        va_list ap;
        struct imap_cmd *cmdp;
 
-       va_start( ap, fmt );
-       cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap );
-       va_end( ap );
+       va_start(ap, fmt);
+       cmdp = v_issue_imap_cmd(ctx, cb, fmt, ap);
+       va_end(ap);
        if (!cmdp)
                return DRV_STORE_BAD;
 
-       switch (get_cmd_result( ctx, cmdp )) {
+       switch (get_cmd_result(ctx, cmdp)) {
        case RESP_BAD: return DRV_STORE_BAD;
        case RESP_NO: return DRV_MSG_BAD;
        default: return DRV_OK;
        }
 }
 
-static int
-is_atom( list_t *list )
+static int is_atom(struct imap_list *list)
 {
        return list && list->val && list->val != NIL && list->val != LIST;
 }
 
-static int
-is_list( list_t *list )
+static int is_list(struct imap_list *list)
 {
        return list && list->val == LIST;
 }
 
-static void
-free_list( list_t *list )
+static void free_list(struct imap_list *list)
 {
-       list_t *tmp;
+       struct imap_list *tmp;
 
        for (; list; list = tmp) {
                tmp = list->next;
-               if (is_list( list ))
-                       free_list( list->child );
-               else if (is_atom( list ))
-                       free( list->val );
-               free( list );
+               if (is_list(list))
+                       free_list(list->child);
+               else if (is_atom(list))
+                       free(list->val);
+               free(list);
        }
 }
 
-static int
-parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level )
+static int parse_imap_list_l(struct imap *imap, char **sp, struct imap_list **curp, int level)
 {
-       list_t *cur;
+       struct imap_list *cur;
        char *s = *sp, *p;
        int n, bytes;
 
        for (;;) {
-               while (isspace( (unsigned char)*s ))
+               while (isspace((unsigned char)*s))
                        s++;
                if (level && *s == ')') {
                        s++;
                        break;
                }
-               *curp = cur = xmalloc( sizeof(*cur) );
+               *curp = cur = xmalloc(sizeof(*cur));
                curp = &cur->next;
                cur->val = NULL; /* for clean bail */
                if (*s == '(') {
                        /* sublist */
                        s++;
                        cur->val = LIST;
-                       if (parse_imap_list_l( imap, &s, &cur->child, level + 1 ))
+                       if (parse_imap_list_l(imap, &s, &cur->child, level + 1))
                                goto bail;
                } else if (imap && *s == '{') {
                        /* literal */
-                       bytes = cur->len = strtol( s + 1, &s, 10 );
+                       bytes = cur->len = strtol(s + 1, &s, 10);
                        if (*s != '}')
                                goto bail;
 
-                       s = cur->val = xmalloc( cur->len );
+                       s = cur->val = xmalloc(cur->len);
 
                        /* dump whats left over in the input buffer */
                        n = imap->buf.bytes - imap->buf.offset;
@@ -610,7 +702,7 @@ parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level )
                                /* the entire message fit in the buffer */
                                n = bytes;
 
-                       memcpy( s, imap->buf.buf + imap->buf.offset, n );
+                       memcpy(s, imap->buf.buf + imap->buf.offset, n);
                        s += n;
                        bytes -= n;
 
@@ -619,13 +711,13 @@ parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level )
 
                        /* now read the rest of the message */
                        while (bytes > 0) {
-                               if ((n = socket_read (&imap->buf.sock, s, bytes)) <= 0)
+                               if ((n = socket_read(&imap->buf.sock, s, bytes)) <= 0)
                                        goto bail;
                                s += n;
                                bytes -= n;
                        }
 
-                       if (buffer_gets( &imap->buf, &s ))
+                       if (buffer_gets(&imap->buf, &s))
                                goto bail;
                } else if (*s == '"') {
                        /* quoted string */
@@ -640,15 +732,14 @@ parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level )
                } else {
                        /* atom */
                        p = s;
-                       for (; *s && !isspace( (unsigned char)*s ); s++)
+                       for (; *s && !isspace((unsigned char)*s); s++)
                                if (level && *s == ')')
                                        break;
                        cur->len = s - p;
-                       if (cur->len == 3 && !memcmp ("NIL", p, 3)) {
+                       if (cur->len == 3 && !memcmp("NIL", p, 3))
                                cur->val = NIL;
-                       } else {
+                       else
                                cur->val = xmemdupz(p, cur->len);
-                       }
                }
 
                if (!level)
@@ -660,127 +751,122 @@ parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level )
        *curp = NULL;
        return 0;
 
-  bail:
+bail:
        *curp = NULL;
        return -1;
 }
 
-static list_t *
-parse_imap_list( imap_t *imap, char **sp )
+static struct imap_list *parse_imap_list(struct imap *imap, char **sp)
 {
-       list_t *head;
+       struct imap_list *head;
 
-       if (!parse_imap_list_l( imap, sp, &head, 0 ))
+       if (!parse_imap_list_l(imap, sp, &head, 0))
                return head;
-       free_list( head );
+       free_list(head);
        return NULL;
 }
 
-static list_t *
-parse_list( char **sp )
+static struct imap_list *parse_list(char **sp)
 {
-       return parse_imap_list( NULL, sp );
+       return parse_imap_list(NULL, sp);
 }
 
-static void
-parse_capability( imap_t *imap, char *cmd )
+static void parse_capability(struct imap *imap, char *cmd)
 {
        char *arg;
        unsigned i;
 
        imap->caps = 0x80000000;
-       while ((arg = next_arg( &cmd )))
+       while ((arg = next_arg(&cmd)))
                for (i = 0; i < ARRAY_SIZE(cap_list); i++)
-                       if (!strcmp( cap_list[i], arg ))
+                       if (!strcmp(cap_list[i], arg))
                                imap->caps |= 1 << i;
        imap->rcaps = imap->caps;
 }
 
-static int
-parse_response_code( imap_store_t *ctx, struct imap_cmd_cb *cb, char *s )
+static int parse_response_code(struct imap_store *ctx, struct imap_cmd_cb *cb,
+                              char *s)
 {
-       imap_t *imap = ctx->imap;
+       struct imap *imap = ctx->imap;
        char *arg, *p;
 
        if (*s != '[')
                return RESP_OK;         /* no response code */
        s++;
-       if (!(p = strchr( s, ']' ))) {
-               fprintf( stderr, "IMAP error: malformed response code\n" );
+       if (!(p = strchr(s, ']'))) {
+               fprintf(stderr, "IMAP error: malformed response code\n");
                return RESP_BAD;
        }
        *p++ = 0;
-       arg = next_arg( &s );
-       if (!strcmp( "UIDVALIDITY", arg )) {
-               if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg ))) {
-                       fprintf( stderr, "IMAP error: malformed UIDVALIDITY status\n" );
+       arg = next_arg(&s);
+       if (!strcmp("UIDVALIDITY", arg)) {
+               if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg))) {
+                       fprintf(stderr, "IMAP error: malformed UIDVALIDITY status\n");
                        return RESP_BAD;
                }
-       } else if (!strcmp( "UIDNEXT", arg )) {
-               if (!(arg = next_arg( &s )) || !(imap->uidnext = atoi( arg ))) {
-                       fprintf( stderr, "IMAP error: malformed NEXTUID status\n" );
+       } else if (!strcmp("UIDNEXT", arg)) {
+               if (!(arg = next_arg(&s)) || !(imap->uidnext = atoi(arg))) {
+                       fprintf(stderr, "IMAP error: malformed NEXTUID status\n");
                        return RESP_BAD;
                }
-       } else if (!strcmp( "CAPABILITY", arg )) {
-               parse_capability( imap, s );
-       } else if (!strcmp( "ALERT", arg )) {
+       } else if (!strcmp("CAPABILITY", arg)) {
+               parse_capability(imap, s);
+       } else if (!strcmp("ALERT", arg)) {
                /* RFC2060 says that these messages MUST be displayed
                 * to the user
                 */
-               for (; isspace( (unsigned char)*p ); p++);
-               fprintf( stderr, "*** IMAP ALERT *** %s\n", p );
-       } else if (cb && cb->ctx && !strcmp( "APPENDUID", arg )) {
-               if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg )) ||
-                   !(arg = next_arg( &s )) || !(*(int *)cb->ctx = atoi( arg )))
-               {
-                       fprintf( stderr, "IMAP error: malformed APPENDUID status\n" );
+               for (; isspace((unsigned char)*p); p++);
+               fprintf(stderr, "*** IMAP ALERT *** %s\n", p);
+       } else if (cb && cb->ctx && !strcmp("APPENDUID", arg)) {
+               if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg)) ||
+                   !(arg = next_arg(&s)) || !(*(int *)cb->ctx = atoi(arg))) {
+                       fprintf(stderr, "IMAP error: malformed APPENDUID status\n");
                        return RESP_BAD;
                }
        }
        return RESP_OK;
 }
 
-static int
-get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
+static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd)
 {
-       imap_t *imap = ctx->imap;
+       struct imap *imap = ctx->imap;
        struct imap_cmd *cmdp, **pcmdp, *ncmdp;
        char *cmd, *arg, *arg1, *p;
        int n, resp, resp2, tag;
 
        for (;;) {
-               if (buffer_gets( &imap->buf, &cmd ))
+               if (buffer_gets(&imap->buf, &cmd))
                        return RESP_BAD;
 
-               arg = next_arg( &cmd );
+               arg = next_arg(&cmd);
                if (*arg == '*') {
-                       arg = next_arg( &cmd );
+                       arg = next_arg(&cmd);
                        if (!arg) {
-                               fprintf( stderr, "IMAP error: unable to parse untagged response\n" );
+                               fprintf(stderr, "IMAP error: unable to parse untagged response\n");
                                return RESP_BAD;
                        }
 
-                       if (!strcmp( "NAMESPACE", arg )) {
-                               imap->ns_personal = parse_list( &cmd );
-                               imap->ns_other = parse_list( &cmd );
-                               imap->ns_shared = parse_list( &cmd );
-                       } else if (!strcmp( "OK", arg ) || !strcmp( "BAD", arg ) ||
-                                  !strcmp( "NO", arg ) || !strcmp( "BYE", arg )) {
-                               if ((resp = parse_response_code( ctx, NULL, cmd )) != RESP_OK)
+                       if (!strcmp("NAMESPACE", arg)) {
+                               imap->ns_personal = parse_list(&cmd);
+                               imap->ns_other = parse_list(&cmd);
+                               imap->ns_shared = parse_list(&cmd);
+                       } else if (!strcmp("OK", arg) || !strcmp("BAD", arg) ||
+                                  !strcmp("NO", arg) || !strcmp("BYE", arg)) {
+                               if ((resp = parse_response_code(ctx, NULL, cmd)) != RESP_OK)
                                        return resp;
-                       } else if (!strcmp( "CAPABILITY", arg ))
-                               parse_capability( imap, cmd );
-                       else if ((arg1 = next_arg( &cmd ))) {
-                               if (!strcmp( "EXISTS", arg1 ))
-                                       ctx->gen.count = atoi( arg );
-                               else if (!strcmp( "RECENT", arg1 ))
-                                       ctx->gen.recent = atoi( arg );
+                       } else if (!strcmp("CAPABILITY", arg))
+                               parse_capability(imap, cmd);
+                       else if ((arg1 = next_arg(&cmd))) {
+                               if (!strcmp("EXISTS", arg1))
+                                       ctx->gen.count = atoi(arg);
+                               else if (!strcmp("RECENT", arg1))
+                                       ctx->gen.recent = atoi(arg);
                        } else {
-                               fprintf( stderr, "IMAP error: unable to parse untagged response\n" );
+                               fprintf(stderr, "IMAP error: unable to parse untagged response\n");
                                return RESP_BAD;
                        }
                } else if (!imap->in_progress) {
-                       fprintf( stderr, "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "" );
+                       fprintf(stderr, "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "");
                        return RESP_BAD;
                } else if (*arg == '+') {
                        /* This can happen only with the last command underway, as
@@ -788,57 +874,57 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
                        cmdp = (struct imap_cmd *)((char *)imap->in_progress_append -
                               offsetof(struct imap_cmd, next));
                        if (cmdp->cb.data) {
-                               n = socket_write( &imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen );
-                               free( cmdp->cb.data );
+                               n = socket_write(&imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen);
+                               free(cmdp->cb.data);
                                cmdp->cb.data = NULL;
                                if (n != (int)cmdp->cb.dlen)
                                        return RESP_BAD;
                        } else if (cmdp->cb.cont) {
-                               if (cmdp->cb.cont( ctx, cmdp, cmd ))
+                               if (cmdp->cb.cont(ctx, cmdp, cmd))
                                        return RESP_BAD;
                        } else {
-                               fprintf( stderr, "IMAP error: unexpected command continuation request\n" );
+                               fprintf(stderr, "IMAP error: unexpected command continuation request\n");
                                return RESP_BAD;
                        }
-                       if (socket_write( &imap->buf.sock, "\r\n", 2 ) != 2)
+                       if (socket_write(&imap->buf.sock, "\r\n", 2) != 2)
                                return RESP_BAD;
                        if (!cmdp->cb.cont)
                                imap->literal_pending = 0;
                        if (!tcmd)
                                return DRV_OK;
                } else {
-                       tag = atoi( arg );
+                       tag = atoi(arg);
                        for (pcmdp = &imap->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next)
                                if (cmdp->tag == tag)
                                        goto gottag;
-                       fprintf( stderr, "IMAP error: unexpected tag %s\n", arg );
+                       fprintf(stderr, "IMAP error: unexpected tag %s\n", arg);
                        return RESP_BAD;
-                 gottag:
+               gottag:
                        if (!(*pcmdp = cmdp->next))
                                imap->in_progress_append = pcmdp;
                        imap->num_in_progress--;
                        if (cmdp->cb.cont || cmdp->cb.data)
                                imap->literal_pending = 0;
-                       arg = next_arg( &cmd );
-                       if (!strcmp( "OK", arg ))
+                       arg = next_arg(&cmd);
+                       if (!strcmp("OK", arg))
                                resp = DRV_OK;
                        else {
-                               if (!strcmp( "NO", arg )) {
-                                       if (cmdp->cb.create && cmd && (cmdp->cb.trycreate || !memcmp( cmd, "[TRYCREATE]", 11 ))) { /* SELECT, APPEND or UID COPY */
-                                               p = strchr( cmdp->cmd, '"' );
-                                               if (!issue_imap_cmd( ctx, NULL, "CREATE \"%.*s\"", strchr( p + 1, '"' ) - p + 1, p )) {
+                               if (!strcmp("NO", arg)) {
+                                       if (cmdp->cb.create && cmd && (cmdp->cb.trycreate || !memcmp(cmd, "[TRYCREATE]", 11))) { /* SELECT, APPEND or UID COPY */
+                                               p = strchr(cmdp->cmd, '"');
+                                               if (!issue_imap_cmd(ctx, NULL, "CREATE \"%.*s\"", strchr(p + 1, '"') - p + 1, p)) {
                                                        resp = RESP_BAD;
                                                        goto normal;
                                                }
                                                /* not waiting here violates the spec, but a server that does not
                                                   grok this nonetheless violates it too. */
                                                cmdp->cb.create = 0;
-                                               if (!(ncmdp = issue_imap_cmd( ctx, &cmdp->cb, "%s", cmdp->cmd ))) {
+                                               if (!(ncmdp = issue_imap_cmd(ctx, &cmdp->cb, "%s", cmdp->cmd))) {
                                                        resp = RESP_BAD;
                                                        goto normal;
                                                }
-                                               free( cmdp->cmd );
-                                               free( cmdp );
+                                               free(cmdp->cmd);
+                                               free(cmdp);
                                                if (!tcmd)
                                                        return 0;       /* ignored */
                                                if (cmdp == tcmd)
@@ -846,21 +932,21 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
                                                continue;
                                        }
                                        resp = RESP_NO;
-                               } else /*if (!strcmp( "BAD", arg ))*/
+                               } else /*if (!strcmp("BAD", arg))*/
                                        resp = RESP_BAD;
-                               fprintf( stderr, "IMAP command '%s' returned response (%s) - %s\n",
-                                        memcmp (cmdp->cmd, "LOGIN", 5) ?
+                               fprintf(stderr, "IMAP command '%s' returned response (%s) - %s\n",
+                                        memcmp(cmdp->cmd, "LOGIN", 5) ?
                                                        cmdp->cmd : "LOGIN <user> <pass>",
                                                        arg, cmd ? cmd : "");
                        }
-                       if ((resp2 = parse_response_code( ctx, &cmdp->cb, cmd )) > resp)
+                       if ((resp2 = parse_response_code(ctx, &cmdp->cb, cmd)) > resp)
                                resp = resp2;
-                 normal:
+               normal:
                        if (cmdp->cb.done)
-                               cmdp->cb.done( ctx, cmdp, resp );
-                       free( cmdp->cb.data );
-                       free( cmdp->cmd );
-                       free( cmdp );
+                               cmdp->cb.done(ctx, cmdp, resp);
+                       free(cmdp->cb.data);
+                       free(cmdp->cmd);
+                       free(cmdp);
                        if (!tcmd || tcmd == cmdp)
                                return resp;
                }
@@ -868,170 +954,184 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
        /* not reached */
 }
 
-static void
-imap_close_server( imap_store_t *ictx )
+static void imap_close_server(struct imap_store *ictx)
 {
-       imap_t *imap = ictx->imap;
+       struct imap *imap = ictx->imap;
 
        if (imap->buf.sock.fd != -1) {
-               imap_exec( ictx, NULL, "LOGOUT" );
-               close( imap->buf.sock.fd );
+               imap_exec(ictx, NULL, "LOGOUT");
+               socket_shutdown(&imap->buf.sock);
        }
-       free_list( imap->ns_personal );
-       free_list( imap->ns_other );
-       free_list( imap->ns_shared );
-       free( imap );
+       free_list(imap->ns_personal);
+       free_list(imap->ns_other);
+       free_list(imap->ns_shared);
+       free(imap);
 }
 
-static void
-imap_close_store( store_t *ctx )
+static void imap_close_store(struct store *ctx)
 {
-       imap_close_server( (imap_store_t *)ctx );
-       free_generic_messages( ctx->msgs );
-       free( ctx );
+       imap_close_server((struct imap_store *)ctx);
+       free_generic_messages(ctx->msgs);
+       free(ctx);
 }
 
-static store_t *
-imap_open_store( imap_server_conf_t *srvc )
+static struct store *imap_open_store(struct imap_server_conf *srvc)
 {
-       imap_store_t *ctx;
-       imap_t *imap;
+       struct imap_store *ctx;
+       struct imap *imap;
        char *arg, *rsp;
        struct hostent *he;
        struct sockaddr_in addr;
        int s, a[2], preauth;
        pid_t pid;
 
-       ctx = xcalloc( sizeof(*ctx), 1 );
+       ctx = xcalloc(sizeof(*ctx), 1);
 
-       ctx->imap = imap = xcalloc( sizeof(*imap), 1 );
+       ctx->imap = imap = xcalloc(sizeof(*imap), 1);
        imap->buf.sock.fd = -1;
        imap->in_progress_append = &imap->in_progress;
 
        /* open connection to IMAP server */
 
        if (srvc->tunnel) {
-               imap_info( "Starting tunnel '%s'... ", srvc->tunnel );
+               imap_info("Starting tunnel '%s'... ", srvc->tunnel);
 
-               if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) {
-                       perror( "socketpair" );
-                       exit( 1 );
+               if (socketpair(PF_UNIX, SOCK_STREAM, 0, a)) {
+                       perror("socketpair");
+                       exit(1);
                }
 
                pid = fork();
                if (pid < 0)
-                       _exit( 127 );
+                       _exit(127);
                if (!pid) {
-                       if (dup2( a[0], 0 ) == -1 || dup2( a[0], 1 ) == -1)
-                               _exit( 127 );
-                       close( a[0] );
-                       close( a[1] );
-                       execl( "/bin/sh", "sh", "-c", srvc->tunnel, NULL );
-                       _exit( 127 );
+                       if (dup2(a[0], 0) == -1 || dup2(a[0], 1) == -1)
+                               _exit(127);
+                       close(a[0]);
+                       close(a[1]);
+                       execl("/bin/sh", "sh", "-c", srvc->tunnel, NULL);
+                       _exit(127);
                }
 
-               close (a[0]);
+               close(a[0]);
 
                imap->buf.sock.fd = a[1];
 
-               imap_info( "ok\n" );
+               imap_info("ok\n");
        } else {
-               memset( &addr, 0, sizeof(addr) );
-               addr.sin_port = htons( srvc->port );
+               memset(&addr, 0, sizeof(addr));
+               addr.sin_port = htons(srvc->port);
                addr.sin_family = AF_INET;
 
-               imap_info( "Resolving %s... ", srvc->host );
-               he = gethostbyname( srvc->host );
+               imap_info("Resolving %s... ", srvc->host);
+               he = gethostbyname(srvc->host);
                if (!he) {
-                       perror( "gethostbyname" );
+                       perror("gethostbyname");
                        goto bail;
                }
-               imap_info( "ok\n" );
+               imap_info("ok\n");
 
                addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
 
-               s = socket( PF_INET, SOCK_STREAM, 0 );
+               s = socket(PF_INET, SOCK_STREAM, 0);
 
-               imap_info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) );
-               if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) {
-                       close( s );
-                       perror( "connect" );
+               imap_info("Connecting to %s:%hu... ", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
+               if (connect(s, (struct sockaddr *)&addr, sizeof(addr))) {
+                       close(s);
+                       perror("connect");
                        goto bail;
                }
-               imap_info( "ok\n" );
 
                imap->buf.sock.fd = s;
 
+               if (srvc->use_ssl &&
+                   ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {
+                       close(s);
+                       goto bail;
+               }
+               imap_info("ok\n");
        }
 
        /* read the greeting string */
-       if (buffer_gets( &imap->buf, &rsp )) {
-               fprintf( stderr, "IMAP error: no greeting response\n" );
+       if (buffer_gets(&imap->buf, &rsp)) {
+               fprintf(stderr, "IMAP error: no greeting response\n");
                goto bail;
        }
-       arg = next_arg( &rsp );
-       if (!arg || *arg != '*' || (arg = next_arg( &rsp )) == NULL) {
-               fprintf( stderr, "IMAP error: invalid greeting response\n" );
+       arg = next_arg(&rsp);
+       if (!arg || *arg != '*' || (arg = next_arg(&rsp)) == NULL) {
+               fprintf(stderr, "IMAP error: invalid greeting response\n");
                goto bail;
        }
        preauth = 0;
-       if (!strcmp( "PREAUTH", arg ))
+       if (!strcmp("PREAUTH", arg))
                preauth = 1;
-       else if (strcmp( "OK", arg ) != 0) {
-               fprintf( stderr, "IMAP error: unknown greeting response\n" );
+       else if (strcmp("OK", arg) != 0) {
+               fprintf(stderr, "IMAP error: unknown greeting response\n");
                goto bail;
        }
-       parse_response_code( ctx, NULL, rsp );
-       if (!imap->caps && imap_exec( ctx, NULL, "CAPABILITY" ) != RESP_OK)
+       parse_response_code(ctx, NULL, rsp);
+       if (!imap->caps && imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK)
                goto bail;
 
        if (!preauth) {
-
-               imap_info ("Logging in...\n");
+#ifndef NO_OPENSSL
+               if (!srvc->use_ssl && CAP(STARTTLS)) {
+                       if (imap_exec(ctx, 0, "STARTTLS") != RESP_OK)
+                               goto bail;
+                       if (ssl_socket_connect(&imap->buf.sock, 1,
+                                              srvc->ssl_verify))
+                               goto bail;
+                       /* capabilities may have changed, so get the new capabilities */
+                       if (imap_exec(ctx, 0, "CAPABILITY") != RESP_OK)
+                               goto bail;
+               }
+#endif
+               imap_info("Logging in...\n");
                if (!srvc->user) {
-                       fprintf( stderr, "Skipping server %s, no user\n", srvc->host );
+                       fprintf(stderr, "Skipping server %s, no user\n", srvc->host);
                        goto bail;
                }
                if (!srvc->pass) {
                        char prompt[80];
-                       sprintf( prompt, "Password (%s@%s): ", srvc->user, srvc->host );
-                       arg = getpass( prompt );
+                       sprintf(prompt, "Password (%s@%s): ", srvc->user, srvc->host);
+                       arg = getpass(prompt);
                        if (!arg) {
-                               perror( "getpass" );
-                               exit( 1 );
+                               perror("getpass");
+                               exit(1);
                        }
                        if (!*arg) {
-                               fprintf( stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host );
+                               fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host);
                                goto bail;
                        }
                        /*
                         * getpass() returns a pointer to a static buffer.  make a copy
                         * for long term storage.
                         */
-                       srvc->pass = xstrdup( arg );
+                       srvc->pass = xstrdup(arg);
                }
                if (CAP(NOLOGIN)) {
-                       fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host );
+                       fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host);
                        goto bail;
                }
-               imap_warn( "*** IMAP Warning *** Password is being sent in the clear\n" );
-               if (imap_exec( ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) {
-                       fprintf( stderr, "IMAP error: LOGIN failed\n" );
+               if (!imap->buf.sock.ssl)
+                       imap_warn("*** IMAP Warning *** Password is being "
+                                 "sent in the clear\n");
+               if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) {
+                       fprintf(stderr, "IMAP error: LOGIN failed\n");
                        goto bail;
                }
        } /* !preauth */
 
        ctx->prefix = "";
        ctx->trashnc = 1;
-       return (store_t *)ctx;
+       return (struct store *)ctx;
 
-  bail:
-       imap_close_store( &ctx->gen );
+bail:
+       imap_close_store(&ctx->gen);
        return NULL;
 }
 
-static int
-imap_make_flags( int flags, char *buf )
+static int imap_make_flags(int flags, char *buf)
 {
        const char *s;
        unsigned i, d;
@@ -1050,11 +1150,10 @@ imap_make_flags( int flags, char *buf )
 
 #define TUIDL 8
 
-static int
-imap_store_msg( store_t *gctx, msg_data_t *data, int *uid )
+static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid)
 {
-       imap_store_t *ctx = (imap_store_t *)gctx;
-       imap_t *imap = ctx->imap;
+       struct imap_store *ctx = (struct imap_store *)gctx;
+       struct imap *imap = ctx->imap;
        struct imap_cmd_cb cb;
        char *fmap, *buf;
        const char *prefix, *box;
@@ -1062,14 +1161,14 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid )
        int start, sbreak = 0, ebreak = 0;
        char flagstr[128], tuid[TUIDL * 2 + 1];
 
-       memset( &cb, 0, sizeof(cb) );
+       memset(&cb, 0, sizeof(cb));
 
        fmap = data->data;
        len = data->len;
        nocr = !data->crlf;
        extra = 0, i = 0;
        if (!CAP(UIDPLUS) && uid) {
-         nloop:
+       nloop:
                start = i;
                while (i < len)
                        if (fmap[i++] == '\n') {
@@ -1078,18 +1177,18 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid )
                                        sbreak = ebreak = i - 2 + nocr;
                                        goto mktid;
                                }
-                               if (!memcmp( fmap + start, "X-TUID: ", 8 )) {
+                               if (!memcmp(fmap + start, "X-TUID: ", 8)) {
                                        extra -= (ebreak = i) - (sbreak = start) + nocr;
                                        goto mktid;
                                }
                                goto nloop;
                        }
                /* invalid message */
-               free( fmap );
+               free(fmap);
                return DRV_MSG_BAD;
-        mktid:
+       mktid:
                for (j = 0; j < TUIDL; j++)
-                       sprintf( tuid + j * 2, "%02x", arc4_getbyte() );
+                       sprintf(tuid + j * 2, "%02x", arc4_getbyte());
                extra += 8 + TUIDL * 2 + 2;
        }
        if (nocr)
@@ -1098,7 +1197,7 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid )
                                extra++;
 
        cb.dlen = len + extra;
-       buf = cb.data = xmalloc( cb.dlen );
+       buf = cb.data = xmalloc(cb.dlen);
        i = 0;
        if (!CAP(UIDPLUS) && uid) {
                if (nocr) {
@@ -1109,12 +1208,12 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid )
                                } else
                                        *buf++ = fmap[i];
                } else {
-                       memcpy( buf, fmap, sbreak );
+                       memcpy(buf, fmap, sbreak);
                        buf += sbreak;
                }
-               memcpy( buf, "X-TUID: ", 8 );
+               memcpy(buf, "X-TUID: ", 8);
                buf += 8;
-               memcpy( buf, tuid, TUIDL * 2 );
+               memcpy(buf, tuid, TUIDL * 2);
                buf += TUIDL * 2;
                *buf++ = '\r';
                *buf++ = '\n';
@@ -1128,13 +1227,13 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid )
                        } else
                                *buf++ = fmap[i];
        } else
-               memcpy( buf, fmap + i, len - i );
+               memcpy(buf, fmap + i, len - i);
 
-       free( fmap );
+       free(fmap);
 
        d = 0;
        if (data->flags) {
-               d = imap_make_flags( data->flags, flagstr );
+               d = imap_make_flags(data->flags, flagstr);
                flagstr[d++] = ' ';
        }
        flagstr[d] = 0;
@@ -1147,11 +1246,11 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid )
                        imap->caps = imap->rcaps & ~(1 << LITERALPLUS);
        } else {
                box = gctx->name;
-               prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix;
+               prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix;
                cb.create = 0;
        }
        cb.ctx = uid;
-       ret = imap_exec_m( ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr );
+       ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr);
        imap->caps = imap->rcaps;
        if (ret != DRV_OK)
                return ret;
@@ -1165,8 +1264,7 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid )
 
 #define CHUNKSIZE 0x1000
 
-static int
-read_message( FILE *f, msg_data_t *msg )
+static int read_message(FILE *f, struct msg_data *msg)
 {
        struct strbuf buf;
 
@@ -1183,8 +1281,7 @@ read_message( FILE *f, msg_data_t *msg )
        return msg->len;
 }
 
-static int
-count_messages( msg_data_t *msg )
+static int count_messages(struct msg_data *msg)
 {
        int count = 0;
        char *p = msg->data;
@@ -1194,7 +1291,7 @@ count_messages( msg_data_t *msg )
                        count++;
                        p += 5;
                }
-               p = strstr( p+5, "\nFrom ");
+               p = strstr(p+5, "\nFrom ");
                if (!p)
                        break;
                p++;
@@ -1202,22 +1299,21 @@ count_messages( msg_data_t *msg )
        return count;
 }
 
-static int
-split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs )
+static int split_msg(struct msg_data *all_msgs, struct msg_data *msg, int *ofs)
 {
        char *p, *data;
 
-       memset( msg, 0, sizeof *msg );
+       memset(msg, 0, sizeof *msg);
        if (*ofs >= all_msgs->len)
                return 0;
 
-       data = &all_msgs->data[ *ofs ];
+       data = &all_msgs->data[*ofs];
        msg->len = all_msgs->len - *ofs;
 
        if (msg->len < 5 || prefixcmp(data, "From "))
                return 0;
 
-       p = strchr( data, '\n' );
+       p = strchr(data, '\n');
        if (p) {
                p = &p[1];
                msg->len -= p-data;
@@ -1225,7 +1321,7 @@ split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs )
                data = p;
        }
 
-       p = strstr( data, "\nFrom " );
+       p = strstr(data, "\nFrom ");
        if (p)
                msg->len = &p[1] - data;
 
@@ -1234,24 +1330,24 @@ split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs )
        return 1;
 }
 
-static imap_server_conf_t server =
-{
+static struct imap_server_conf server = {
        NULL,   /* name */
        NULL,   /* tunnel */
        NULL,   /* host */
        0,      /* port */
        NULL,   /* user */
        NULL,   /* pass */
+       0,      /* use_ssl */
+       1,      /* ssl_verify */
 };
 
 static char *imap_folder;
 
-static int
-git_imap_config(const char *key, const char *val, void *cb)
+static int git_imap_config(const char *key, const char *val, void *cb)
 {
        char imap_key[] = "imap.";
 
-       if (strncmp( key, imap_key, sizeof imap_key - 1 ))
+       if (strncmp(key, imap_key, sizeof imap_key - 1))
                return 0;
 
        if (!val)
@@ -1259,90 +1355,96 @@ git_imap_config(const char *key, const char *val, void *cb)
 
        key += sizeof imap_key - 1;
 
-       if (!strcmp( "folder", key )) {
-               imap_folder = xstrdup( val );
-       } else if (!strcmp( "host", key )) {
-               {
-                       if (!prefixcmp(val, "imap:"))
-                               val += 5;
-                       if (!server.port)
-                               server.port = 143;
+       if (!strcmp("folder", key)) {
+               imap_folder = xstrdup(val);
+       } else if (!strcmp("host", key)) {
+               if (!prefixcmp(val, "imap:"))
+                       val += 5;
+               else if (!prefixcmp(val, "imaps:")) {
+                       val += 6;
+                       server.use_ssl = 1;
                }
                if (!prefixcmp(val, "//"))
                        val += 2;
-               server.host = xstrdup( val );
-       }
-       else if (!strcmp( "user", key ))
-               server.user = xstrdup( val );
-       else if (!strcmp( "pass", key ))
-               server.pass = xstrdup( val );
-       else if (!strcmp( "port", key ))
-               server.port = git_config_int( key, val );
-       else if (!strcmp( "tunnel", key ))
-               server.tunnel = xstrdup( val );
+               server.host = xstrdup(val);
+       } else if (!strcmp("user", key))
+               server.user = xstrdup(val);
+       else if (!strcmp("pass", key))
+               server.pass = xstrdup(val);
+       else if (!strcmp("port", key))
+               server.port = git_config_int(key, val);
+       else if (!strcmp("tunnel", key))
+               server.tunnel = xstrdup(val);
+       else if (!strcmp("sslverify", key))
+               server.ssl_verify = git_config_bool(key, val);
        return 0;
 }
 
-int
-main(int argc, char **argv)
+int main(int argc, char **argv)
 {
-       msg_data_t all_msgs, msg;
-       store_t *ctx = NULL;
+       struct msg_data all_msgs, msg;
+       struct store *ctx = NULL;
        int uid = 0;
        int ofs = 0;
        int r;
        int total, n = 0;
+       int nongit_ok;
 
        /* init the random number generator */
        arc4_init();
 
+       setup_git_directory_gently(&nongit_ok);
        git_config(git_imap_config, NULL);
 
+       if (!server.port)
+               server.port = server.use_ssl ? 993 : 143;
+
        if (!imap_folder) {
-               fprintf( stderr, "no imap store specified\n" );
+               fprintf(stderr, "no imap store specified\n");
                return 1;
        }
        if (!server.host) {
                if (!server.tunnel) {
-                       fprintf( stderr, "no imap host specified\n" );
+                       fprintf(stderr, "no imap host specified\n");
                        return 1;
                }
                server.host = "tunnel";
        }
 
        /* read the messages */
-       if (!read_message( stdin, &all_msgs )) {
-               fprintf(stderr,"nothing to send\n");
+       if (!read_message(stdin, &all_msgs)) {
+               fprintf(stderr, "nothing to send\n");
                return 1;
        }
 
-       total = count_messages( &all_msgs );
+       total = count_messages(&all_msgs);
        if (!total) {
-               fprintf(stderr,"no messages to send\n");
+               fprintf(stderr, "no messages to send\n");
                return 1;
        }
 
        /* write it to the imap server */
-       ctx = imap_open_store( &server );
+       ctx = imap_open_store(&server);
        if (!ctx) {
-               fprintf( stderr,"failed to open store\n");
+               fprintf(stderr, "failed to open store\n");
                return 1;
        }
 
-       fprintf( stderr, "sending %d message%s\n", total, (total!=1)?"s":"" );
+       fprintf(stderr, "sending %d message%s\n", total, (total != 1) ? "s" : "");
        ctx->name = imap_folder;
        while (1) {
                unsigned percent = n * 100 / total;
-               fprintf( stderr, "%4u%% (%d/%d) done\r", percent, n, total );
-               if (!split_msg( &all_msgs, &msg, &ofs ))
+               fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
+               if (!split_msg(&all_msgs, &msg, &ofs))
+                       break;
+               r = imap_store_msg(ctx, &msg, &uid);
+               if (r != DRV_OK)
                        break;
-               r = imap_store_msg( ctx, &msg, &uid );
-               if (r != DRV_OK) break;
                n++;
        }
-       fprintf( stderr,"\n" );
+       fprintf(stderr, "\n");
 
-       imap_close_store( ctx );
+       imap_close_store(ctx);
 
        return 0;
 }
diff --git a/levenshtein.c b/levenshtein.c
new file mode 100644 (file)
index 0000000..db52f2c
--- /dev/null
@@ -0,0 +1,47 @@
+#include "cache.h"
+#include "levenshtein.h"
+
+int levenshtein(const char *string1, const char *string2,
+               int w, int s, int a, int d)
+{
+       int len1 = strlen(string1), len2 = strlen(string2);
+       int *row0 = xmalloc(sizeof(int) * (len2 + 1));
+       int *row1 = xmalloc(sizeof(int) * (len2 + 1));
+       int *row2 = xmalloc(sizeof(int) * (len2 + 1));
+       int i, j;
+
+       for (j = 0; j <= len2; j++)
+               row1[j] = j * a;
+       for (i = 0; i < len1; i++) {
+               int *dummy;
+
+               row2[0] = (i + 1) * d;
+               for (j = 0; j < len2; j++) {
+                       /* substitution */
+                       row2[j + 1] = row1[j] + s * (string1[i] != string2[j]);
+                       /* swap */
+                       if (i > 0 && j > 0 && string1[i - 1] == string2[j] &&
+                                       string1[i] == string2[j - 1] &&
+                                       row2[j + 1] > row0[j - 1] + w)
+                               row2[j + 1] = row0[j - 1] + w;
+                       /* deletion */
+                       if (j + 1 < len2 && row2[j + 1] > row1[j + 1] + d)
+                               row2[j + 1] = row1[j + 1] + d;
+                       /* insertion */
+                       if (row2[j + 1] > row2[j] + a)
+                               row2[j + 1] = row2[j] + a;
+               }
+
+               dummy = row0;
+               row0 = row1;
+               row1 = row2;
+               row2 = dummy;
+       }
+
+       i = row1[len2];
+       free(row0);
+       free(row1);
+       free(row2);
+
+       return i;
+}
diff --git a/levenshtein.h b/levenshtein.h
new file mode 100644 (file)
index 0000000..0173abe
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef LEVENSHTEIN_H
+#define LEVENSHTEIN_H
+
+int levenshtein(const char *string1, const char *string2,
+       int swap_penalty, int substition_penalty,
+       int insertion_penalty, int deletion_penalty);
+
+#endif
index bd8b9e45ab46b8664c8b7016b33bee22f86c9e0d..2c1f3e673ae7c9441cb171244d3fb91a8dd9fb1b 100644 (file)
@@ -1,12 +1,48 @@
 #include "cache.h"
 #include "diff.h"
 #include "commit.h"
+#include "tag.h"
 #include "graph.h"
 #include "log-tree.h"
 #include "reflog-walk.h"
+#include "refs.h"
 
 struct decoration name_decoration = { "object names" };
 
+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;
+}
+
+void load_ref_decorations(void)
+{
+       static int loaded;
+       if (!loaded) {
+               loaded = 1;
+               for_each_ref(add_ref_decoration, NULL);
+       }
+}
+
 static void show_parents(struct commit *commit, int abbrev)
 {
        struct commit_list *p;
@@ -432,7 +468,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
        struct commit_list *parents;
        unsigned const char *sha1 = commit->object.sha1;
 
-       if (!opt->diff)
+       if (!opt->diff && !DIFF_OPT_TST(&opt->diffopt, EXIT_WITH_STATUS))
                return 0;
 
        /* Root commit? */
index 59ba4c48b7966db34c6345a445ab0b10e235ac83..3c8127bb7cc8d27ff1e91ad6c159bc718fb7d9d0 100644 (file)
@@ -17,5 +17,6 @@ void log_write_email_headers(struct rev_info *opt, const char *name,
                             const char **subject_p,
                             const char **extra_headers_p,
                             int *need_8bit_cte_p);
+void load_ref_decorations(void);
 
 #endif
diff --git a/merge-recursive.c b/merge-recursive.c
new file mode 100644 (file)
index 0000000..0891731
--- /dev/null
@@ -0,0 +1,1372 @@
+/*
+ * 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 "merge-recursive.h"
+
+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.
+ */
+
+struct commit *make_virtual_commit(struct tree *tree, const char *comment)
+{
+       struct commit *commit = xcalloc(1, sizeof(struct commit));
+       commit->tree = tree;
+       commit->util = (void*)comment;
+       /* 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 int show(struct merge_options *o, int v)
+{
+       return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5;
+}
+
+static void flush_output(struct merge_options *o)
+{
+       if (o->obuf.len) {
+               fputs(o->obuf.buf, stdout);
+               strbuf_reset(&o->obuf);
+       }
+}
+
+static void output(struct merge_options *o, int v, const char *fmt, ...)
+{
+       int len;
+       va_list ap;
+
+       if (!show(o, v))
+               return;
+
+       strbuf_grow(&o->obuf, o->call_depth * 2 + 2);
+       memset(o->obuf.buf + o->obuf.len, ' ', o->call_depth * 2);
+       strbuf_setlen(&o->obuf, o->obuf.len + o->call_depth * 2);
+
+       va_start(ap, fmt);
+       len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap);
+       va_end(ap);
+
+       if (len < 0)
+               len = 0;
+       if (len >= strbuf_avail(&o->obuf)) {
+               strbuf_grow(&o->obuf, len + 2);
+               va_start(ap, fmt);
+               len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap);
+               va_end(ap);
+               if (len >= strbuf_avail(&o->obuf)) {
+                       die("this should not happen, your snprintf is broken");
+               }
+       }
+       strbuf_setlen(&o->obuf, o->obuf.len + len);
+       strbuf_add(&o->obuf, "\n", 1);
+       if (!o->buffer_output)
+               flush_output(o);
+}
+
+static void output_commit_title(struct merge_options *o, struct commit *commit)
+{
+       int i;
+       flush_output(o);
+       for (i = o->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);
+}
+
+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(struct merge_options *o)
+{
+       struct tree *result = NULL;
+
+       if (unmerged_cache()) {
+               int i;
+               output(o, 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(o, 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);
+       struct merge_options *o = context;
+
+       memcpy(newpath, base, baselen);
+       memcpy(newpath + baselen, path, len);
+       newpath[baselen + len] = '\0';
+
+       if (S_ISDIR(mode))
+               string_list_insert(newpath, &o->current_directory_set);
+       else
+               string_list_insert(newpath, &o->current_file_set);
+       free(newpath);
+
+       return READ_TREE_RECURSIVE;
+}
+
+static int get_files_dirs(struct merge_options *o, struct tree *tree)
+{
+       int n;
+       if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, o))
+               return 0;
+       n = o->current_file_set.nr + o->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 merge_options *o,
+                                      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 = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
+                           o->diff_rename_limit >= 0 ? o->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_path(const char *name)
+{
+       char *slash, *dirs;
+
+       if (unlink(name))
+               return -1;
+       dirs = xstrdup(name);
+       while ((slash = strrchr(name, '/'))) {
+               *slash = '\0';
+               if (rmdir(name) != 0)
+                       break;
+       }
+       free(dirs);
+       return 0;
+}
+
+static int remove_file(struct merge_options *o, int clean,
+                      const char *path, int no_wd)
+{
+       int update_cache = o->call_depth || clean;
+       int update_working_directory = !o->call_depth && !no_wd;
+
+       if (update_cache) {
+               if (remove_file_from_cache(path))
+                       return -1;
+       }
+       if (update_working_directory) {
+               if (remove_path(path) && errno != ENOENT)
+                       return -1;
+       }
+       return 0;
+}
+
+static char *unique_path(struct merge_options *o, 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(&o->current_file_set, newpath) ||
+              string_list_has_string(&o->current_directory_set, newpath) ||
+              lstat(newpath, &st) == 0)
+               sprintf(p, "_%d", suffix++);
+
+       string_list_insert(newpath, &o->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(struct merge_options *o,
+                             const unsigned char *sha,
+                             unsigned mode,
+                             const char *path,
+                             int update_cache,
+                             int update_wd)
+{
+       if (o->call_depth)
+               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(struct merge_options *o,
+                       int clean,
+                       const unsigned char *sha,
+                       unsigned mode,
+                       const char *path)
+{
+       update_file_flags(o, sha, mode, path, o->call_depth || clean, !o->call_depth);
+}
+
+/* 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(struct merge_options *o,
+                     mmbuffer_t *result_buf,
+                     struct diff_filespec *one,
+                     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(one->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,
+                               o->call_depth);
+
+       free(name1);
+       free(name2);
+       free(orig.ptr);
+       free(src1.ptr);
+       free(src2.ptr);
+       return merge_status;
+}
+
+static struct merge_file_info merge_file(struct merge_options *o,
+                                        struct diff_filespec *one,
+                                        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, one->sha1) && !sha_eq(b->sha1, one->sha1))
+                       result.merge = 1;
+
+               /*
+                * Merge modes
+                */
+               if (a->mode == b->mode || a->mode == one->mode)
+                       result.mode = b->mode;
+               else {
+                       result.mode = a->mode;
+                       if (b->mode != one->mode) {
+                               result.clean = 0;
+                               result.merge = 1;
+                       }
+               }
+
+               if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, one->sha1))
+                       hashcpy(result.sha, b->sha1);
+               else if (sha_eq(b->sha1, one->sha1))
+                       hashcpy(result.sha, a->sha1);
+               else if (S_ISREG(a->mode)) {
+                       mmbuffer_t result_buf;
+                       int merge_status;
+
+                       merge_status = merge_3way(o, &result_buf, one, 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 merge_options *o,
+                                  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(&o->current_directory_set, ren1_dst)) {
+               dst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1);
+               output(o, 1, "%s is a directory in %s adding as %s instead",
+                      ren1_dst, branch2, dst_name1);
+               remove_file(o, 0, ren1_dst, 0);
+       }
+       if (string_list_has_string(&o->current_directory_set, ren2_dst)) {
+               dst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2);
+               output(o, 1, "%s is a directory in %s adding as %s instead",
+                      ren2_dst, branch1, dst_name2);
+               remove_file(o, 0, ren2_dst, 0);
+       }
+       if (o->call_depth) {
+               remove_file_from_cache(dst_name1);
+               remove_file_from_cache(dst_name2);
+               /*
+                * Uncomment to leave the conflicting names in the resulting tree
+                *
+                * update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1);
+                * update_file(o, 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 merge_options *o,
+                               struct rename *ren1,
+                               const char *branch1)
+{
+       char *new_path = unique_path(o, ren1->pair->two->path, branch1);
+       output(o, 1, "Renaming %s to %s instead", ren1->pair->one->path, new_path);
+       remove_file(o, 0, ren1->pair->two->path, 0);
+       update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
+       free(new_path);
+}
+
+static void conflict_rename_rename_2(struct merge_options *o,
+                                    struct rename *ren1,
+                                    const char *branch1,
+                                    struct rename *ren2,
+                                    const char *branch2)
+{
+       char *new_path1 = unique_path(o, ren1->pair->two->path, branch1);
+       char *new_path2 = unique_path(o, ren2->pair->two->path, branch2);
+       output(o, 1, "Renaming %s to %s and %s to %s instead",
+              ren1->pair->one->path, new_path1,
+              ren2->pair->one->path, new_path2);
+       remove_file(o, 0, ren1->pair->two->path, 0);
+       update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
+       update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
+       free(new_path2);
+       free(new_path1);
+}
+
+static int process_renames(struct merge_options *o,
+                          struct string_list *a_renames,
+                          struct string_list *b_renames)
+{
+       int clean_merge = 1, i, j;
+       struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};
+       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 = o->branch1;
+                       branch2 = o->branch2;
+               } else {
+                       struct rename *tmp;
+                       renames1 = b_renames;
+                       renames2 = a_renames;
+                       renames2Dst = &a_by_dst;
+                       branch1 = o->branch2;
+                       branch2 = o->branch1;
+                       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(o, 1, "CONFLICT (rename/rename): "
+                                      "Rename \"%s\"->\"%s\" in branch \"%s\" "
+                                      "rename \"%s\"->\"%s\" in \"%s\"%s",
+                                      src, ren1_dst, branch1,
+                                      src, ren2_dst, branch2,
+                                      o->call_depth ? " (left unresolved)": "");
+                               if (o->call_depth) {
+                                       remove_file_from_cache(src);
+                                       update_file(o, 0, ren1->pair->one->sha1,
+                                                   ren1->pair->one->mode, src);
+                               }
+                               conflict_rename_rename(o, ren1, branch1, ren2, branch2);
+                       } else {
+                               struct merge_file_info mfi;
+                               remove_file(o, 1, ren1_src, 1);
+                               mfi = merge_file(o,
+                                                ren1->pair->one,
+                                                ren1->pair->two,
+                                                ren2->pair->two,
+                                                branch1,
+                                                branch2);
+                               if (mfi.merge || !mfi.clean)
+                                       output(o, 1, "Renaming %s->%s", src, ren1_dst);
+
+                               if (mfi.merge)
+                                       output(o, 2, "Auto-merging %s", ren1_dst);
+
+                               if (!mfi.clean) {
+                                       output(o, 1, "CONFLICT (content): merge conflict in %s",
+                                              ren1_dst);
+                                       clean_merge = 0;
+
+                                       if (!o->call_depth)
+                                               update_stages(ren1_dst,
+                                                             ren1->pair->one,
+                                                             ren1->pair->two,
+                                                             ren2->pair->two,
+                                                             1 /* clear */);
+                               }
+                               update_file(o, 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(o, 1, ren1_src, o->call_depth || 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(&o->current_directory_set, ren1_dst)) {
+                               clean_merge = 0;
+                               output(o, 1, "CONFLICT (rename/directory): Rename %s->%s in %s "
+                                      " directory %s added in %s",
+                                      ren1_src, ren1_dst, branch1,
+                                      ren1_dst, branch2);
+                               conflict_rename_dir(o, ren1, branch1);
+                       } else if (sha_eq(src_other.sha1, null_sha1)) {
+                               clean_merge = 0;
+                               output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
+                                      "and deleted in %s",
+                                      ren1_src, ren1_dst, branch1,
+                                      branch2);
+                               update_file(o, 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(o, 1, "CONFLICT (rename/add): Rename %s->%s in %s. "
+                                      "%s added in %s",
+                                      ren1_src, ren1_dst, branch1,
+                                      ren1_dst, branch2);
+                               new_path = unique_path(o, ren1_dst, branch2);
+                               output(o, 1, "Adding as %s instead", new_path);
+                               update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
+                       } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) {
+                               ren2 = item->util;
+                               clean_merge = 0;
+                               ren2->processed = 1;
+                               output(o, 1, "CONFLICT (rename/rename): "
+                                      "Rename %s->%s in %s. "
+                                      "Rename %s->%s in %s",
+                                      ren1_src, ren1_dst, branch1,
+                                      ren2->pair->one->path, ren2->pair->two->path, branch2);
+                               conflict_rename_rename_2(o, ren1, branch1, ren2, branch2);
+                       } else
+                               try_merge = 1;
+
+                       if (try_merge) {
+                               struct diff_filespec *one, *a, *b;
+                               struct merge_file_info mfi;
+                               src_other.path = (char *)ren1_src;
+
+                               one = 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, one, a, b,
+                                               o->branch1, o->branch2);
+
+                               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(o, 3, "Skipped %s (merged same as existing)", ren1_dst);
+                               else {
+                                       if (mfi.merge || !mfi.clean)
+                                               output(o, 1, "Renaming %s => %s", ren1_src, ren1_dst);
+                                       if (mfi.merge)
+                                               output(o, 2, "Auto-merging %s", ren1_dst);
+                                       if (!mfi.clean) {
+                                               output(o, 1, "CONFLICT (rename/modify): Merge conflict in %s",
+                                                      ren1_dst);
+                                               clean_merge = 0;
+
+                                               if (!o->call_depth)
+                                                       update_stages(ren1_dst,
+                                                                     one, a, b, 1);
+                                       }
+                                       update_file(o, 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(struct merge_options *o,
+                        const char *path, struct stage_data *entry)
+{
+       /*
+       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(o, 2, "Removing %s", path);
+                       /* do not touch working file if it did not exist */
+                       remove_file(o, 1, path, !a_sha);
+               } else {
+                       /* Deleted in one and changed in the other */
+                       clean_merge = 0;
+                       if (!a_sha) {
+                               output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
+                                      "and modified in %s. Version %s of %s left in tree.",
+                                      path, o->branch1,
+                                      o->branch2, o->branch2, path);
+                               update_file(o, 0, b_sha, b_mode, path);
+                       } else {
+                               output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
+                                      "and modified in %s. Version %s of %s left in tree.",
+                                      path, o->branch2,
+                                      o->branch1, o->branch1, path);
+                               update_file(o, 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 = o->branch1;
+                       other_branch = o->branch2;
+                       mode = a_mode;
+                       sha = a_sha;
+                       conf = "file/directory";
+               } else {
+                       add_branch = o->branch2;
+                       other_branch = o->branch1;
+                       mode = b_mode;
+                       sha = b_sha;
+                       conf = "directory/file";
+               }
+               if (string_list_has_string(&o->current_directory_set, path)) {
+                       const char *new_path = unique_path(o, path, add_branch);
+                       clean_merge = 0;
+                       output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
+                              "Adding %s as %s",
+                              conf, path, other_branch, path, new_path);
+                       remove_file(o, 0, path, 0);
+                       update_file(o, 0, sha, mode, new_path);
+               } else {
+                       output(o, 2, "Adding %s", path);
+                       update_file(o, 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 one, a, b;
+
+               if (!o_sha) {
+                       reason = "add/add";
+                       o_sha = (unsigned char *)null_sha1;
+               }
+               output(o, 2, "Auto-merging %s", path);
+               one.path = a.path = b.path = (char *)path;
+               hashcpy(one.sha1, o_sha);
+               one.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, &one, &a, &b,
+                                o->branch1, o->branch2);
+
+               clean_merge = mfi.clean;
+               if (mfi.clean)
+                       update_file(o, 1, mfi.sha, mfi.mode, path);
+               else if (S_ISGITLINK(mfi.mode))
+                       output(o, 1, "CONFLICT (submodule): Merge conflict in %s "
+                              "- needs %s", path, sha1_to_hex(b.sha1));
+               else {
+                       output(o, 1, "CONFLICT (%s): Merge conflict in %s",
+                                       reason, path);
+
+                       if (o->call_depth)
+                               update_file(o, 0, mfi.sha, mfi.mode, path);
+                       else
+                               update_file_flags(o, 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(o, 1, path, !a_mode);
+       } else
+               die("Fatal merge failure, shouldn't happen.");
+
+       return clean_merge;
+}
+
+int merge_trees(struct merge_options *o,
+               struct tree *head,
+               struct tree *merge,
+               struct tree *common,
+               struct tree **result)
+{
+       int code, clean;
+
+       if (o->subtree_merge) {
+               merge = shift_tree_object(head, merge);
+               common = shift_tree_object(head, common);
+       }
+
+       if (sha_eq(common->object.sha1, merge->object.sha1)) {
+               output(o, 0, "Already uptodate!");
+               *result = head;
+               return 1;
+       }
+
+       code = git_merge_trees(o->call_depth, common, head, merge);
+
+       if (code != 0)
+               die("merging of trees %s and %s failed",
+                   sha1_to_hex(head->object.sha1),
+                   sha1_to_hex(merge->object.sha1));
+
+       if (unmerged_cache()) {
+               struct string_list *entries, *re_head, *re_merge;
+               int i;
+               string_list_clear(&o->current_file_set, 1);
+               string_list_clear(&o->current_directory_set, 1);
+               get_files_dirs(o, head);
+               get_files_dirs(o, merge);
+
+               entries = get_unmerged();
+               re_head  = get_renames(o, head, common, head, merge, entries);
+               re_merge = get_renames(o, merge, common, head, merge, entries);
+               clean = process_renames(o, re_head, re_merge);
+               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(o, path, e))
+                               clean = 0;
+               }
+
+               string_list_clear(re_merge, 0);
+               string_list_clear(re_head, 0);
+               string_list_clear(entries, 1);
+
+       }
+       else
+               clean = 1;
+
+       if (o->call_depth)
+               *result = write_tree_from_memory(o);
+
+       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 merge_options *o,
+                   struct commit *h1,
+                   struct commit *h2,
+                   struct commit_list *ca,
+                   struct commit **result)
+{
+       struct commit_list *iter;
+       struct commit *merged_common_ancestors;
+       struct tree *mrtree = mrtree;
+       int clean;
+
+       if (show(o, 4)) {
+               output(o, 4, "Merging:");
+               output_commit_title(o, h1);
+               output_commit_title(o, h2);
+       }
+
+       if (!ca) {
+               ca = get_merge_bases(h1, h2, 1);
+               ca = reverse_commit_list(ca);
+       }
+
+       if (show(o, 5)) {
+               output(o, 5, "found %u common ancestor(s):", commit_list_count(ca));
+               for (iter = ca; iter; iter = iter->next)
+                       output_commit_title(o, 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) {
+               const char *saved_b1, *saved_b2;
+               o->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();
+               saved_b1 = o->branch1;
+               saved_b2 = o->branch2;
+               o->branch1 = "Temporary merge branch 1";
+               o->branch2 = "Temporary merge branch 2";
+               merge_recursive(o, merged_common_ancestors, iter->item,
+                               NULL, &merged_common_ancestors);
+               o->branch1 = saved_b1;
+               o->branch2 = saved_b2;
+               o->call_depth--;
+
+               if (!merged_common_ancestors)
+                       die("merge returned no commit");
+       }
+
+       discard_cache();
+       if (!o->call_depth)
+               read_cache();
+
+       clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
+                           &mrtree);
+
+       if (o->call_depth) {
+               *result = make_virtual_commit(mrtree, "merged tree");
+               commit_list_insert(h1, &(*result)->parents);
+               commit_list_insert(h2, &(*result)->parents->next);
+       }
+       flush_output(o);
+       return clean;
+}
+
+static struct commit *get_ref(const unsigned char *sha1, const char *name)
+{
+       struct object *object;
+
+       object = deref_tag(parse_object(sha1), name, strlen(name));
+       if (!object)
+               return NULL;
+       if (object->type == OBJ_TREE)
+               return make_virtual_commit((struct tree*)object, name);
+       if (object->type != OBJ_COMMIT)
+               return NULL;
+       if (parse_commit((struct commit *)object))
+               return NULL;
+       return (struct commit *)object;
+}
+
+int merge_recursive_generic(struct merge_options *o,
+                           const unsigned char *head,
+                           const unsigned char *merge,
+                           int num_base_list,
+                           const unsigned char **base_list,
+                           struct commit **result)
+{
+       int clean, index_fd;
+       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+       struct commit *head_commit = get_ref(head, o->branch1);
+       struct commit *next_commit = get_ref(merge, o->branch2);
+       struct commit_list *ca = NULL;
+
+       if (base_list) {
+               int i;
+               for (i = 0; i < num_base_list; ++i) {
+                       struct commit *base;
+                       if (!(base = get_ref(base_list[i], sha1_to_hex(base_list[i]))))
+                               return error("Could not parse object '%s'",
+                                       sha1_to_hex(base_list[i]));
+                       commit_list_insert(base, &ca);
+               }
+       }
+
+       index_fd = hold_locked_index(lock, 1);
+       clean = merge_recursive(o, head_commit, next_commit, ca,
+                       result);
+       if (active_cache_changed &&
+                       (write_cache(index_fd, active_cache, active_nr) ||
+                        commit_locked_index(lock)))
+               return error("Unable to write index.");
+
+       return clean ? 0 : 1;
+}
+
+static int merge_recursive_config(const char *var, const char *value, void *cb)
+{
+       struct merge_options *o = cb;
+       if (!strcasecmp(var, "merge.verbosity")) {
+               o->verbosity = git_config_int(var, value);
+               return 0;
+       }
+       if (!strcasecmp(var, "diff.renamelimit")) {
+               o->diff_rename_limit = git_config_int(var, value);
+               return 0;
+       }
+       if (!strcasecmp(var, "merge.renamelimit")) {
+               o->merge_rename_limit = git_config_int(var, value);
+               return 0;
+       }
+       return git_default_config(var, value, cb);
+}
+
+void init_merge_options(struct merge_options *o)
+{
+       memset(o, 0, sizeof(struct merge_options));
+       o->verbosity = 2;
+       o->buffer_output = 1;
+       o->diff_rename_limit = -1;
+       o->merge_rename_limit = -1;
+       git_config(merge_recursive_config, o);
+       if (getenv("GIT_MERGE_VERBOSITY"))
+               o->verbosity =
+                       strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10);
+       if (o->verbosity >= 5)
+               o->buffer_output = 0;
+       strbuf_init(&o->obuf, 0);
+       memset(&o->current_file_set, 0, sizeof(struct string_list));
+       o->current_file_set.strdup_strings = 1;
+       memset(&o->current_directory_set, 0, sizeof(struct string_list));
+       o->current_directory_set.strdup_strings = 1;
+}
index f37630a8ad07709ae106ddde44a34daf6bad8b16..fd138ca14006843a7ce0c38cfde4de580d5ce36e 100644 (file)
@@ -1,20 +1,48 @@
 #ifndef MERGE_RECURSIVE_H
 #define MERGE_RECURSIVE_H
 
-int merge_recursive(struct commit *h1,
+#include "string-list.h"
+
+struct merge_options {
+       const char *branch1;
+       const char *branch2;
+       unsigned subtree_merge : 1;
+       unsigned buffer_output : 1;
+       int verbosity;
+       int diff_rename_limit;
+       int merge_rename_limit;
+       int call_depth;
+       struct strbuf obuf;
+       struct string_list current_file_set;
+       struct string_list current_directory_set;
+};
+
+/* merge_trees() but with recursive ancestor consolidation */
+int merge_recursive(struct merge_options *o,
+                   struct commit *h1,
                    struct commit *h2,
-                   const char *branch1,
-                   const char *branch2,
                    struct commit_list *ancestors,
                    struct commit **result);
 
-int merge_trees(struct tree *head,
+/* rename-detecting three-way merge, no recursion */
+int merge_trees(struct merge_options *o,
+               struct tree *head,
                struct tree *merge,
                struct tree *common,
-               const char *branch1,
-               const char *branch2,
                struct tree **result);
 
-struct tree *write_tree_from_memory(void);
+/*
+ * "git-merge-recursive" can be fed trees; wrap them into
+ * virtual commits and call merge_recursive() proper.
+ */
+int merge_recursive_generic(struct merge_options *o,
+                           const unsigned char *head,
+                           const unsigned char *merge,
+                           int num_ca,
+                           const unsigned char **ca,
+                           struct commit **result);
+
+void init_merge_options(struct merge_options *o);
+struct tree *write_tree_from_memory(struct merge_options *o);
 
 #endif
index 036bd66fe9b6591e959e6df51160e636ab1a682e..d962ff11d1b2f810e21b049c7dbfed104cc199cb 100644 (file)
--- a/object.h
+++ b/object.h
@@ -41,7 +41,18 @@ extern int type_from_string(const char *str);
 extern unsigned int get_max_object_index(void);
 extern struct object *get_indexed_object(unsigned int);
 
-/** Internal only **/
+/*
+ * This can be used to see if we have heard of the object before, but
+ * it can return "yes we have, and here is a half-initialised object"
+ * for an object that we haven't loaded/parsed yet.
+ *
+ * When parsing a commit to create an in-core commit object, its
+ * parents list holds commit objects that represent its parents, but
+ * they are expected to be lazily initialized and do not know what
+ * their trees or parents are yet.  When this function returns such a
+ * half-initialised objects, the caller is expected to initialize them
+ * by calling parse_object() on them.
+ */
 struct object *lookup_object(const unsigned char *sha1);
 
 extern void *create_object(const unsigned char *sha1, int type, void *obj);
diff --git a/pager.c b/pager.c
index 6b5c9e44b4ded338ddb344ae454d83a685b7569a..aa0966c9c55566382bf32c946c0a1846f004125a 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "run-command.h"
 
 /*
  * This is split up from the rest of git so that we can do
@@ -8,7 +9,7 @@
 static int spawned_pager;
 
 #ifndef __MINGW32__
-static void run_pager(const char *pager)
+static void pager_preexec(void)
 {
        /*
         * Work around bug in "less" by not starting it until we
@@ -20,17 +21,13 @@ static void run_pager(const char *pager)
        FD_SET(0, &in);
        select(1, &in, NULL, &in, NULL);
 
-       execlp(pager, pager, NULL);
-       execl("/bin/sh", "sh", "-c", pager, NULL);
+       setenv("LESS", "FRSX", 0);
 }
-#else
-#include "run-command.h"
+#endif
 
 static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
-static struct child_process pager_process = {
-       .argv = pager_argv,
-       .in = -1
-};
+static struct child_process pager_process;
+
 static void wait_for_pager(void)
 {
        fflush(stdout);
@@ -40,14 +37,9 @@ static void wait_for_pager(void)
        close(2);
        finish_command(&pager_process);
 }
-#endif
 
 void setup_pager(void)
 {
-#ifndef __MINGW32__
-       pid_t pid;
-       int fd[2];
-#endif
        const char *pager = getenv("GIT_PAGER");
 
        if (!isatty(1))
@@ -66,37 +58,13 @@ void setup_pager(void)
 
        spawned_pager = 1; /* means we are emitting to terminal */
 
-#ifndef __MINGW32__
-       if (pipe(fd) < 0)
-               return;
-       pid = fork();
-       if (pid < 0) {
-               close(fd[0]);
-               close(fd[1]);
-               return;
-       }
-
-       /* return in the child */
-       if (!pid) {
-               dup2(fd[1], 1);
-               dup2(fd[1], 2);
-               close(fd[0]);
-               close(fd[1]);
-               return;
-       }
-
-       /* The original process turns into the PAGER */
-       dup2(fd[0], 0);
-       close(fd[0]);
-       close(fd[1]);
-
-       setenv("LESS", "FRSX", 0);
-       run_pager(pager);
-       die("unable to execute pager '%s'", pager);
-       exit(255);
-#else
        /* spawn the pager */
        pager_argv[2] = pager;
+       pager_process.argv = pager_argv;
+       pager_process.in = -1;
+#ifndef __MINGW32__
+       pager_process.preexec_cb = pager_preexec;
+#endif
        if (start_command(&pager_process))
                return;
 
@@ -107,7 +75,6 @@ void setup_pager(void)
 
        /* this makes sure that the parent terminates after the pager */
        atexit(wait_for_pager);
-#endif
 }
 
 int pager_in_use(void)
index a29c290009587a12cdc6aec335d508d29481e697..8beafa08d3927e943a94279f5d776d6a45c673c5 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -5,6 +5,7 @@
 #include "revision.h"
 #include "string-list.h"
 #include "mailmap.h"
+#include "log-tree.h"
 
 static char *user_format;
 
@@ -481,6 +482,23 @@ static void parse_commit_header(struct format_commit_context *context)
        context->commit_header_parsed = 1;
 }
 
+static void format_decoration(struct strbuf *sb, const struct commit *commit)
+{
+       struct name_decoration *d;
+       const char *prefix = " (";
+
+       load_ref_decorations();
+       d = lookup_decoration(&name_decoration, &commit->object);
+       while (d) {
+               strbuf_addstr(sb, prefix);
+               prefix = ", ";
+               strbuf_addstr(sb, d->name);
+               d = d->next;
+       }
+       if (prefix[0] == ',')
+               strbuf_addch(sb, ')');
+}
+
 static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
                                void *context)
 {
@@ -573,6 +591,9 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
                                 ? '<'
                                 : '>');
                return 1;
+       case 'd':
+               format_decoration(sb, commit);
+               return 1;
        }
 
        /* For the rest we have to parse the commit header. */
index 8f96fd17226e26c4b8a1497aace5b9fffb664bbe..5b1b3ad03b6d7294ca06b5b1053799958ad831c7 100644 (file)
@@ -8,6 +8,11 @@
 #include "cache-tree.h"
 #include "refs.h"
 #include "dir.h"
+#include "tree.h"
+#include "commit.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
 
 /* Index extensions.
  *
@@ -1118,6 +1123,10 @@ static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_en
        ce->ce_size  = ntohl(ondisk->size);
        /* On-disk flags are just 16 bits */
        ce->ce_flags = ntohs(ondisk->flags);
+
+       /* For future extension: we do not understand this entry yet */
+       if (ce->ce_flags & CE_EXTENDED)
+               die("Unknown index entry format");
        hashcpy(ce->sha1, ondisk->sha1);
 
        len = ce->ce_flags & CE_NAMEMASK;
@@ -1480,3 +1489,59 @@ int read_index_unmerged(struct index_state *istate)
        istate->cache_nr = dst - istate->cache;
        return !!last;
 }
+
+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_index(&the_index, path, data->flags)) {
+                               if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
+                                       die("updating files failed");
+                               data->add_errors++;
+                       }
+                       break;
+               case DIFF_STATUS_DELETED:
+                       if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
+                               break;
+                       if (!(data->flags & ADD_CACHE_PRETEND))
+                               remove_file_from_index(&the_index, path);
+                       if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
+                               printf("remove '%s'\n", path);
+                       break;
+               }
+       }
+}
+
+int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
+{
+       struct update_callback_data data;
+       struct rev_info rev;
+       init_revisions(&rev, prefix);
+       setup_revisions(0, NULL, &rev, NULL);
+       rev.prune_data = pathspec;
+       rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = update_callback;
+       data.flags = flags;
+       data.add_errors = 0;
+       rev.diffopt.format_callback_data = &data;
+       run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
+       return !!data.add_errors;
+}
+
diff --git a/receive-pack.c b/receive-pack.c
deleted file mode 100644 (file)
index d44c19e..0000000
+++ /dev/null
@@ -1,520 +0,0 @@
-#include "cache.h"
-#include "pack.h"
-#include "refs.h"
-#include "pkt-line.h"
-#include "run-command.h"
-#include "exec_cmd.h"
-#include "commit.h"
-#include "object.h"
-
-static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
-
-static int deny_non_fast_forwards = 0;
-static int receive_fsck_objects;
-static int receive_unpack_limit = -1;
-static int transfer_unpack_limit = -1;
-static int unpack_limit = 100;
-static int report_status;
-
-static char capabilities[] = " report-status delete-refs ";
-static int capabilities_sent;
-
-static int receive_pack_config(const char *var, const char *value, void *cb)
-{
-       if (strcmp(var, "receive.denynonfastforwards") == 0) {
-               deny_non_fast_forwards = git_config_bool(var, value);
-               return 0;
-       }
-
-       if (strcmp(var, "receive.unpacklimit") == 0) {
-               receive_unpack_limit = git_config_int(var, value);
-               return 0;
-       }
-
-       if (strcmp(var, "transfer.unpacklimit") == 0) {
-               transfer_unpack_limit = git_config_int(var, value);
-               return 0;
-       }
-
-       if (strcmp(var, "receive.fsckobjects") == 0) {
-               receive_fsck_objects = git_config_bool(var, value);
-               return 0;
-       }
-
-       return git_default_config(var, value, cb);
-}
-
-static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
-       if (capabilities_sent)
-               packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
-       else
-               packet_write(1, "%s %s%c%s\n",
-                            sha1_to_hex(sha1), path, 0, capabilities);
-       capabilities_sent = 1;
-       return 0;
-}
-
-static void write_head_info(void)
-{
-       for_each_ref(show_ref, NULL);
-       if (!capabilities_sent)
-               show_ref("capabilities^{}", null_sha1, 0, NULL);
-
-}
-
-struct command {
-       struct command *next;
-       const char *error_string;
-       unsigned char old_sha1[20];
-       unsigned char new_sha1[20];
-       char ref_name[FLEX_ARRAY]; /* more */
-};
-
-static struct command *commands;
-
-static const char pre_receive_hook[] = "hooks/pre-receive";
-static const char post_receive_hook[] = "hooks/post-receive";
-
-static int hook_status(int code, const char *hook_name)
-{
-       switch (code) {
-       case 0:
-               return 0;
-       case -ERR_RUN_COMMAND_FORK:
-               return error("hook fork failed");
-       case -ERR_RUN_COMMAND_EXEC:
-               return error("hook execute failed");
-       case -ERR_RUN_COMMAND_PIPE:
-               return error("hook pipe failed");
-       case -ERR_RUN_COMMAND_WAITPID:
-               return error("waitpid failed");
-       case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
-               return error("waitpid is confused");
-       case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
-               return error("%s died of signal", hook_name);
-       case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
-               return error("%s died strangely", hook_name);
-       default:
-               error("%s exited with error code %d", hook_name, -code);
-               return -code;
-       }
-}
-
-static int run_hook(const char *hook_name)
-{
-       static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
-       struct command *cmd;
-       struct child_process proc;
-       const char *argv[2];
-       int have_input = 0, code;
-
-       for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
-               if (!cmd->error_string)
-                       have_input = 1;
-       }
-
-       if (!have_input || access(hook_name, X_OK) < 0)
-               return 0;
-
-       argv[0] = hook_name;
-       argv[1] = NULL;
-
-       memset(&proc, 0, sizeof(proc));
-       proc.argv = argv;
-       proc.in = -1;
-       proc.stdout_to_stderr = 1;
-
-       code = start_command(&proc);
-       if (code)
-               return hook_status(code, hook_name);
-       for (cmd = commands; cmd; cmd = cmd->next) {
-               if (!cmd->error_string) {
-                       size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
-                               sha1_to_hex(cmd->old_sha1),
-                               sha1_to_hex(cmd->new_sha1),
-                               cmd->ref_name);
-                       if (write_in_full(proc.in, buf, n) != n)
-                               break;
-               }
-       }
-       close(proc.in);
-       return hook_status(finish_command(&proc), hook_name);
-}
-
-static int run_update_hook(struct command *cmd)
-{
-       static const char update_hook[] = "hooks/update";
-       struct child_process proc;
-       const char *argv[5];
-
-       if (access(update_hook, X_OK) < 0)
-               return 0;
-
-       argv[0] = update_hook;
-       argv[1] = cmd->ref_name;
-       argv[2] = sha1_to_hex(cmd->old_sha1);
-       argv[3] = sha1_to_hex(cmd->new_sha1);
-       argv[4] = NULL;
-
-       memset(&proc, 0, sizeof(proc));
-       proc.argv = argv;
-       proc.no_stdin = 1;
-       proc.stdout_to_stderr = 1;
-
-       return hook_status(run_command(&proc), update_hook);
-}
-
-static const char *update(struct command *cmd)
-{
-       const char *name = cmd->ref_name;
-       unsigned char *old_sha1 = cmd->old_sha1;
-       unsigned char *new_sha1 = cmd->new_sha1;
-       struct ref_lock *lock;
-
-       /* only refs/... are allowed */
-       if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
-               error("refusing to create funny ref '%s' remotely", name);
-               return "funny refname";
-       }
-
-       if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
-               error("unpack should have generated %s, "
-                     "but I can't find it!", sha1_to_hex(new_sha1));
-               return "bad pack";
-       }
-       if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
-           !is_null_sha1(old_sha1) &&
-           !prefixcmp(name, "refs/heads/")) {
-               struct object *old_object, *new_object;
-               struct commit *old_commit, *new_commit;
-               struct commit_list *bases, *ent;
-
-               old_object = parse_object(old_sha1);
-               new_object = parse_object(new_sha1);
-
-               if (!old_object || !new_object ||
-                   old_object->type != OBJ_COMMIT ||
-                   new_object->type != OBJ_COMMIT) {
-                       error("bad sha1 objects for %s", name);
-                       return "bad ref";
-               }
-               old_commit = (struct commit *)old_object;
-               new_commit = (struct commit *)new_object;
-               bases = get_merge_bases(old_commit, new_commit, 1);
-               for (ent = bases; ent; ent = ent->next)
-                       if (!hashcmp(old_sha1, ent->item->object.sha1))
-                               break;
-               free_commit_list(bases);
-               if (!ent) {
-                       error("denying non-fast forward %s"
-                             " (you should pull first)", name);
-                       return "non-fast forward";
-               }
-       }
-       if (run_update_hook(cmd)) {
-               error("hook declined to update %s", name);
-               return "hook declined";
-       }
-
-       if (is_null_sha1(new_sha1)) {
-               if (!parse_object(old_sha1)) {
-                       warning ("Allowing deletion of corrupt ref.");
-                       old_sha1 = NULL;
-               }
-               if (delete_ref(name, old_sha1)) {
-                       error("failed to delete %s", name);
-                       return "failed to delete";
-               }
-               return NULL; /* good */
-       }
-       else {
-               lock = lock_any_ref_for_update(name, old_sha1, 0);
-               if (!lock) {
-                       error("failed to lock %s", name);
-                       return "failed to lock";
-               }
-               if (write_ref_sha1(lock, new_sha1, "push")) {
-                       return "failed to write"; /* error() already called */
-               }
-               return NULL; /* good */
-       }
-}
-
-static char update_post_hook[] = "hooks/post-update";
-
-static void run_update_post_hook(struct command *cmd)
-{
-       struct command *cmd_p;
-       int argc;
-       const char **argv;
-
-       for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
-               if (cmd_p->error_string)
-                       continue;
-               argc++;
-       }
-       if (!argc || access(update_post_hook, X_OK) < 0)
-               return;
-       argv = xmalloc(sizeof(*argv) * (2 + argc));
-       argv[0] = update_post_hook;
-
-       for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
-               char *p;
-               if (cmd_p->error_string)
-                       continue;
-               p = xmalloc(strlen(cmd_p->ref_name) + 1);
-               strcpy(p, cmd_p->ref_name);
-               argv[argc] = p;
-               argc++;
-       }
-       argv[argc] = NULL;
-       run_command_v_opt(argv, RUN_COMMAND_NO_STDIN
-               | RUN_COMMAND_STDOUT_TO_STDERR);
-}
-
-static void execute_commands(const char *unpacker_error)
-{
-       struct command *cmd = commands;
-
-       if (unpacker_error) {
-               while (cmd) {
-                       cmd->error_string = "n/a (unpacker error)";
-                       cmd = cmd->next;
-               }
-               return;
-       }
-
-       if (run_hook(pre_receive_hook)) {
-               while (cmd) {
-                       cmd->error_string = "pre-receive hook declined";
-                       cmd = cmd->next;
-               }
-               return;
-       }
-
-       while (cmd) {
-               cmd->error_string = update(cmd);
-               cmd = cmd->next;
-       }
-}
-
-static void read_head_info(void)
-{
-       struct command **p = &commands;
-       for (;;) {
-               static char line[1000];
-               unsigned char old_sha1[20], new_sha1[20];
-               struct command *cmd;
-               char *refname;
-               int len, reflen;
-
-               len = packet_read_line(0, line, sizeof(line));
-               if (!len)
-                       break;
-               if (line[len-1] == '\n')
-                       line[--len] = 0;
-               if (len < 83 ||
-                   line[40] != ' ' ||
-                   line[81] != ' ' ||
-                   get_sha1_hex(line, old_sha1) ||
-                   get_sha1_hex(line + 41, new_sha1))
-                       die("protocol error: expected old/new/ref, got '%s'",
-                           line);
-
-               refname = line + 82;
-               reflen = strlen(refname);
-               if (reflen + 82 < len) {
-                       if (strstr(refname + reflen + 1, "report-status"))
-                               report_status = 1;
-               }
-               cmd = xmalloc(sizeof(struct command) + len - 80);
-               hashcpy(cmd->old_sha1, old_sha1);
-               hashcpy(cmd->new_sha1, new_sha1);
-               memcpy(cmd->ref_name, line + 82, len - 81);
-               cmd->error_string = NULL;
-               cmd->next = NULL;
-               *p = cmd;
-               p = &cmd->next;
-       }
-}
-
-static const char *parse_pack_header(struct pack_header *hdr)
-{
-       switch (read_pack_header(0, hdr)) {
-       case PH_ERROR_EOF:
-               return "eof before pack header was fully read";
-
-       case PH_ERROR_PACK_SIGNATURE:
-               return "protocol error (pack signature mismatch detected)";
-
-       case PH_ERROR_PROTOCOL:
-               return "protocol error (pack version unsupported)";
-
-       default:
-               return "unknown error in parse_pack_header";
-
-       case 0:
-               return NULL;
-       }
-}
-
-static const char *pack_lockfile;
-
-static const char *unpack(void)
-{
-       struct pack_header hdr;
-       const char *hdr_err;
-       char hdr_arg[38];
-
-       hdr_err = parse_pack_header(&hdr);
-       if (hdr_err)
-               return hdr_err;
-       snprintf(hdr_arg, sizeof(hdr_arg),
-                       "--pack_header=%"PRIu32",%"PRIu32,
-                       ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
-
-       if (ntohl(hdr.hdr_entries) < unpack_limit) {
-               int code, i = 0;
-               const char *unpacker[4];
-               unpacker[i++] = "unpack-objects";
-               if (receive_fsck_objects)
-                       unpacker[i++] = "--strict";
-               unpacker[i++] = hdr_arg;
-               unpacker[i++] = NULL;
-               code = run_command_v_opt(unpacker, RUN_GIT_CMD);
-               switch (code) {
-               case 0:
-                       return NULL;
-               case -ERR_RUN_COMMAND_FORK:
-                       return "unpack fork failed";
-               case -ERR_RUN_COMMAND_EXEC:
-                       return "unpack execute failed";
-               case -ERR_RUN_COMMAND_WAITPID:
-                       return "waitpid failed";
-               case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
-                       return "waitpid is confused";
-               case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
-                       return "unpacker died of signal";
-               case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
-                       return "unpacker died strangely";
-               default:
-                       return "unpacker exited with error code";
-               }
-       } else {
-               const char *keeper[7];
-               int s, status, i = 0;
-               char keep_arg[256];
-               struct child_process ip;
-
-               s = sprintf(keep_arg, "--keep=receive-pack %i on ", getpid());
-               if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
-                       strcpy(keep_arg + s, "localhost");
-
-               keeper[i++] = "index-pack";
-               keeper[i++] = "--stdin";
-               if (receive_fsck_objects)
-                       keeper[i++] = "--strict";
-               keeper[i++] = "--fix-thin";
-               keeper[i++] = hdr_arg;
-               keeper[i++] = keep_arg;
-               keeper[i++] = NULL;
-               memset(&ip, 0, sizeof(ip));
-               ip.argv = keeper;
-               ip.out = -1;
-               ip.git_cmd = 1;
-               if (start_command(&ip))
-                       return "index-pack fork failed";
-               pack_lockfile = index_pack_lockfile(ip.out);
-               close(ip.out);
-               status = finish_command(&ip);
-               if (!status) {
-                       reprepare_packed_git();
-                       return NULL;
-               }
-               return "index-pack abnormal exit";
-       }
-}
-
-static void report(const char *unpack_status)
-{
-       struct command *cmd;
-       packet_write(1, "unpack %s\n",
-                    unpack_status ? unpack_status : "ok");
-       for (cmd = commands; cmd; cmd = cmd->next) {
-               if (!cmd->error_string)
-                       packet_write(1, "ok %s\n",
-                                    cmd->ref_name);
-               else
-                       packet_write(1, "ng %s %s\n",
-                                    cmd->ref_name, cmd->error_string);
-       }
-       packet_flush(1);
-}
-
-static int delete_only(struct command *cmd)
-{
-       while (cmd) {
-               if (!is_null_sha1(cmd->new_sha1))
-                       return 0;
-               cmd = cmd->next;
-       }
-       return 1;
-}
-
-int main(int argc, char **argv)
-{
-       int i;
-       char *dir = NULL;
-
-       argv++;
-       for (i = 1; i < argc; i++) {
-               char *arg = *argv++;
-
-               if (*arg == '-') {
-                       /* Do flag handling here */
-                       usage(receive_pack_usage);
-               }
-               if (dir)
-                       usage(receive_pack_usage);
-               dir = arg;
-       }
-       if (!dir)
-               usage(receive_pack_usage);
-
-       setup_path();
-
-       if (!enter_repo(dir, 0))
-               die("'%s': unable to chdir or not a git archive", dir);
-
-       if (is_repository_shallow())
-               die("attempt to push into a shallow repository");
-
-       git_config(receive_pack_config, NULL);
-
-       if (0 <= transfer_unpack_limit)
-               unpack_limit = transfer_unpack_limit;
-       else if (0 <= receive_unpack_limit)
-               unpack_limit = receive_unpack_limit;
-
-       write_head_info();
-
-       /* EOF */
-       packet_flush(1);
-
-       read_head_info();
-       if (commands) {
-               const char *unpack_status = NULL;
-
-               if (!delete_only(commands))
-                       unpack_status = unpack();
-               execute_commands(unpack_status);
-               if (pack_lockfile)
-                       unlink(pack_lockfile);
-               if (report_status)
-                       report(unpack_status);
-               run_hook(post_receive_hook);
-               run_update_post_hook(commands);
-       }
-       return 0;
-}
diff --git a/refs.c b/refs.c
index 39a3b23804d2da715c564459bf320be23d41c1bf..b6807505e243fd26cae50460bc003254406ae7e5 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -390,6 +390,18 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *re
        return retval;
 }
 
+/*
+ * If the "reading" argument is set, this function finds out what _object_
+ * the ref points at by "reading" the ref.  The ref, if it is not symbolic,
+ * has to exist, and if it is symbolic, it has to point at an existing ref,
+ * because the "read" goes through the symref to the ref it points at.
+ *
+ * The access that is not "reading" may often be "writing", but does not
+ * have to; it can be merely checking _where it leads to_. If it is a
+ * prelude to "writing" to the ref, a write to a symref that points at
+ * yet-to-be-born ref will create the real ref pointed by the symref.
+ * reading=0 allows the caller to check where such a symref leads to.
+ */
 const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
 {
        int depth = MAXDEPTH;
@@ -409,13 +421,7 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                if (--depth < 0)
                        return NULL;
 
-               /* Special case: non-existing file.
-                * Not having the refs/heads/new-branch is OK
-                * if we are writing into it, so is .git/HEAD
-                * that points at refs/heads/master still to be
-                * born.  It is NOT OK if we are resolving for
-                * reading.
-                */
+               /* Special case: non-existing file. */
                if (lstat(path, &st) < 0) {
                        struct ref_list *list = get_packed_refs();
                        while (list) {
index 105668f8a3cf13c2a759e9da4d0acc50ef781de9..c45d96e98fffe3411284144b6d836cf499d42ba4 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -69,7 +69,7 @@ static const char *alias_url(const char *url)
        if (!longest)
                return url;
 
-       ret = malloc(rewrite[longest_i]->baselen +
+       ret = xmalloc(rewrite[longest_i]->baselen +
                     (strlen(url) - longest->len) + 1);
        strcpy(ret, rewrite[longest_i]->base);
        strcpy(ret + rewrite[longest_i]->baselen, url + longest->len);
@@ -152,7 +152,7 @@ static struct branch *make_branch(const char *name, int len)
                ret->name = xstrndup(name, len);
        else
                ret->name = xstrdup(name);
-       refname = malloc(strlen(name) + strlen("refs/heads/") + 1);
+       refname = xmalloc(strlen(name) + strlen("refs/heads/") + 1);
        strcpy(refname, "refs/heads/");
        strcpy(refname + strlen("refs/heads/"), ret->name);
        ret->refname = refname;
@@ -449,6 +449,26 @@ static int verify_refname(char *name, int is_glob)
        return result;
 }
 
+/*
+ * This function frees a refspec array.
+ * Warning: code paths should be checked to ensure that the src
+ *          and dst pointers are always freeable pointers as well
+ *          as the refspec pointer itself.
+ */
+static void free_refspecs(struct refspec *refspec, int nr_refspec)
+{
+       int i;
+
+       if (!refspec)
+               return;
+
+       for (i = 0; i < nr_refspec; i++) {
+               free(refspec[i].src);
+               free(refspec[i].dst);
+       }
+       free(refspec);
+}
+
 static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify)
 {
        int i;
@@ -567,7 +587,12 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
 
  invalid:
        if (verify) {
-               free(rs);
+               /*
+                * nr_refspec must be greater than zero and i must be valid
+                * since it is only possible to reach this point from within
+                * the for loop above.
+                */
+               free_refspecs(rs, i+1);
                return NULL;
        }
        die("Invalid refspec '%s'", refspec[i]);
@@ -579,7 +604,7 @@ int valid_fetch_refspec(const char *fetch_refspec_str)
        struct refspec *refspec;
 
        refspec = parse_refspec_internal(1, fetch_refspec, 1, 1);
-       free(refspec);
+       free_refspecs(refspec, 1);
        return !!refspec;
 }
 
@@ -588,7 +613,7 @@ struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec)
        return parse_refspec_internal(nr_refspec, refspec, 1, 0);
 }
 
-struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
+static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
 {
        return parse_refspec_internal(nr_refspec, refspec, 0, 0);
 }
@@ -758,7 +783,7 @@ struct ref *copy_ref_list(const struct ref *ref)
        return ret;
 }
 
-void free_ref(struct ref *ref)
+static void free_ref(struct ref *ref)
 {
        if (!ref)
                return;
index 091b1d041f8a4d255f59bfc001e098e692dbc15c..c6163ff5b1f7e7c96bedaa9a6b561a9f4a8ed7c2 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -77,7 +77,6 @@ void ref_remove_duplicates(struct ref *ref_map);
 
 int valid_fetch_refspec(const char *refspec);
 struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
-struct refspec *parse_push_refspec(int nr_refspec, const char **refspec);
 
 int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
               int nr_refspec, const char **refspec, int all);
index 323e493dafee46c0d3f95e3c4cd9c4c9b463bbef..c38886b22af677083e749148604320fcada1ee75 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -319,7 +319,6 @@ static int git_rerere_config(const char *var, const char *value, void *cb)
 
 static int is_rerere_enabled(void)
 {
-       struct stat st;
        const char *rr_cache;
        int rr_cache_exists;
 
@@ -327,7 +326,7 @@ static int is_rerere_enabled(void)
                return 0;
 
        rr_cache = git_path("rr-cache");
-       rr_cache_exists = !stat(rr_cache, &st) && S_ISDIR(st.st_mode);
+       rr_cache_exists = is_directory(rr_cache);
        if (rerere_enabled < 0)
                return rr_cache_exists;
 
index 270294af8321c0b81f720db0f36e8f7f9eddbc88..2f646deab09c423143185b7f7928ae46ab9f4c97 100644 (file)
@@ -489,7 +489,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit,
                        p->object.flags |= SEEN;
                        insert_by_date_cached(p, list, cached_base, cache_ptr);
                }
-               if(revs->first_parent_only)
+               if (revs->first_parent_only)
                        break;
        }
        return 0;
@@ -1028,6 +1028,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        } else if (!strcmp(arg, "--topo-order")) {
                revs->lifo = 1;
                revs->topo_order = 1;
+       } else if (!strcmp(arg, "--simplify-merges")) {
+               revs->simplify_merges = 1;
+               revs->rewrite_parents = 1;
+               revs->simplify_history = 0;
+               revs->limited = 1;
        } else if (!strcmp(arg, "--date-order")) {
                revs->lifo = 0;
                revs->topo_order = 1;
@@ -1355,6 +1360,179 @@ static void add_child(struct rev_info *revs, struct commit *parent, struct commi
        l->next = add_decoration(&revs->children, &parent->object, l);
 }
 
+static int remove_duplicate_parents(struct commit *commit)
+{
+       struct commit_list **pp, *p;
+       int surviving_parents;
+
+       /* Examine existing parents while marking ones we have seen... */
+       pp = &commit->parents;
+       while ((p = *pp) != NULL) {
+               struct commit *parent = p->item;
+               if (parent->object.flags & TMP_MARK) {
+                       *pp = p->next;
+                       continue;
+               }
+               parent->object.flags |= TMP_MARK;
+               pp = &p->next;
+       }
+       /* count them while clearing the temporary mark */
+       surviving_parents = 0;
+       for (p = commit->parents; p; p = p->next) {
+               p->item->object.flags &= ~TMP_MARK;
+               surviving_parents++;
+       }
+       return surviving_parents;
+}
+
+struct merge_simplify_state {
+       struct commit *simplified;
+};
+
+static struct merge_simplify_state *locate_simplify_state(struct rev_info *revs, struct commit *commit)
+{
+       struct merge_simplify_state *st;
+
+       st = lookup_decoration(&revs->merge_simplification, &commit->object);
+       if (!st) {
+               st = xcalloc(1, sizeof(*st));
+               add_decoration(&revs->merge_simplification, &commit->object, st);
+       }
+       return st;
+}
+
+static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail)
+{
+       struct commit_list *p;
+       struct merge_simplify_state *st, *pst;
+       int cnt;
+
+       st = locate_simplify_state(revs, commit);
+
+       /*
+        * Have we handled this one?
+        */
+       if (st->simplified)
+               return tail;
+
+       /*
+        * An UNINTERESTING commit simplifies to itself, so does a
+        * root commit.  We do not rewrite parents of such commit
+        * anyway.
+        */
+       if ((commit->object.flags & UNINTERESTING) || !commit->parents) {
+               st->simplified = commit;
+               return tail;
+       }
+
+       /*
+        * Do we know what commit all of our parents should be rewritten to?
+        * Otherwise we are not ready to rewrite this one yet.
+        */
+       for (cnt = 0, p = commit->parents; p; p = p->next) {
+               pst = locate_simplify_state(revs, p->item);
+               if (!pst->simplified) {
+                       tail = &commit_list_insert(p->item, tail)->next;
+                       cnt++;
+               }
+       }
+       if (cnt) {
+               tail = &commit_list_insert(commit, tail)->next;
+               return tail;
+       }
+
+       /*
+        * Rewrite our list of parents.
+        */
+       for (p = commit->parents; p; p = p->next) {
+               pst = locate_simplify_state(revs, p->item);
+               p->item = pst->simplified;
+       }
+       cnt = remove_duplicate_parents(commit);
+
+       /*
+        * It is possible that we are a merge and one side branch
+        * does not have any commit that touches the given paths;
+        * in such a case, the immediate parents will be rewritten
+        * to different commits.
+        *
+        *      o----X          X: the commit we are looking at;
+        *     /    /           o: a commit that touches the paths;
+        * ---o----'
+        *
+        * Further reduce the parents by removing redundant parents.
+        */
+       if (1 < cnt) {
+               struct commit_list *h = reduce_heads(commit->parents);
+               cnt = commit_list_count(h);
+               free_commit_list(commit->parents);
+               commit->parents = h;
+       }
+
+       /*
+        * A commit simplifies to itself if it is a root, if it is
+        * UNINTERESTING, if it touches the given paths, or if it is a
+        * merge and its parents simplifies to more than one commits
+        * (the first two cases are already handled at the beginning of
+        * this function).
+        *
+        * Otherwise, it simplifies to what its sole parent simplifies to.
+        */
+       if (!cnt ||
+           (commit->object.flags & UNINTERESTING) ||
+           !(commit->object.flags & TREESAME) ||
+           (1 < cnt))
+               st->simplified = commit;
+       else {
+               pst = locate_simplify_state(revs, commit->parents->item);
+               st->simplified = pst->simplified;
+       }
+       return tail;
+}
+
+static void simplify_merges(struct rev_info *revs)
+{
+       struct commit_list *list;
+       struct commit_list *yet_to_do, **tail;
+
+       if (!revs->topo_order)
+               sort_in_topological_order(&revs->commits, revs->lifo);
+       if (!revs->prune)
+               return;
+
+       /* feed the list reversed */
+       yet_to_do = NULL;
+       for (list = revs->commits; list; list = list->next)
+               commit_list_insert(list->item, &yet_to_do);
+       while (yet_to_do) {
+               list = yet_to_do;
+               yet_to_do = NULL;
+               tail = &yet_to_do;
+               while (list) {
+                       struct commit *commit = list->item;
+                       struct commit_list *next = list->next;
+                       free(list);
+                       list = next;
+                       tail = simplify_one(revs, commit, tail);
+               }
+       }
+
+       /* clean up the result, removing the simplified ones */
+       list = revs->commits;
+       revs->commits = NULL;
+       tail = &revs->commits;
+       while (list) {
+               struct commit *commit = list->item;
+               struct commit_list *next = list->next;
+               struct merge_simplify_state *st;
+               free(list);
+               list = next;
+               st = locate_simplify_state(revs, commit);
+               if (st->simplified == commit)
+                       tail = &commit_list_insert(commit, tail)->next;
+       }
+}
+
 static void set_children(struct rev_info *revs)
 {
        struct commit_list *l;
@@ -1395,6 +1573,8 @@ int prepare_revision_walk(struct rev_info *revs)
                        return -1;
        if (revs->topo_order)
                sort_in_topological_order(&revs->commits, revs->lifo);
+       if (revs->simplify_merges)
+               simplify_merges(revs);
        if (revs->children.name)
                set_children(revs);
        return 0;
@@ -1427,26 +1607,6 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp
        }
 }
 
-static void remove_duplicate_parents(struct commit *commit)
-{
-       struct commit_list **pp, *p;
-
-       /* Examine existing parents while marking ones we have seen... */
-       pp = &commit->parents;
-       while ((p = *pp) != NULL) {
-               struct commit *parent = p->item;
-               if (parent->object.flags & TMP_MARK) {
-                       *pp = p->next;
-                       continue;
-               }
-               parent->object.flags |= TMP_MARK;
-               pp = &p->next;
-       }
-       /* ... and clear the temporary mark */
-       for (p = commit->parents; p; p = p->next)
-               p->item->object.flags &= ~TMP_MARK;
-}
-
 static int rewrite_parents(struct rev_info *revs, struct commit *commit)
 {
        struct commit_list **pp = &commit->parents;
@@ -1633,26 +1793,6 @@ static struct commit *get_revision_internal(struct rev_info *revs)
                return c;
        }
 
-       if (revs->reverse) {
-               int limit = -1;
-
-               if (0 <= revs->max_count) {
-                       limit = revs->max_count;
-                       if (0 < revs->skip_count)
-                               limit += revs->skip_count;
-               }
-               l = NULL;
-               while ((c = get_revision_1(revs))) {
-                       commit_list_insert(c, &l);
-                       if ((0 < limit) && !--limit)
-                               break;
-               }
-               revs->commits = l;
-               revs->reverse = 0;
-               revs->max_count = -1;
-               c = NULL;
-       }
-
        /*
         * Now pick up what they want to give us
         */
@@ -1725,7 +1865,23 @@ static struct commit *get_revision_internal(struct rev_info *revs)
 
 struct commit *get_revision(struct rev_info *revs)
 {
-       struct commit *c = get_revision_internal(revs);
+       struct commit *c;
+       struct commit_list *reversed;
+
+       if (revs->reverse) {
+               reversed = NULL;
+               while ((c = get_revision_internal(revs))) {
+                       commit_list_insert(c, &reversed);
+               }
+               revs->commits = reversed;
+               revs->reverse = 0;
+               revs->reverse_output_stage = 1;
+       }
+
+       if (revs->reverse_output_stage)
+               return pop_commit(&revs->commits);
+
+       c = get_revision_internal(revs);
        if (c && revs->graph)
                graph_update(revs->graph, c);
        return c;
index 91f194478bb91d381ab2b2440215144d8bb8d18d..2fdb2dd0ff3425b68b47aa8fd11155aa881d4a4a 100644 (file)
@@ -42,6 +42,7 @@ struct rev_info {
                        simplify_history:1,
                        lifo:1,
                        topo_order:1,
+                       simplify_merges:1,
                        tag_objects:1,
                        tree_objects:1,
                        blob_objects:1,
@@ -53,6 +54,7 @@ struct rev_info {
                        rewrite_parents:1,
                        print_parents:1,
                        reverse:1,
+                       reverse_output_stage:1,
                        cherry_pick:1,
                        first_parent_only:1;
 
@@ -110,6 +112,7 @@ struct rev_info {
 
        struct reflog_walk_info *reflog_info;
        struct decoration children;
+       struct decoration merge_simplification;
 };
 
 #define REV_TREE_SAME          0
index bbb9c777e583c345d25a6651f9ddf7725c10f6af..caab374577e02e9a33cd8095b8da9234acb065f8 100644 (file)
@@ -111,6 +111,8 @@ int start_command(struct child_process *cmd)
                                        unsetenv(*cmd->env);
                        }
                }
+               if (cmd->preexec_cb)
+                       cmd->preexec_cb();
                if (cmd->git_cmd) {
                        execv_git_cmd(cmd->argv);
                } else {
index 5203a9ebb10b14bd06862abafed0ab73d7514a3d..4f2b7d7d403ee6d87fea5ba2dc32da7596965e5e 100644 (file)
@@ -42,6 +42,7 @@ struct child_process {
        unsigned no_stderr:1;
        unsigned git_cmd:1; /* if this is to be git sub-command */
        unsigned stdout_to_stderr:1;
+       void (*preexec_cb)(void);
 };
 
 int start_command(struct child_process *);
index e2cb342a32f31be2b9ffc1867fbfd671fe63cef1..70ff904717c2ffed12a70b988b1aa4dea3a896e2 100644 (file)
@@ -99,7 +99,11 @@ int safe_create_leading_directories(char *path)
                pos = strchr(pos, '/');
                if (!pos)
                        break;
-               *pos = 0;
+               while (*++pos == '/')
+                       ;
+               if (!*pos)
+                       break;
+               *--pos = '\0';
                if (!stat(path, &st)) {
                        /* path exists */
                        if (!S_ISDIR(st.st_mode)) {
@@ -250,7 +254,6 @@ static void read_info_alternates(const char * alternates, int depth);
  */
 static int link_alt_odb_entry(const char * entry, int len, const char * relative_base, int depth)
 {
-       struct stat st;
        const char *objdir = get_object_directory();
        struct alternate_object_database *ent;
        struct alternate_object_database *alt;
@@ -281,7 +284,7 @@ static int link_alt_odb_entry(const char * entry, int len, const char * relative
        ent->base[pfxlen] = ent->base[entlen-1] = 0;
 
        /* Detect cases where alternate disappeared */
-       if (stat(ent->base, &st) || !S_ISDIR(st.st_mode)) {
+       if (!is_directory(ent->base)) {
                error("object directory %s does not exist; "
                      "check .git/objects/info/alternates.",
                      ent->base);
@@ -394,6 +397,16 @@ void add_to_alternates_file(const char *reference)
                link_alt_odb_entries(alt, alt + strlen(alt), '\n', NULL, 0);
 }
 
+void foreach_alt_odb(alt_odb_fn fn, void *cb)
+{
+       struct alternate_object_database *ent;
+
+       prepare_alt_odb();
+       for (ent = alt_odb_list; ent; ent = ent->next)
+               if (fn(ent, cb))
+                       return;
+}
+
 void prepare_alt_odb(void)
 {
        const char *alt;
@@ -2363,51 +2376,22 @@ int has_sha1_file(const unsigned char *sha1)
        return has_loose_object(sha1);
 }
 
-int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object)
-{
-       struct strbuf buf;
-       int ret;
-
-       strbuf_init(&buf, 0);
-       if (strbuf_read(&buf, fd, 4096) < 0) {
-               strbuf_release(&buf);
-               return -1;
-       }
-
-       if (!type)
-               type = blob_type;
-       if (write_object)
-               ret = write_sha1_file(buf.buf, buf.len, type, sha1);
-       else
-               ret = hash_sha1_file(buf.buf, buf.len, type, sha1);
-       strbuf_release(&buf);
-
-       return ret;
-}
-
-int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
-            enum object_type type, const char *path)
+static int index_mem(unsigned char *sha1, void *buf, size_t size,
+                    int write_object, enum object_type type, const char *path)
 {
-       size_t size = xsize_t(st->st_size);
-       void *buf = NULL;
        int ret, re_allocated = 0;
 
-       if (size)
-               buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
-       close(fd);
-
        if (!type)
                type = OBJ_BLOB;
 
        /*
         * Convert blobs to git internal format
         */
-       if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) {
+       if ((type == OBJ_BLOB) && path) {
                struct strbuf nbuf;
                strbuf_init(&nbuf, 0);
                if (convert_to_git(path, buf, size, &nbuf,
                                   write_object ? safe_crlf : 0)) {
-                       munmap(buf, size);
                        buf = strbuf_detach(&nbuf, &size);
                        re_allocated = 1;
                }
@@ -2417,12 +2401,33 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
                ret = write_sha1_file(buf, size, typename(type), sha1);
        else
                ret = hash_sha1_file(buf, size, typename(type), sha1);
-       if (re_allocated) {
+       if (re_allocated)
                free(buf);
-               return ret;
-       }
-       if (size)
+       return ret;
+}
+
+int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
+            enum object_type type, const char *path)
+{
+       int ret;
+       size_t size = xsize_t(st->st_size);
+
+       if (!S_ISREG(st->st_mode)) {
+               struct strbuf sbuf;
+               strbuf_init(&sbuf, 0);
+               if (strbuf_read(&sbuf, fd, 4096) >= 0)
+                       ret = index_mem(sha1, sbuf.buf, sbuf.len, write_object,
+                                       type, path);
+               else
+                       ret = -1;
+               strbuf_release(&sbuf);
+       } else if (size) {
+               void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+               ret = index_mem(sha1, buf, size, write_object, type, path);
                munmap(buf, size);
+       } else
+               ret = index_mem(sha1, NULL, size, write_object, type, path);
+       close(fd);
        return ret;
 }
 
index 4fb77f8863ec075de38b84171d3ef039a00cee4c..41b680915d7348bf622397da8b1465d3769a361a 100644 (file)
@@ -349,7 +349,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                        else
                                nth = -1;
                }
-               if (0 <= nth)
+               if (100000000 <= nth) {
+                       at_time = nth;
+                       nth = -1;
+               } else if (0 <= nth)
                        at_time = 0;
                else {
                        char *tmp = xstrndup(str + at + 2, reflog_len);
index b27e280083867ac03c4abc188f0f37291eb123a0..7dcbb232cd876cb7b976443cc586f60a94ab92bf 100644 (file)
@@ -1,2 +1,2 @@
-/trash directory
+/trash directory*
 /test-results
index 0d65cedaa6566a6dd654753cb574c9ee64b1c90b..ed49c20b16b520da1b460960dbadd5d31c4927ba 100644 (file)
@@ -14,7 +14,8 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
 TSVN = $(wildcard t91[0-9][0-9]-*.sh)
 
-all: pre-clean $(T) aggregate-results clean
+all: pre-clean
+       $(MAKE) aggregate-results-and-cleanup
 
 $(T):
        @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
@@ -25,6 +26,10 @@ pre-clean:
 clean:
        $(RM) -r 'trash directory' test-results
 
+aggregate-results-and-cleanup: $(T)
+       $(MAKE) aggregate-results
+       $(MAKE) clean
+
 aggregate-results:
        '$(SHELL_PATH_SQ)' ./aggregate-results.sh test-results/t*-*
 
@@ -34,4 +39,3 @@ full-svn-test:
        $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8
 
 .PHONY: pre-clean $(T) aggregate-results clean
-.NOTPARALLEL:
index 5b5f288809bd019662cf8af45c277bf61cd6df5c..67c431d4ebbb32fe8d88a83104485b38d746fa62 100644 (file)
@@ -1,8 +1,11 @@
 . ./test-lib.sh
 
+remotes_git_svn=remotes/git""-svn
+git_svn_id=git""-svn-id
+
 if test -n "$NO_SVN_TESTS"
 then
-       test_expect_success 'skipping git-svn tests, NO_SVN_TESTS defined' :
+       test_expect_success 'skipping git svn tests, NO_SVN_TESTS defined' :
        test_done
        exit
 fi
@@ -14,7 +17,7 @@ SVN_TREE=$GIT_SVN_DIR/svn-tree
 svn >/dev/null 2>&1
 if test $? -ne 1
 then
-    test_expect_success 'skipping git-svn tests, svn not found' :
+    test_expect_success 'skipping git svn tests, svn not found' :
     test_done
     exit
 fi
@@ -88,7 +91,7 @@ start_httpd () {
        mkdir "$GIT_DIR"/logs
 
        cat > "$GIT_DIR/httpd.conf" <<EOF
-ServerName "git-svn test"
+ServerName "git svn test"
 ServerRoot "$GIT_DIR"
 DocumentRoot "$GIT_DIR"
 PidFile "$GIT_DIR/httpd.pid"
index dc473dfb53d5ffafee72738a55caf21732fa4fb1..6ac312b9059394b44cd6e106f9da6394674ee54a 100644 (file)
@@ -14,7 +14,7 @@ fi
 LIB_HTTPD_PATH=${LIB_HTTPD_PATH-'/usr/sbin/apache2'}
 LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'8111'}
 
-TEST_PATH="$PWD"/../lib-httpd
+TEST_PATH="$TEST_DIRECTORY"/lib-httpd
 HTTPD_ROOT_PATH="$PWD"/httpd
 HTTPD_DOCUMENT_ROOT_PATH=$HTTPD_ROOT_PATH/www
 
index 7d1ce2d0563b3734d754c171d21580075264bbb2..f1e1d48869a25c6ab1d0b570eb4c0e28afc5fa05 100755 (executable)
@@ -6,13 +6,13 @@ test_description='ignore CR in CRLF sequence while computing similiarity'
 
 test_expect_success setup '
 
-       cat ../t0022-crlf-rename.sh >sample &&
+       cat "$TEST_DIRECTORY"/t0022-crlf-rename.sh >sample &&
        git add sample &&
 
        test_tick &&
        git commit -m Initial &&
 
-       sed -e "s/\$/\r/" ../t0022-crlf-rename.sh >elpmas &&
+       sed -e "s/\$/\r/" "$TEST_DIRECTORY"/t0022-crlf-rename.sh >elpmas &&
        git add elpmas &&
        rm -f sample &&
 
diff --git a/t/t0055-beyond-symlinks.sh b/t/t0055-beyond-symlinks.sh
new file mode 100755 (executable)
index 0000000..b29c37a
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+test_description='update-index and add refuse to add beyond symlinks'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       >a &&
+       mkdir b &&
+       ln -s b c &&
+       >c/d &&
+       git update-index --add a b/d
+'
+
+test_expect_success 'update-index --add beyond symlinks' '
+       test_must_fail git update-index --add c/d &&
+       ! ( git ls-files | grep c/d )
+'
+
+test_expect_success 'add beyond symlinks' '
+       test_must_fail git add c/d &&
+       ! ( git ls-files | grep c/d )
+'
+
+test_done
index 807fb83af8c65304f1dae2ee35ba0f2909ddf465..22ba7a5442c587f4536aad5668df43661231de56 100755 (executable)
@@ -72,7 +72,7 @@ In addition:
 
 '
 . ./test-lib.sh
-. ../lib-read-tree-m-3way.sh
+. "$TEST_DIRECTORY"/lib-read-tree-m-3way.sh
 
 ################################################################
 # Trivial "majority when 3 stages exist" merge plus #2ALT, #3ALT
index 076b08292d9901bd5d47b5ac49216a448ef7ecb9..fd98e445bf2e74284709df54d2cd2d3f0006b19c 100755 (executable)
@@ -49,16 +49,28 @@ setup_repo
 # Argument checking
 
 test_expect_success "multiple '--stdin's are rejected" '
-       test_must_fail git hash-object --stdin --stdin < example
+       echo example | test_must_fail git hash-object --stdin --stdin
 '
 
 test_expect_success "Can't use --stdin and --stdin-paths together" '
-       test_must_fail git hash-object --stdin --stdin-paths &&
-       test_must_fail git hash-object --stdin-paths --stdin
+       echo example | test_must_fail git hash-object --stdin --stdin-paths &&
+       echo example | test_must_fail git hash-object --stdin-paths --stdin
 '
 
 test_expect_success "Can't pass filenames as arguments with --stdin-paths" '
-       test_must_fail git hash-object --stdin-paths hello < example
+       echo example | test_must_fail git hash-object --stdin-paths hello
+'
+
+test_expect_success "Can't use --path with --stdin-paths" '
+       echo example | test_must_fail git hash-object --stdin-paths --path=foo
+'
+
+test_expect_success "Can't use --stdin-paths with --no-filters" '
+       echo example | test_must_fail git hash-object --stdin-paths --no-filters
+'
+
+test_expect_success "Can't use --path with --no-filters" '
+       test_must_fail git hash-object --no-filters --path=foo
 '
 
 # Behavior
@@ -93,6 +105,42 @@ test_expect_success 'git hash-object --stdin file1 <file0 first operates on file
        test "$obname1" = "$obname1new"
 '
 
+test_expect_success 'check that appropriate filter is invoke when --path is used' '
+       echo fooQ | tr Q "\\015" >file0 &&
+       cp file0 file1 &&
+       echo "file0 -crlf" >.gitattributes &&
+       echo "file1 crlf" >>.gitattributes &&
+       git config core.autocrlf true &&
+       file0_sha=$(git hash-object file0) &&
+       file1_sha=$(git hash-object file1) &&
+       test "$file0_sha" != "$file1_sha" &&
+       path1_sha=$(git hash-object --path=file1 file0) &&
+       path0_sha=$(git hash-object --path=file0 file1) &&
+       test "$file0_sha" = "$path0_sha" &&
+       test "$file1_sha" = "$path1_sha" &&
+       path1_sha=$(cat file0 | git hash-object --path=file1 --stdin) &&
+       path0_sha=$(cat file1 | git hash-object --path=file0 --stdin) &&
+       test "$file0_sha" = "$path0_sha" &&
+       test "$file1_sha" = "$path1_sha" &&
+       git config --unset core.autocrlf
+'
+
+test_expect_success 'check that --no-filters option works' '
+       echo fooQ | tr Q "\\015" >file0 &&
+       cp file0 file1 &&
+       echo "file0 -crlf" >.gitattributes &&
+       echo "file1 crlf" >>.gitattributes &&
+       git config core.autocrlf true &&
+       file0_sha=$(git hash-object file0) &&
+       file1_sha=$(git hash-object file1) &&
+       test "$file0_sha" != "$file1_sha" &&
+       nofilters_file1=$(git hash-object --no-filters file1) &&
+       test "$file0_sha" = "$nofilters_file1" &&
+       nofilters_file1=$(cat file1 | git hash-object --stdin) &&
+       test "$file0_sha" = "$nofilters_file1" &&
+       git config --unset core.autocrlf
+'
+
 pop_repo
 
 for args in "-w --stdin" "--stdin -w"; do
diff --git a/t/t3504-cherry-pick-rerere.sh b/t/t3504-cherry-pick-rerere.sh
new file mode 100755 (executable)
index 0000000..f7b3518
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+test_description='cherry-pick should rerere for conflicts'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo foo >foo &&
+       git add foo && test_tick && git commit -q -m 1 &&
+       echo foo-master >foo &&
+       git add foo && test_tick && git commit -q -m 2 &&
+
+       git checkout -b dev HEAD^ &&
+       echo foo-dev >foo &&
+       git add foo && test_tick && git commit -q -m 3 &&
+       git config rerere.enabled true
+'
+
+test_expect_success 'conflicting merge' '
+       test_must_fail git merge master
+'
+
+test_expect_success 'fixup' '
+       echo foo-dev >foo &&
+       git add foo && test_tick && git commit -q -m 4 &&
+       git reset --hard HEAD^
+       echo foo-dev >expect
+'
+
+test_expect_success 'cherry-pick conflict' '
+       test_must_fail git cherry-pick master &&
+       test_cmp expect foo
+'
+
+test_expect_success 'reconfigure' '
+       git config rerere.enabled false
+       git reset --hard
+'
+
+test_expect_success 'cherry-pick conflict without rerere' '
+       test_must_fail git cherry-pick master &&
+       test_must_fail test_cmp expect foo
+'
+
+test_done
index f31353323b92d6422f685ceb544c81760714500f..784c31aec99d90b69186079ddb66350d9f4a8827 100755 (executable)
@@ -16,7 +16,7 @@ test_expect_success setup '
        : >F &&
        git add F &&
        T=$(git write-tree) &&
-       C=$(git commit-tree $T <../t3900/1-UTF-8.txt) &&
+       C=$(git commit-tree $T <"$TEST_DIRECTORY"/t3900/1-UTF-8.txt) &&
        git update-ref HEAD $C &&
        git tag C0
 '
@@ -32,7 +32,7 @@ do
                git config i18n.commitencoding $H &&
                git checkout -b $H C0 &&
                echo $H >F &&
-               git commit -a -F ../t3900/$H.txt
+               git commit -a -F "$TEST_DIRECTORY"/t3900/$H.txt
        '
 done
 
@@ -57,13 +57,13 @@ test_expect_success 'config to remove customization' '
 '
 
 test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' '
-       compare_with ISO-8859-1 ../t3900/1-UTF-8.txt
+       compare_with ISO-8859-1 "$TEST_DIRECTORY"/t3900/1-UTF-8.txt
 '
 
 for H in EUCJP ISO-2022-JP
 do
        test_expect_success "$H should be shown in UTF-8 now" '
-               compare_with '$H' ../t3900/2-UTF-8.txt
+               compare_with '$H' "$TEST_DIRECTORY"/t3900/2-UTF-8.txt
        '
 done
 
@@ -82,7 +82,7 @@ for H in ISO-8859-1 EUCJP ISO-2022-JP
 do
        test_expect_success "$H should be shown in itself now" '
                git config i18n.commitencoding '$H' &&
-               compare_with '$H' ../t3900/'$H'.txt
+               compare_with '$H' "$TEST_DIRECTORY"/t3900/'$H'.txt
        '
 done
 
@@ -91,13 +91,13 @@ test_expect_success 'config to tweak customization' '
 '
 
 test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' '
-       compare_with ISO-8859-1 ../t3900/1-UTF-8.txt
+       compare_with ISO-8859-1 "$TEST_DIRECTORY"/t3900/1-UTF-8.txt
 '
 
 for H in EUCJP ISO-2022-JP
 do
        test_expect_success "$H should be shown in UTF-8 now" '
-               compare_with '$H' ../t3900/2-UTF-8.txt
+               compare_with '$H' "$TEST_DIRECTORY"/t3900/2-UTF-8.txt
        '
 done
 
@@ -107,7 +107,7 @@ do
        for H in EUCJP ISO-2022-JP
        do
                test_expect_success "$H should be shown in $J now" '
-                       compare_with '$H' ../t3900/'$J'.txt
+                       compare_with '$H' "$TEST_DIRECTORY"/t3900/'$J'.txt
                '
        done
 done
@@ -115,7 +115,7 @@ done
 for H in ISO-8859-1 EUCJP ISO-2022-JP
 do
        test_expect_success "No conversion with $H" '
-               compare_with "--encoding=none '$H'" ../t3900/'$H'.txt
+               compare_with "--encoding=none '$H'" "$TEST_DIRECTORY"/t3900/'$H'.txt
        '
 done
 
index 567760e1d25f851ffecbb609d60419d2a601cb31..7655da3f8d5e68f293ae5afe2d58dd41b1396f37 100755 (executable)
@@ -35,7 +35,7 @@ test_expect_success setup '
 
        # use UTF-8 in author and committer name to match the
        # i18n.commitencoding settings
-       . ../t3901-utf8.txt &&
+       . "$TEST_DIRECTORY"/t3901-utf8.txt &&
 
        test_tick &&
        echo "$GIT_AUTHOR_NAME" >mine &&
@@ -57,7 +57,7 @@ test_expect_success setup '
        # the second one on the side branch is ISO-8859-1
        git config i18n.commitencoding ISO-8859-1 &&
        # use author and committer name in ISO-8859-1 to match it.
-       . ../t3901-8859-1.txt &&
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
        test_tick &&
        echo Yet another >theirs &&
        git add theirs &&
@@ -101,7 +101,7 @@ test_expect_success 'rebase (U/U)' '
 
        # The result will be committed by GIT_COMMITTER_NAME --
        # we want UTF-8 encoded name.
-       . ../t3901-utf8.txt &&
+       . "$TEST_DIRECTORY"/t3901-utf8.txt &&
        git checkout -b test &&
        git rebase master &&
 
@@ -111,7 +111,7 @@ test_expect_success 'rebase (U/U)' '
 test_expect_success 'rebase (U/L)' '
        git config i18n.commitencoding UTF-8 &&
        git config i18n.logoutputencoding ISO-8859-1 &&
-       . ../t3901-utf8.txt &&
+       . "$TEST_DIRECTORY"/t3901-utf8.txt &&
 
        git reset --hard side &&
        git rebase master &&
@@ -123,7 +123,7 @@ test_expect_success 'rebase (L/L)' '
        # In this test we want ISO-8859-1 encoded commits as the result
        git config i18n.commitencoding ISO-8859-1 &&
        git config i18n.logoutputencoding ISO-8859-1 &&
-       . ../t3901-8859-1.txt &&
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
 
        git reset --hard side &&
        git rebase master &&
@@ -136,7 +136,7 @@ test_expect_success 'rebase (L/U)' '
        # to get ISO-8859-1 results.
        git config i18n.commitencoding ISO-8859-1 &&
        git config i18n.logoutputencoding UTF-8 &&
-       . ../t3901-8859-1.txt &&
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
 
        git reset --hard side &&
        git rebase master &&
@@ -149,7 +149,7 @@ test_expect_success 'cherry-pick(U/U)' '
 
        git config i18n.commitencoding UTF-8 &&
        git config i18n.logoutputencoding UTF-8 &&
-       . ../t3901-utf8.txt &&
+       . "$TEST_DIRECTORY"/t3901-utf8.txt &&
 
        git reset --hard master &&
        git cherry-pick side^ &&
@@ -164,7 +164,7 @@ test_expect_success 'cherry-pick(L/L)' '
 
        git config i18n.commitencoding ISO-8859-1 &&
        git config i18n.logoutputencoding ISO-8859-1 &&
-       . ../t3901-8859-1.txt &&
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
 
        git reset --hard master &&
        git cherry-pick side^ &&
@@ -179,7 +179,7 @@ test_expect_success 'cherry-pick(U/L)' '
 
        git config i18n.commitencoding UTF-8 &&
        git config i18n.logoutputencoding ISO-8859-1 &&
-       . ../t3901-utf8.txt &&
+       . "$TEST_DIRECTORY"/t3901-utf8.txt &&
 
        git reset --hard master &&
        git cherry-pick side^ &&
@@ -195,7 +195,7 @@ test_expect_success 'cherry-pick(L/U)' '
 
        git config i18n.commitencoding ISO-8859-1 &&
        git config i18n.logoutputencoding UTF-8 &&
-       . ../t3901-8859-1.txt &&
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
 
        git reset --hard master &&
        git cherry-pick side^ &&
@@ -208,7 +208,7 @@ test_expect_success 'cherry-pick(L/U)' '
 test_expect_success 'rebase --merge (U/U)' '
        git config i18n.commitencoding UTF-8 &&
        git config i18n.logoutputencoding UTF-8 &&
-       . ../t3901-utf8.txt &&
+       . "$TEST_DIRECTORY"/t3901-utf8.txt &&
 
        git reset --hard side &&
        git rebase --merge master &&
@@ -219,7 +219,7 @@ test_expect_success 'rebase --merge (U/U)' '
 test_expect_success 'rebase --merge (U/L)' '
        git config i18n.commitencoding UTF-8 &&
        git config i18n.logoutputencoding ISO-8859-1 &&
-       . ../t3901-utf8.txt &&
+       . "$TEST_DIRECTORY"/t3901-utf8.txt &&
 
        git reset --hard side &&
        git rebase --merge master &&
@@ -231,7 +231,7 @@ test_expect_success 'rebase --merge (L/L)' '
        # In this test we want ISO-8859-1 encoded commits as the result
        git config i18n.commitencoding ISO-8859-1 &&
        git config i18n.logoutputencoding ISO-8859-1 &&
-       . ../t3901-8859-1.txt &&
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
 
        git reset --hard side &&
        git rebase --merge master &&
@@ -244,7 +244,7 @@ test_expect_success 'rebase --merge (L/U)' '
        # to get ISO-8859-1 results.
        git config i18n.commitencoding ISO-8859-1 &&
        git config i18n.logoutputencoding UTF-8 &&
-       . ../t3901-8859-1.txt &&
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
 
        git reset --hard side &&
        git rebase --merge master &&
index c44b27aeb24816a346f0aa84d70546a0ffd83b2a..6ddd46915d2757bb5a40057e6850a4f72cd4dafb 100755 (executable)
@@ -7,7 +7,7 @@ test_description='Test built-in diff output engine.
 
 '
 . ./test-lib.sh
-. ../diff-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
 
 echo >path0 'Line 1
 Line 2
index a32692417db73444dbdc143e6908b7371be79d42..71bac83dd5e42a19e3b1a7e869df0e7143371c99 100755 (executable)
@@ -7,7 +7,7 @@ test_description='Test rename detection in diff engine.
 
 '
 . ./test-lib.sh
-. ../diff-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
 
 echo >path0 'Line 1
 Line 2
index a4cfde6b2927a4655f582d7e92dad4568f7b5f89..cc3681f16118ca70ae9f65a27ccd6f354a6deee1 100755 (executable)
@@ -7,7 +7,7 @@ test_description='Test diff raw-output.
 
 '
 . ./test-lib.sh
-. ../lib-read-tree-m-3way.sh
+. "$TEST_DIRECTORY"/lib-read-tree-m-3way.sh
 
 cat >.test-plain-OA <<\EOF
 :000000 100644 0000000000000000000000000000000000000000 ccba72ad3888a3520b39efcf780b9ee64167535d A     AA
@@ -168,6 +168,20 @@ test_expect_success \
     'git diff-tree -r $tree_A $tree_B >.test-a &&
      cmp -s .test-a .test-recursive-AB'
 
+test_expect_success \
+    'diff-tree --stdin of known trees.' \
+    'echo $tree_A $tree_B | git diff-tree --stdin > .test-a &&
+     echo $tree_A $tree_B > .test-plain-ABx &&
+     cat .test-plain-AB >> .test-plain-ABx &&
+     cmp -s .test-a .test-plain-ABx'
+
+test_expect_success \
+    'diff-tree --stdin of known trees.' \
+    'echo $tree_A $tree_B | git diff-tree -r --stdin > .test-a &&
+     echo $tree_A $tree_B > .test-recursive-ABx &&
+     cat .test-recursive-AB >> .test-recursive-ABx &&
+     cmp -s .test-a .test-recursive-ABx'
+
 test_expect_success \
     'diff-cache O with A in cache' \
     'git read-tree $tree_A &&
index 8b1f875286b25b5fc1a527b5b0281f866724a82d..c6130c40198ad5ed5ec8a0342341a4ec5cc49d7d 100755 (executable)
@@ -7,11 +7,11 @@ test_description='More rename detection
 
 '
 . ./test-lib.sh
-. ../diff-lib.sh ;# test-lib chdir's into trash
+. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
 
 test_expect_success \
     'prepare reference tree' \
-    'cat ../../COPYING >COPYING &&
+    'cat "$TEST_DIRECTORY"/../COPYING >COPYING &&
      echo frotz >rezrov &&
     git update-index --add COPYING rezrov &&
     tree=$(git write-tree) &&
@@ -99,7 +99,7 @@ test_expect_success \
 
 test_expect_success \
     'prepare work tree once again' \
-    'cat ../../COPYING >COPYING &&
+    'cat "$TEST_DIRECTORY"/../COPYING >COPYING &&
      git update-index --add --remove COPYING COPYING.1'
 
 # tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
index 3d25be7a6709cdd23e0d583a8f1a3e19a3927cd8..b35af9b42d318904bd12649562be309fd49977a3 100755 (executable)
@@ -10,7 +10,7 @@ copy of symbolic links, but should not produce rename/copy followed
 by an edit for them.
 '
 . ./test-lib.sh
-. ../diff-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
 
 test_expect_success \
     'prepare reference tree' \
index 66300173124eee8480e7214322faf8bc493ad940..1ba359d478e3d3491aed5f622932382767c8f4dc 100755 (executable)
@@ -7,11 +7,11 @@ test_description='Same rename detection as t4003 but testing diff-raw.
 
 '
 . ./test-lib.sh
-. ../diff-lib.sh ;# test-lib chdir's into trash
+. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
 
 test_expect_success \
     'prepare reference tree' \
-    'cat ../../COPYING >COPYING &&
+    'cat "$TEST_DIRECTORY"/../COPYING >COPYING &&
      echo frotz >rezrov &&
     git update-index --add COPYING rezrov &&
     tree=$(git write-tree) &&
@@ -71,7 +71,7 @@ test_expect_success \
 
 test_expect_success \
     'prepare work tree once again' \
-    'cat ../../COPYING >COPYING &&
+    'cat "$TEST_DIRECTORY"/../COPYING >COPYING &&
      git update-index --add --remove COPYING COPYING.1'
 
 git diff-index -C --find-copies-harder $tree >current
index 104a4e1492b65a64d061e1ce1df0b0a2a49fb20e..42072d724ef67c51e6a9adcf9bf8e3ca1757e053 100755 (executable)
@@ -7,12 +7,12 @@ test_description='Rename interaction with pathspec.
 
 '
 . ./test-lib.sh
-. ../diff-lib.sh ;# test-lib chdir's into trash
+. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
 
 test_expect_success \
     'prepare reference tree' \
     'mkdir path0 path1 &&
-     cp ../../COPYING path0/COPYING &&
+     cp "$TEST_DIRECTORY"/../COPYING path0/COPYING &&
      git update-index --add path0/COPYING &&
     tree=$(git write-tree) &&
     echo $tree'
index 26c2e4aa65c539c527ae8e2d71b5abb40c21fbbd..7e343a9cd130a73547ed1df9a4d9cd364e60bf9f 100755 (executable)
@@ -22,12 +22,12 @@ four changes in total.
 Further, with -B and -M together, these should turn into two renames.
 '
 . ./test-lib.sh
-. ../diff-lib.sh ;# test-lib chdir's into trash
+. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
 
 test_expect_success \
     setup \
-    'cat ../../README >file0 &&
-     cat ../../COPYING >file1 &&
+    'cat "$TEST_DIRECTORY"/../README >file0 &&
+     cat "$TEST_DIRECTORY"/../COPYING >file1 &&
     git update-index --add file0 file1 &&
     tree=$(git write-tree) &&
     echo "$tree"'
index d2b45e7b8fb3902cb740e0df582f92195a295f24..de3f17478efcaf008340a7ab81cb049f9a9e9a3a 100755 (executable)
@@ -7,11 +7,11 @@ test_description='Same rename detection as t4003 but testing diff-raw -z.
 
 '
 . ./test-lib.sh
-. ../diff-lib.sh ;# test-lib chdir's into trash
+. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
 
 test_expect_success \
     'prepare reference tree' \
-    'cat ../../COPYING >COPYING &&
+    'cat "$TEST_DIRECTORY"/../COPYING >COPYING &&
      echo frotz >rezrov &&
     git update-index --add COPYING rezrov &&
     tree=$(git write-tree) &&
@@ -78,7 +78,7 @@ test_expect_success \
 
 test_expect_success \
     'prepare work tree once again' \
-    'cat ../../COPYING >COPYING &&
+    'cat "$TEST_DIRECTORY"/../COPYING >COPYING &&
      git update-index --add --remove COPYING COPYING.1'
 
 git diff-index -z -C --find-copies-harder $tree >current
index ad3d9e48454d2e72afce682df009cdaaee9ba3c5..9322298ecc6cb79f0123dea4e329317c5bfbfd11 100755 (executable)
@@ -10,7 +10,7 @@ Prepare:
         path1/file1
 '
 . ./test-lib.sh
-. ../diff-lib.sh ;# test-lib chdir's into trash
+. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
 
 test_expect_success \
     setup \
index c6d13693ba45b594704c2ef55d40540ee3f9c25f..02efecae3ad06e5a62553e990fc0934dd0c65eab 100755 (executable)
@@ -7,7 +7,7 @@ test_description='Test diff of symlinks.
 
 '
 . ./test-lib.sh
-. ../diff-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
 
 cat > expected << EOF
 diff --git a/frotz b/frotz
index 64c372a025bd78afc3d3a2ce13543561f9c97706..b8ec6e90afb970b1b2540e30901b2f058522544e 100755 (executable)
@@ -12,7 +12,7 @@ test_expect_success 'prepare repository' \
        'echo AIT >a && echo BIT >b && echo CIT >c && echo DIT >d &&
         git update-index --add a b c d &&
         echo git >a &&
-        cat ../test4012.png >b &&
+        cat "$TEST_DIRECTORY"/test4012.png >b &&
         echo git >c &&
         cat b b >d'
 
index 9337b81064bbdbe4e7f590830b458c48226c4a17..1a6b52234d8df70e5ddcb28c0ddde582d8310d43 100755 (executable)
@@ -99,7 +99,7 @@ do
        test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'`
        cnt=`expr $test_count + 1`
        pfx=`printf "%04d" $cnt`
-       expect="../t4013/diff.$test"
+       expect="$TEST_DIRECTORY/t4013/diff.$test"
        actual="$pfx-diff.$test"
 
        test_expect_success "git $cmd" '
index 7fe853c20d1111e40371a3796d82bb8485f5ebcf..9d99dc28879d4f7f35001e0785f97f319fe13b40 100755 (executable)
@@ -230,4 +230,29 @@ test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
 
 '
 
+cat > expect << EOF
+---
+ file |   16 ++++++++++++++++
+ 1 files changed, 16 insertions(+), 0 deletions(-)
+
+diff --git a/file b/file
+index 40f36c6..2dc5c23 100644
+--- a/file
++++ b/file
+@@ -13,4 +13,20 @@ C
+ 10
+ D
+ E
+ F
++5
+EOF
+
+test_expect_success 'format-patch respects -U' '
+
+       git format-patch -U4 -2 &&
+       sed -e "1,/^$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+       test_cmp expect output
+
+'
+
 test_done
index b1cbd36d1710a38b94838a2fdf08e0e5ded431f8..fc2307eaa3b4e8b1481fd7aa5ae0f93d085338af 100755 (executable)
@@ -7,7 +7,7 @@ test_description='Test special whitespace in diff engine.
 
 '
 . ./test-lib.sh
-. ../diff-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
 
 # Ray Lehtiniemi's example
 
index 637b4e19d52e81cf1472a4ed9dcfb0c9ff00da2b..dfe3fbc74b96fff50160c032f0a65ba54f9ad1b2 100755 (executable)
@@ -104,7 +104,7 @@ echo NULZbetweenZwords | perl -pe 'y/Z/\000/' > file
 test_expect_success 'force diff with "diff"' '
        echo >.gitattributes "file diff" &&
        git diff >actual &&
-       test_cmp ../t4020/diff.NUL actual
+       test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual
 '
 
 test_done
index bf996fc414d3b5ea16488a8b274973a8347b29cb..2a537a21e8e6793e6ffa2fe1522e30f877b209fe 100755 (executable)
@@ -6,12 +6,12 @@ test_description='rewrite diff'
 
 test_expect_success setup '
 
-       cat ../../COPYING >test &&
+       cat "$TEST_DIRECTORY"/../COPYING >test &&
        git add test &&
        tr \
          "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" \
          "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM" \
-         <../../COPYING >test
+         <"$TEST_DIRECTORY"/../COPYING >test
 
 '
 
index 4dbfc6e8b751a6c93b1f9dfee8ce649235c98c93..297ddb5a25b682412851aed70370771aa5a15976 100755 (executable)
@@ -7,21 +7,21 @@ test_description='typechange rename detection'
 test_expect_success setup '
 
        rm -f foo bar &&
-       cat ../../COPYING >foo &&
+       cat "$TEST_DIRECTORY"/../COPYING >foo &&
        ln -s linklink bar &&
        git add foo bar &&
        git commit -a -m Initial &&
        git tag one &&
 
        rm -f foo bar &&
-       cat ../../COPYING >bar &&
+       cat "$TEST_DIRECTORY"/../COPYING >bar &&
        ln -s linklink foo &&
        git add foo bar &&
        git commit -a -m Second &&
        git tag two &&
 
        rm -f foo bar &&
-       cat ../../COPYING >foo &&
+       cat "$TEST_DIRECTORY"/../COPYING >foo &&
        git add foo &&
        git commit -a -m Third &&
        git tag three &&
@@ -35,15 +35,15 @@ test_expect_success setup '
        # This is purely for sanity check
 
        rm -f foo bar &&
-       cat ../../COPYING >foo &&
-       cat ../../Makefile >bar &&
+       cat "$TEST_DIRECTORY"/../COPYING >foo &&
+       cat "$TEST_DIRECTORY"/../Makefile >bar &&
        git add foo bar &&
        git commit -a -m Fifth &&
        git tag five &&
 
        rm -f foo bar &&
-       cat ../../Makefile >foo &&
-       cat ../../COPYING >bar &&
+       cat "$TEST_DIRECTORY"/../Makefile >foo &&
+       cat "$TEST_DIRECTORY"/../COPYING >bar &&
        git add foo bar &&
        git commit -a -m Sixth &&
        git tag six
index ba6679c6e4032bb12e4206226f95770946ece8cc..1c2edebb09bd6e1d7581365011c8500da57942c5 100755 (executable)
@@ -3,7 +3,7 @@
 test_description='difference in submodules'
 
 . ./test-lib.sh
-. ../diff-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
 
 _z40=0000000000000000000000000000000000000000
 test_expect_success setup '
diff --git a/t/t4029-diff-trailing-space.sh b/t/t4029-diff-trailing-space.sh
new file mode 100755 (executable)
index 0000000..4ca65e0
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/bash
+#
+# Copyright (c) Jim Meyering
+#
+test_description='diff honors config option, diff.suppress-blank-empty'
+
+. ./test-lib.sh
+
+cat <<\EOF > exp ||
+diff --git a/f b/f
+index 5f6a263..8cb8bae 100644
+--- a/f
++++ b/f
+@@ -1,2 +1,2 @@
+
+-x
++y
+EOF
+exit 1
+
+test_expect_success \
+    "$test_description" \
+    'printf "\nx\n" > f &&
+     git add f &&
+     git commit -q -m. f &&
+     printf "\ny\n" > f &&
+     git config --bool diff.suppress-blank-empty true &&
+     git diff f > actual &&
+     test_cmp exp actual &&
+     perl -i.bak -p -e "s/^\$/ /" exp &&
+     git config --bool diff.suppress-blank-empty false &&
+     git diff f > actual &&
+     test_cmp exp actual &&
+     git config --bool --unset diff.suppress-blank-empty &&
+     git diff f > actual &&
+     test_cmp exp actual
+     '
+
+test_done
index e0c67740a5ef85aaa3edc9a4514da72c92ce30ec..9b433de83630774206fb89dfae1a4396264390cc 100755 (executable)
@@ -17,13 +17,13 @@ do
        test_expect_success "$title" '
                git apply --stat --summary \
                        <"$TEST_DIRECTORY/t4100/t-apply-$num.patch" >current &&
-               test_cmp ../t4100/t-apply-$num.expect current
+               test_cmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
        '
 
        test_expect_success "$title with recount" '
                sed -e "$UNC" <"$TEST_DIRECTORY/t4100/t-apply-$num.patch" |
                git apply --recount --stat --summary >current &&
-               test_cmp ../t4100/t-apply-$num.expect current
+               test_cmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
        '
 done <<\EOF
 rename
index da8abcf36418dbd2e9d8ec85871c245991f96fda..e3443d004d026c86fd783cb8e6e3d03f22676778 100755 (executable)
@@ -21,9 +21,10 @@ do
   do
     test $i -eq $j && continue
     cat frotz.$i >frotz
-    test_expect_success \
-        "apply diff between $i and $j" \
-       "git apply <../t4101/diff.$i-$j && diff frotz.$j frotz"
+    test_expect_success "apply diff between $i and $j" '
+       git apply <"$TEST_DIRECTORY"/t4101/diff.$i-$j &&
+       test_cmp frotz.$j frotz
+    '
   done
 done
 
index 198e3503d53c109e5090e213914ee23360c06902..fe14589427643b9bb7759c597935da724adf1a64 100755 (executable)
@@ -8,27 +8,28 @@ test_description='git mailinfo and git mailsplit test'
 . ./test-lib.sh
 
 test_expect_success 'split sample box' \
-       'git mailsplit -o. ../t5100/sample.mbox >last &&
+       'git mailsplit -o. "$TEST_DIRECTORY"/t5100/sample.mbox >last &&
        last=`cat last` &&
        echo total is $last &&
        test `cat last` = 11'
 
 for mail in `echo 00*`
 do
-       test_expect_success "mailinfo $mail" \
-               "git mailinfo -u msg$mail patch$mail <$mail >info$mail &&
+       test_expect_success "mailinfo $mail" '
+               git mailinfo -u msg$mail patch$mail <$mail >info$mail &&
                echo msg &&
-               diff ../t5100/msg$mail msg$mail &&
+               test_cmp "$TEST_DIRECTORY"/t5100/msg$mail msg$mail &&
                echo patch &&
-               diff ../t5100/patch$mail patch$mail &&
+               test_cmp "$TEST_DIRECTORY"/t5100/patch$mail patch$mail &&
                echo info &&
-               diff ../t5100/info$mail info$mail"
+               test_cmp "$TEST_DIRECTORY"/t5100/info$mail info$mail
+       '
 done
 
 test_expect_success 'respect NULs' '
 
-       git mailsplit -d3 -o. ../t5100/nul-plain &&
-       cmp ../t5100/nul-plain 001 &&
+       git mailsplit -d3 -o. "$TEST_DIRECTORY"/t5100/nul-plain &&
+       test_cmp "$TEST_DIRECTORY"/t5100/nul-plain 001 &&
        (cat 001 | git mailinfo msg patch) &&
        test 4 = $(wc -l < patch)
 
@@ -36,10 +37,10 @@ test_expect_success 'respect NULs' '
 
 test_expect_success 'Preserve NULs out of MIME encoded message' '
 
-       git mailsplit -d5 -o. ../t5100/nul-b64.in &&
-       cmp ../t5100/nul-b64.in 00001 &&
+       git mailsplit -d5 -o. "$TEST_DIRECTORY"/t5100/nul-b64.in &&
+       test_cmp "$TEST_DIRECTORY"/t5100/nul-b64.in 00001 &&
        git mailinfo msg patch <00001 &&
-       cmp ../t5100/nul-b64.expect patch
+       test_cmp "$TEST_DIRECTORY"/t5100/nul-b64.expect patch
 
 '
 
index 3a0ef8759c9d7a55b95c56ca38cd3c37ac2432fa..b335c6b42de59b9632ff29693c2d7c75dae9794a 100755 (executable)
@@ -272,7 +272,8 @@ test_expect_success \
 
 test_expect_success \
     'make sure index-pack detects the SHA1 collision' \
-    'test_must_fail git index-pack -o bad.idx test-3.pack'
+    'test_must_fail git index-pack -o bad.idx test-3.pack 2>msg &&
+     grep "SHA1 COLLISION FOUND" msg'
 
 test_expect_success \
     'honor pack.packSizeLimit' \
index 448ec7156585a22f61943041dd8381f2cb7ec5d1..c450f33f333e6f1c367f8f350dfd78f8f44a0fee 100755 (executable)
@@ -137,7 +137,7 @@ test_expect_success "clone shallow object count" \
        "test \"in-pack: 18\" = \"$(grep in-pack count.shallow)\""
 
 count_output () {
-       sed -e '/^in-pack:/d' -e '/^packs:/d' -e '/: 0$/d' "$1"
+       sed -e '/^in-pack:/d' -e '/^packs:/d' -e '/^size-pack:/d' -e '/: 0$/d' "$1"
 }
 
 test_expect_success "clone shallow object count (part 2)" '
index de26c203270522983f9ffae4e0bde64a61898567..9aae4965dac0f2bf4bb8684c4b8d43dcb60529e9 100755 (executable)
@@ -303,4 +303,24 @@ test_expect_success 'pushing nonexistent branch by mistake should not segv' '
 
 '
 
+test_expect_success 'auto tag following fetches minimum' '
+
+       cd "$D" &&
+       git clone .git follow &&
+       git checkout HEAD^0 &&
+       (
+               for i in 1 2 3 4 5 6 7
+               do
+                       echo $i >>file &&
+                       git commit -m $i -a &&
+                       git tag -a -m $i excess-$i || exit 1
+               done
+       ) &&
+       git checkout master &&
+       (
+               cd follow &&
+               git fetch
+       )
+'
+
 test_done
index 8becbc3f38fde02371ebbcd9a39a320a1c00c290..1f4608d8ba4748a2bd5c7a3d5a75a04364e8f646 100755 (executable)
@@ -131,9 +131,9 @@ do
        test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'`
        cnt=`expr $test_count + 1`
        pfx=`printf "%04d" $cnt`
-       expect_f="../../t5515/fetch.$test"
+       expect_f="$TEST_DIRECTORY/t5515/fetch.$test"
        actual_f="$pfx-fetch.$test"
-       expect_r="../../t5515/refs.$test"
+       expect_r="$TEST_DIRECTORY/t5515/refs.$test"
        actual_r="$pfx-refs.$test"
 
        test_expect_success "$cmd" '
index b0d242e3edc13dd7cad8f5fc4b5d2d205e620190..da9588645cd9d0054440e5ed3ba14f630c44f506 100755 (executable)
@@ -19,7 +19,7 @@ then
        exit
 fi
 
-. ../lib-httpd.sh
+. "$TEST_DIRECTORY"/lib-httpd.sh
 
 if ! start_httpd >&3 2>&4
 then
index 8f5de097ecd703ae5f6f889ecb735f7277f361be..b4e8fbaa5e6f2a56094c05ca505630669a51e101 100755 (executable)
@@ -5,7 +5,7 @@
 test_description='Tests git rev-list --bisect functionality'
 
 . ./test-lib.sh
-. ../t6000lib.sh # t6xxx specific functions
+. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions
 
 # usage: test_bisection max-diff bisect-option head ^prune...
 #
index 5daa0be8cc856bff513905bc6583854e0b5ae53a..2c73f2da7b0a1f560bfd41376b587d1c91b18615 100755 (executable)
@@ -6,7 +6,7 @@
 test_description='Tests git rev-list --topo-order functionality'
 
 . ./test-lib.sh
-. ../t6000lib.sh # t6xxx specific functions
+. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions
 
 list_duplicates()
 {
index b6e57b2426728cce308a57315247cd2a66cabf4a..04e4b7c5c2aa4f7c6922ebc5c9236962ab5d175c 100755 (executable)
@@ -108,4 +108,52 @@ test_expect_success 'compute merge-base (all)' \
     'MB=$(git merge-base --all PL PR) &&
      expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/C2"'
 
+# Another set to demonstrate base between one commit and a merge
+# in the documentation.
+
+test_expect_success 'merge-base for octopus-step (setup)' '
+       test_tick && git commit --allow-empty -m root && git tag MMR &&
+       test_tick && git commit --allow-empty -m 1 && git tag MM1 &&
+       test_tick && git commit --allow-empty -m o &&
+       test_tick && git commit --allow-empty -m o &&
+       test_tick && git commit --allow-empty -m o &&
+       test_tick && git commit --allow-empty -m A && git tag MMA &&
+       git checkout MM1 &&
+       test_tick && git commit --allow-empty -m o &&
+       test_tick && git commit --allow-empty -m o &&
+       test_tick && git commit --allow-empty -m o &&
+       test_tick && git commit --allow-empty -m B && git tag MMB &&
+       git checkout MMR &&
+       test_tick && git commit --allow-empty -m o &&
+       test_tick && git commit --allow-empty -m o &&
+       test_tick && git commit --allow-empty -m o &&
+       test_tick && git commit --allow-empty -m o &&
+       test_tick && git commit --allow-empty -m C && git tag MMC
+'
+
+test_expect_success 'merge-base A B C' '
+       MB=$(git merge-base --all MMA MMB MMC) &&
+       MM1=$(git rev-parse --verify MM1) &&
+       test "$MM1" = "$MB"
+'
+
+test_expect_success 'criss-cross merge-base for octopus-step (setup)' '
+       git reset --hard MMR &&
+       test_tick && git commit --allow-empty -m 1 && git tag CC1 &&
+       git reset --hard E &&
+       test_tick && git commit --allow-empty -m 2 && git tag CC2 &&
+       test_tick && git merge -s ours CC1 &&
+       test_tick && git commit --allow-empty -m o &&
+       test_tick && git commit --allow-empty -m B && git tag CCB &&
+       git reset --hard CC1 &&
+       test_tick && git merge -s ours CC2 &&
+       test_tick && git commit --allow-empty -m A && git tag CCA
+'
+
+test_expect_success 'merge-base B A^^ A^^2' '
+       MB0=$(git merge-base --all CCB CCA^^ CCA^^2 | sort) &&
+       MB1=$(git rev-parse CC1 CC2 | sort) &&
+       test "$MB0" = "$MB1"
+'
+
 test_done
diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh
new file mode 100755 (executable)
index 0000000..510bb96
--- /dev/null
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+test_description='merge simplification'
+
+. ./test-lib.sh
+
+note () {
+       git tag "$1"
+}
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+
+unnote () {
+       git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\)) |\1 |g"
+}
+
+test_expect_success setup '
+       echo "Hi there" >file &&
+       git add file &&
+       test_tick && git commit -m "Initial file" &&
+       note A &&
+
+       git branch other-branch &&
+
+       echo "Hello" >file &&
+       git add file &&
+       test_tick && git commit -m "Modified file" &&
+       note B &&
+
+       git checkout other-branch &&
+
+       echo "Hello" >file &&
+       git add file &&
+       test_tick && git commit -m "Modified the file identically" &&
+       note C &&
+
+       echo "This is a stupid example" >another-file &&
+       git add another-file &&
+       test_tick && git commit -m "Add another file" &&
+       note D &&
+
+       test_tick && git merge -m "merge" master &&
+       note E &&
+
+       echo "Yet another" >elif &&
+       git add elif &&
+       test_tick && git commit -m "Irrelevant change" &&
+       note F &&
+
+       git checkout master &&
+       echo "Yet another" >elif &&
+       git add elif &&
+       test_tick && git commit -m "Another irrelevant change" &&
+       note G &&
+
+       test_tick && git merge -m "merge" other-branch &&
+       note H &&
+
+       echo "Final change" >file &&
+       test_tick && git commit -a -m "Final change" &&
+       note I
+'
+
+FMT='tformat:%P        %H | %s'
+
+check_result () {
+       for c in $1
+       do
+               echo "$c"
+       done >expect &&
+       shift &&
+       param="$*" &&
+       test_expect_success "log $param" '
+               git log --pretty="$FMT" --parents $param |
+               unnote >actual &&
+               sed -e "s/^.*   \([^ ]*\) .*/\1/" >check <actual &&
+               test_cmp expect check || {
+                       cat actual
+                       false
+               }
+       '
+}
+
+check_result 'I H G F E D C B A' --full-history
+check_result 'I H E C B A' --full-history -- file
+check_result 'I H E C B A' --full-history --topo-order -- file
+check_result 'I H E C B A' --full-history --date-order -- file
+check_result 'I E C B A' --simplify-merges -- file
+check_result 'I B A' -- file
+check_result 'I B A' --topo-order -- file
+
+test_done
diff --git a/t/t6013-rev-list-reverse-parents.sh b/t/t6013-rev-list-reverse-parents.sh
new file mode 100755 (executable)
index 0000000..59fc2f0
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='--reverse combines with --parents'
+
+. ./test-lib.sh
+
+
+commit () {
+       test_tick &&
+       echo $1 > foo &&
+       git add foo &&
+       git commit -m "$1"
+}
+
+test_expect_success 'set up --reverse example' '
+       commit one &&
+       git tag root &&
+       commit two &&
+       git checkout -b side HEAD^ &&
+       commit three &&
+       git checkout master &&
+       git merge -s ours side &&
+       commit five
+       '
+
+test_expect_success '--reverse --parents --full-history combines correctly' '
+       git rev-list --parents --full-history master -- foo |
+               perl -e "print reverse <>" > expected &&
+       git rev-list --reverse --parents --full-history master -- foo \
+               > actual &&
+       test_cmp actual expected
+       '
+
+test_expect_success '--boundary does too' '
+       git rev-list --boundary --parents --full-history master ^root -- foo |
+               perl -e "print reverse <>" > expected &&
+       git rev-list --boundary --reverse --parents --full-history \
+               master ^root -- foo > actual &&
+       test_cmp actual expected
+       '
+
+test_done
index f674c48cab3e80d63b5a5831c667b8e08b542905..5e18d68c97dc52537365adce5c3c84d291fe20be 100755 (executable)
@@ -136,7 +136,7 @@ test_expect_success "expected conflict markers" "test_cmp expect out"
 
 test_expect_success 'binary files cannot be merged' '
        test_must_fail git merge-file -p \
-               orig.txt ../test4012.png new1.txt 2> merge.err &&
+               orig.txt "$TEST_DIRECTORY"/test4012.png new1.txt 2> merge.err &&
        grep "Cannot merge binary files" merge.err
 '
 
@@ -150,8 +150,8 @@ test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' '
 
 '
 
-sed -e 's/deerit./&\n\n\n\n/' -e "s/locavit,/locavit;/" < new6.txt > new8.txt
-sed -e 's/deerit./&\n\n\n\n/' -e "s/locavit,/locavit --/" < new7.txt > new9.txt
+sed -e 's/deerit./&%%%%/' -e "s/locavit,/locavit;/"< new6.txt | tr '%' '\012' > new8.txt
+sed -e 's/deerit./&%%%%/' -e "s/locavit,/locavit --/" < new7.txt | tr '%' '\012' > new9.txt
 
 test_expect_success 'ZEALOUS_ALNUM' '
 
index 4b423e937dbd259e5b2311051907a54309e0edb9..1ba0a252230a283a6bb2463d98537aab1eaf4fb8 100755 (executable)
@@ -142,4 +142,26 @@ test_expect_success 'custom merge backend' '
        rm -f $o $a $b
 '
 
+test_expect_success 'up-to-date merge without common ancestor' '
+       test_create_repo repo1 &&
+       test_create_repo repo2 &&
+       test_tick &&
+       (
+               cd repo1 &&
+               >a &&
+               git add a &&
+               git commit -m initial
+       ) &&
+       test_tick &&
+       (
+               cd repo2 &&
+               git commit --allow-empty -m initial
+       ) &&
+       test_tick &&
+       (
+               cd repo1 &&
+               git pull ../repo2 master
+       )
+'
+
 test_done
index 92ca1f0f8ccabe6f01159ea3e4a73683387ec4a3..b519626ca09255a433b0d1cb32780a6feafa09f6 100755 (executable)
@@ -6,7 +6,7 @@ test_description='ask merge-recursive to merge binary files'
 
 test_expect_success setup '
 
-       cat ../test4012.png >m &&
+       cat "$TEST_DIRECTORY"/test4012.png >m &&
        git add m &&
        git ls-files -s | sed -e "s/ 0  / 1     /" >E1 &&
        test_tick &&
index 36c9a6965f2b200f712ae5a6ae008ca1ef5007a0..85fa39cf0b80d6e3d12a3894f752b9e094345958 100755 (executable)
@@ -350,6 +350,120 @@ test_expect_success 'bisect does not create a "bisect" branch' '
        git branch -D bisect
 '
 
+# This creates a "side" branch to test "siblings" cases.
+#
+# H1-H2-H3-H4-H5-H6-H7  <--other
+#            \
+#             S5-S6-S7  <--side
+#
+test_expect_success 'side branch creation' '
+       git bisect reset &&
+       git checkout -b side $HASH4 &&
+       add_line_into_file "5(side): first line on a side branch" hello2 &&
+       SIDE_HASH5=$(git rev-parse --verify HEAD) &&
+       add_line_into_file "6(side): second line on a side branch" hello2 &&
+       SIDE_HASH6=$(git rev-parse --verify HEAD) &&
+       add_line_into_file "7(side): third line on a side branch" hello2 &&
+       SIDE_HASH7=$(git rev-parse --verify HEAD)
+'
+
+test_expect_success 'good merge base when good and bad are siblings' '
+       git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt &&
+       grep "merge base must be tested" my_bisect_log.txt &&
+       grep $HASH4 my_bisect_log.txt &&
+       git bisect good > my_bisect_log.txt &&
+       test_must_fail grep "merge base must be tested" my_bisect_log.txt &&
+       grep $HASH6 my_bisect_log.txt &&
+       git bisect reset
+'
+test_expect_success 'skipped merge base when good and bad are siblings' '
+       git bisect start "$SIDE_HASH7" "$HASH7" > my_bisect_log.txt &&
+       grep "merge base must be tested" my_bisect_log.txt &&
+       grep $HASH4 my_bisect_log.txt &&
+       git bisect skip > my_bisect_log.txt 2>&1 &&
+       grep "Warning" my_bisect_log.txt &&
+       grep $SIDE_HASH6 my_bisect_log.txt &&
+       git bisect reset
+'
+
+test_expect_success 'bad merge base when good and bad are siblings' '
+       git bisect start "$HASH7" HEAD > my_bisect_log.txt &&
+       grep "merge base must be tested" my_bisect_log.txt &&
+       grep $HASH4 my_bisect_log.txt &&
+       test_must_fail git bisect bad > my_bisect_log.txt 2>&1 &&
+       grep "merge base $HASH4 is bad" my_bisect_log.txt &&
+       grep "fixed between $HASH4 and \[$SIDE_HASH7\]" my_bisect_log.txt &&
+       git bisect reset
+'
+
+# This creates a few more commits (A and B) to test "siblings" cases
+# when a good and a bad rev have many merge bases.
+#
+# We should have the following:
+#
+# H1-H2-H3-H4-H5-H6-H7
+#            \  \     \
+#             S5-A     \
+#              \        \
+#               S6-S7----B
+#
+# And there A and B have 2 merge bases (S5 and H5) that should be
+# reported by "git merge-base --all A B".
+#
+test_expect_success 'many merge bases creation' '
+       git checkout "$SIDE_HASH5" &&
+       git merge -m "merge HASH5 and SIDE_HASH5" "$HASH5" &&
+       A_HASH=$(git rev-parse --verify HEAD) &&
+       git checkout side &&
+       git merge -m "merge HASH7 and SIDE_HASH7" "$HASH7" &&
+       B_HASH=$(git rev-parse --verify HEAD) &&
+       git merge-base --all "$A_HASH" "$B_HASH" > merge_bases.txt &&
+       test $(wc -l < merge_bases.txt) = "2" &&
+       grep "$HASH5" merge_bases.txt &&
+       grep "$SIDE_HASH5" merge_bases.txt
+'
+
+test_expect_success 'good merge bases when good and bad are siblings' '
+       git bisect start "$B_HASH" "$A_HASH" > my_bisect_log.txt &&
+       grep "merge base must be tested" my_bisect_log.txt &&
+       git bisect good > my_bisect_log2.txt &&
+       grep "merge base must be tested" my_bisect_log2.txt &&
+       {
+               {
+                       grep "$SIDE_HASH5" my_bisect_log.txt &&
+                       grep "$HASH5" my_bisect_log2.txt
+               } || {
+                       grep "$SIDE_HASH5" my_bisect_log2.txt &&
+                       grep "$HASH5" my_bisect_log.txt
+               }
+       } &&
+       git bisect reset
+'
+
+check_trace() {
+       grep "$1" "$GIT_TRACE" | grep "\^$2" | grep "$3" >/dev/null
+}
+
+test_expect_success 'optimized merge base checks' '
+       GIT_TRACE="$(pwd)/trace.log" &&
+       export GIT_TRACE &&
+       git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt &&
+       grep "merge base must be tested" my_bisect_log.txt &&
+       grep "$HASH4" my_bisect_log.txt &&
+       check_trace "rev-list" "$HASH7" "$SIDE_HASH7" &&
+       git bisect good > my_bisect_log2.txt &&
+       test -f ".git/BISECT_ANCESTORS_OK" &&
+       test "$HASH6" = $(git rev-parse --verify HEAD) &&
+       : > "$GIT_TRACE" &&
+       git bisect bad > my_bisect_log3.txt &&
+       test_must_fail check_trace "rev-list" "$HASH6" "$SIDE_HASH7" &&
+       git bisect good "$A_HASH" > my_bisect_log4.txt &&
+       grep "merge base must be tested" my_bisect_log4.txt &&
+       test_must_fail test -f ".git/BISECT_ANCESTORS_OK" &&
+       check_trace "rev-list" "$HASH6" "$A_HASH" &&
+       unset GIT_TRACE
+'
+
 #
 #
 test_done
index 919552a2fc5544c130268befca322a6e6a8081c3..f105fab98e2d493ab489d345676101fc13096c22 100755 (executable)
@@ -6,7 +6,7 @@
 test_description='Test git rev-parse with different parent options'
 
 . ./test-lib.sh
-. ../t6000lib.sh # t6xxx specific functions
+. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions
 
 date >path0
 git update-index --add path0
index bc74349416d858834c43f6c648daa95c8b9f3a7a..8f5a06f7dd8383722022eb7a8abd0ff9bafa6f45 100755 (executable)
@@ -83,13 +83,13 @@ test_expect_success 'merge-msg test #1' '
 '
 
 cat >expected <<EOF
-Merge branch 'left' of ../$test
+Merge branch 'left' of $TEST_DIRECTORY/$test
 EOF
 
 test_expect_success 'merge-msg test #2' '
 
        git checkout master &&
-       git fetch ../"$test" left &&
+       git fetch "$TEST_DIRECTORY/$test" left &&
 
        git fmt-merge-msg <.git/FETCH_HEAD >actual &&
        test_cmp expected actual
index 26995b3cdda32b34b2ecf428e5bbf5ef2ff2fc8a..8bfae44a8392898453785f43c7e35b65e9c1f017 100755 (executable)
@@ -262,6 +262,50 @@ for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
        "
 done
 
+cat >expected <<\EOF
+master
+testtag
+EOF
+
+test_expect_success 'Check short refname format' '
+       (git for-each-ref --format="%(refname:short)" refs/heads &&
+       git for-each-ref --format="%(refname:short)" refs/tags) >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'Check for invalid refname format' '
+       test_must_fail git for-each-ref --format="%(refname:INVALID)"
+'
+
+cat >expected <<\EOF
+heads/master
+master
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs' '
+       git checkout -b newtag &&
+       echo "Using $datestamp" > one &&
+       git add one &&
+       git commit -m "Branch" &&
+       setdate_and_increment &&
+       git tag -m "Tagging at $datestamp" master &&
+       git for-each-ref --format "%(refname:short)" refs/heads/master refs/tags/master >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<\EOF
+heads/ambiguous
+ambiguous
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs II' '
+       git checkout master &&
+       git tag ambiguous testtag^0 &&
+       git branch ambiguous testtag^0 &&
+       git for-each-ref --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
+       test_cmp expected actual
+'
+
 test_expect_success 'an unusual tag with an incomplete line' '
 
        git tag -m "bogo" bogo &&
index 66bb1264ff7f8528168ca82ba908cff29658fa9d..575ef5beb2bdd3a0814fb45010ae7889b936f543 100755 (executable)
@@ -6,7 +6,7 @@ test_description='git mv in subdirs'
 test_expect_success \
     'prepare reference tree' \
     'mkdir path0 path1 &&
-     cp ../../COPYING path0/COPYING &&
+     cp "$TEST_DIRECTORY"/../COPYING path0/COPYING &&
      git add path0/COPYING &&
      git commit -m add -a'
 
@@ -40,7 +40,7 @@ test_expect_success \
 
 test_expect_success \
     'adding another file' \
-    'cp ../../README path0/README &&
+    'cp "$TEST_DIRECTORY"/../README path0/README &&
      git add path0/README &&
      git commit -m add2 -a'
 
index 182aea4267c33fd0f737c9028cedcb635e3348e2..b0a9d7d536314ec842b141c09ba0d6f8b06b6288 100755 (executable)
@@ -96,13 +96,17 @@ test_expect_success 'filter subdirectory only' '
        test_tick &&
        git commit -m "again not subdir" &&
        git branch sub &&
-       git filter-branch -f --subdirectory-filter subdir refs/heads/sub
+       git branch sub-earlier HEAD~2 &&
+       git filter-branch -f --subdirectory-filter subdir \
+               refs/heads/sub refs/heads/sub-earlier
 '
 
 test_expect_success 'subdirectory filter result looks okay' '
        test 2 = $(git rev-list sub | wc -l) &&
        git show sub:new &&
-       test_must_fail git show sub:subdir
+       test_must_fail git show sub:subdir &&
+       git show sub-earlier:new &&
+       test_must_fail git show sub-earlier:subdir
 '
 
 test_expect_success 'more setup' '
@@ -250,4 +254,12 @@ test_expect_success 'Tag name filtering strips gpg signature' '
        test_cmp expect actual
 '
 
+test_expect_success 'Tag name filtering allows slashes in tag names' '
+       git tag -m tag-with-slash X/1 &&
+       git cat-file tag X/1 | sed -e s,X/1,X/2, > expect &&
+       git filter-branch -f --tag-name-filter "echo X/2" &&
+       git cat-file tag X/2 > actual &&
+       test_cmp expect actual
+'
+
 test_done
index 33cde705953acc9a30d8535da77fe0f90e2b3118..f0edbf1a76739177943006461b853ec17e8bbb2b 100755 (executable)
@@ -625,7 +625,7 @@ esac
 # Name and email: C O Mitter <committer@example.com>
 # No password given, to enable non-interactive operation.
 
-cp -R ../t7004 ./gpghome
+cp -R "$TEST_DIRECTORY"/t7004 ./gpghome
 chmod 0700 gpghome
 GNUPGHOME="$(pwd)/gpghome"
 export GNUPGHOME
index c4ef19e402c7f4097842b9902a751ead46703974..96e163f084f471ea75e6d5b927a5edc6462e54d4 100755 (executable)
@@ -9,7 +9,7 @@ test_description='git reset should cull empty subdirs'
 test_expect_success \
     'creating initial files' \
     'mkdir path0 &&
-     cp ../../COPYING path0/COPYING &&
+     cp "$TEST_DIRECTORY"/../COPYING path0/COPYING &&
      git add path0/COPYING &&
      git commit -m add -a'
 
@@ -17,10 +17,10 @@ test_expect_success \
     'creating second files' \
     'mkdir path1 &&
      mkdir path1/path2 &&
-     cp ../../COPYING path1/path2/COPYING &&
-     cp ../../COPYING path1/COPYING &&
-     cp ../../COPYING COPYING &&
-     cp ../../COPYING path0/COPYING-TOO &&
+     cp "$TEST_DIRECTORY"/../COPYING path1/path2/COPYING &&
+     cp "$TEST_DIRECTORY"/../COPYING path1/COPYING &&
+     cp "$TEST_DIRECTORY"/../COPYING COPYING &&
+     cp "$TEST_DIRECTORY"/../COPYING path0/COPYING-TOO &&
      git add path1/path2/COPYING &&
      git add path1/COPYING &&
      git add COPYING &&
index fbec70d3c6abff4a97aa205aa10fbf6fe336fa5f..62d73f934a0c0be2d41a30d11308430838f6c9c0 100755 (executable)
@@ -337,6 +337,38 @@ test_expect_success \
     test refs/heads/delete-me = "$(git symbolic-ref HEAD)" &&
     test_must_fail git checkout --track -b track'
 
+test_expect_success \
+    'checkout with --track fakes a sensible -b <name>' '
+    git update-ref refs/remotes/origin/koala/bear renamer &&
+    git update-ref refs/new/koala/bear renamer &&
+
+    git checkout --track origin/koala/bear &&
+    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
+
+    git checkout master && git branch -D koala/bear &&
+
+    git checkout --track refs/remotes/origin/koala/bear &&
+    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
+
+    git checkout master && git branch -D koala/bear &&
+
+    git checkout --track remotes/origin/koala/bear &&
+    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
+
+    git checkout master && git branch -D koala/bear &&
+
+    git checkout --track refs/new/koala/bear &&
+    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)"
+'
+
+test_expect_success \
+    'checkout with --track, but without -b, fails with too short tracked name' '
+    test_must_fail git checkout --track renamer'
+
 test_expect_success 'checkout an unmerged path should fail' '
        rm -f .git/index &&
        O=$(echo original | git hash-object -w --stdin) &&
@@ -359,4 +391,14 @@ test_expect_success 'checkout an unmerged path should fail' '
        test_cmp sample file
 '
 
+test_expect_success 'failing checkout -b should not break working tree' '
+       git reset --hard master &&
+       git symbolic-ref HEAD refs/heads/master &&
+       test_must_fail git checkout -b renamer side^ &&
+       test $(git symbolic-ref HEAD) = refs/heads/master &&
+       git diff --exit-code &&
+       git diff --cached --exit-code
+
+'
+
 test_done
index 0fe745ea0dbfda5e72ee9ef1df0b32245812e268..6e18a96319bef5c4ddfbc86ce79b02b364f04387 100755 (executable)
@@ -46,15 +46,24 @@ test_expect_success 'unedited template with comments should not commit' '
 '
 
 test_expect_success 'a Signed-off-by line by itself should not commit' '
-       ! GIT_EDITOR=../t7500/add-signed-off git commit --template "$TEMPLATE"
+       (
+               test_set_editor "$TEST_DIRECTORY"/t7500/add-signed-off &&
+               test_must_fail git commit --template "$TEMPLATE"
+       )
 '
 
 test_expect_success 'adding comments to a template should not commit' '
-       ! GIT_EDITOR=../t7500/add-comments git commit --template "$TEMPLATE"
+       (
+               test_set_editor "$TEST_DIRECTORY"/t7500/add-comments &&
+               test_must_fail git commit --template "$TEMPLATE"
+       )
 '
 
 test_expect_success 'adding real content to a template should commit' '
-       GIT_EDITOR=../t7500/add-content git commit --template "$TEMPLATE" &&
+       (
+               test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+               git commit --template "$TEMPLATE"
+       ) &&
        commit_msg_is "template linecommit message"
 '
 
@@ -62,7 +71,10 @@ test_expect_success '-t option should be short for --template' '
        echo "short template" > "$TEMPLATE" &&
        echo "new content" >> foo &&
        git add foo &&
-       GIT_EDITOR=../t7500/add-content git commit -t "$TEMPLATE" &&
+       (
+               test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+               git commit -t "$TEMPLATE"
+       ) &&
        commit_msg_is "short templatecommit message"
 '
 
@@ -71,7 +83,10 @@ test_expect_success 'config-specified template should commit' '
        git config commit.template "$TEMPLATE" &&
        echo "more content" >> foo &&
        git add foo &&
-       GIT_EDITOR=../t7500/add-content git commit &&
+       (
+               test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+               git commit
+       ) &&
        git config --unset commit.template &&
        commit_msg_is "new templatecommit message"
 '
@@ -79,7 +94,7 @@ test_expect_success 'config-specified template should commit' '
 test_expect_success 'explicit commit message should override template' '
        echo "still more content" >> foo &&
        git add foo &&
-       GIT_EDITOR=../t7500/add-content git commit --template "$TEMPLATE" \
+       GIT_EDITOR="$TEST_DIRECTORY"/t7500/add-content git commit --template "$TEMPLATE" \
                -m "command line msg" &&
        commit_msg_is "command line msg"
 '
@@ -88,8 +103,10 @@ test_expect_success 'commit message from file should override template' '
        echo "content galore" >> foo &&
        git add foo &&
        echo "standard input msg" |
-               GIT_EDITOR=../t7500/add-content git commit \
-                       --template "$TEMPLATE" --file - &&
+       (
+               test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+               git commit --template "$TEMPLATE" --file -
+       ) &&
        commit_msg_is "standard input msg"
 '
 
@@ -132,10 +149,12 @@ EOF
 
 test_expect_success '--signoff' '
        echo "yet another content *narf*" >> foo &&
-       echo "zort" |
-               GIT_EDITOR=../t7500/add-content git commit -s -F - foo &&
+       echo "zort" | (
+               test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+               git commit -s -F - foo
+       ) &&
        git cat-file commit HEAD | sed "1,/^$/d" > output &&
-       diff expect output
+       test_cmp expect output
 '
 
 test_expect_success 'commit message from file (1)' '
index c8e4c2e7b452c5e8db3958a85195c8cc5f6918f5..1905fb34cd222aa9443d28fd673b0cc9e02f3335 100755 (executable)
@@ -46,6 +46,7 @@ cat > expect << \EOF
 #
 # Changed but not updated:
 #   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
 #
 #      modified:   dir1/modified
 #
@@ -76,6 +77,7 @@ cat >expect <<EOF
 #
 # Changed but not updated:
 #   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
 #
 #      modified:   dir1/modified
 #
@@ -104,6 +106,7 @@ cat >expect <<EOF
 #
 # Changed but not updated:
 #   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
 #
 #      modified:   dir1/modified
 #
@@ -138,6 +141,7 @@ cat >expect <<EOF
 #
 # Changed but not updated:
 #   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
 #
 #      modified:   dir1/modified
 #
@@ -174,6 +178,7 @@ cat > expect << \EOF
 #
 # Changed but not updated:
 #   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
 #
 #      modified:   modified
 #
@@ -204,6 +209,7 @@ cat > expect << \EOF
 #
 # Changed but not updated:
 #   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
 #
 #      modified:   dir1/modified
 #
@@ -267,6 +273,7 @@ cat >expect <<EOF
 #
 # Changed but not updated:
 #   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
 #
 #      modified:   dir1/modified
 #
@@ -297,6 +304,7 @@ cat >expect <<EOF
 #
 # Changed but not updated:
 #   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
 #
 #      modified:   dir1/modified
 #
@@ -326,6 +334,7 @@ cat >expect <<EOF
 # On branch master
 # Changed but not updated:
 #   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
 #
 #      modified:   dir1/modified
 #
@@ -357,6 +366,7 @@ cat >expect <<EOF
 #
 # Changed but not updated:
 #   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
 #
 #      modified:   dir1/modified
 #
index 94bc556cb2ad5dd56be2aaed82cec8e2edde7265..9516f541e9c47f83fed2fc8d3baa065a9bb206de 100755 (executable)
@@ -230,6 +230,10 @@ test_expect_success 'test option parsing' '
        test_must_fail git merge
 '
 
+test_expect_success 'reject non-strategy with a git-merge-foo name' '
+       test_must_fail git merge -s index c1
+'
+
 test_expect_success 'merge c0 with c1' '
        git reset --hard c0 &&
        git merge c1 &&
index b47b7b9757dffe2d3d41127b43ffa929505c0e77..7e17eb490d4b1f4ac275a7033562aaa6693181ab 100755 (executable)
@@ -60,4 +60,57 @@ test_expect_success 'merge c1 with c2, c3, c4, c5' '
        test -f c5.c
 '
 
+test_expect_success 'setup' '
+       for i in A B C D E
+       do
+               echo $i > $i.c &&
+               git add $i.c &&
+               git commit -m $i &&
+               git tag $i
+       done &&
+       git reset --hard A &&
+       for i in F G H I
+       do
+               echo $i > $i.c &&
+               git add $i.c &&
+               git commit -m $i &&
+               git tag $i
+       done
+'
+
+test_expect_success 'merge E and I' '
+       git reset --hard A &&
+       git merge E I
+'
+
+test_expect_success 'verify merge result' '
+       test $(git rev-parse HEAD^1) = $(git rev-parse E) &&
+       test $(git rev-parse HEAD^2) = $(git rev-parse I)
+'
+
+test_expect_success 'add conflicts' '
+       git reset --hard E &&
+       echo foo > file.c &&
+       git add file.c &&
+       git commit -m E2 &&
+       git tag E2 &&
+       git reset --hard I &&
+       echo bar >file.c &&
+       git add file.c &&
+       git commit -m I2 &&
+       git tag I2
+'
+
+test_expect_success 'merge E2 and I2, causing a conflict and resolve it' '
+       git reset --hard A &&
+       test_must_fail git merge E2 I2 &&
+       echo baz > file.c &&
+       git add file.c &&
+       git commit -m "resolve conflict"
+'
+
+test_expect_success 'verify merge result' '
+       test $(git rev-parse HEAD^1) = $(git rev-parse E2) &&
+       test $(git rev-parse HEAD^2) = $(git rev-parse I2)
+'
 test_done
diff --git a/t/t7606-merge-custom.sh b/t/t7606-merge-custom.sh
new file mode 100755 (executable)
index 0000000..52a451d
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+test_description='git merge
+
+Testing a custom strategy.'
+
+. ./test-lib.sh
+
+cat >git-merge-theirs <<EOF
+#!$SHELL_PATH
+eval git read-tree --reset -u \\\$\$#
+EOF
+chmod +x git-merge-theirs
+PATH=.:$PATH
+export PATH
+
+test_expect_success 'setup' '
+       echo c0 >c0.c &&
+       git add c0.c &&
+       git commit -m c0 &&
+       git tag c0 &&
+       echo c1 >c1.c &&
+       git add c1.c &&
+       git commit -m c1 &&
+       git tag c1 &&
+       git reset --hard c0 &&
+       echo c1c1 >c1.c &&
+       echo c2 >c2.c &&
+       git add c1.c c2.c &&
+       git commit -m c2 &&
+       git tag c2
+'
+
+test_expect_success 'merge c2 with a custom strategy' '
+       git reset --hard c1 &&
+       git merge -s theirs c2 &&
+       test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+       test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
+       test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
+       test "$(git rev-parse c2^{tree})" = "$(git rev-parse HEAD^{tree})" &&
+       git diff --exit-code &&
+       git diff --exit-code c2 HEAD &&
+       git diff --exit-code c2 &&
+       test -f c0.c &&
+       grep c1c1 c1.c &&
+       test -f c2.c
+'
+
+test_done
index eabec2e06e2f97fc1790cd4ce30a80e402d4a205..45cb60ea4b167676b07ae1c847c0467f2a5e3d69 100755 (executable)
@@ -4,7 +4,7 @@ test_description='git annotate'
 . ./test-lib.sh
 
 PROG='git annotate'
-. ../annotate-tests.sh
+. "$TEST_DIRECTORY"/annotate-tests.sh
 
 test_expect_success \
     'Annotating an old revision works' \
index 92ece30fa94784bdad8ae50fc370487e60bbcb5c..597cf0486fbe1034594d3eec821f5278d9648d43 100755 (executable)
@@ -4,6 +4,6 @@ test_description='git blame'
 . ./test-lib.sh
 
 PROG='git blame -c'
-. ../annotate-tests.sh
+. "$TEST_DIRECTORY"/annotate-tests.sh
 
 test_done
index 843a5013b96c675a629bd7f738eca220861e6ff8..9b238c329b87d98dca4d0e66839df97c5c3a0673 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2006 Eric Wong
 #
 
-test_description='git-svn basic tests'
+test_description='git svn basic tests'
 GIT_SVN_LC_ALL=${LC_ALL:-$LANG}
 
 case "$GIT_SVN_LC_ALL" in
@@ -17,10 +17,10 @@ esac
 
 . ./lib-git-svn.sh
 
-say 'define NO_SVN_TESTS to skip git-svn tests'
+say 'define NO_SVN_TESTS to skip git svn tests'
 
 test_expect_success \
-    'initialize git-svn' '
+    'initialize git svn' '
        mkdir import &&
        cd import &&
        echo foo > foo &&
@@ -31,26 +31,26 @@ test_expect_success \
        echo "zzz" > bar/zzz &&
        echo "#!/bin/sh" > exec.sh &&
        chmod +x exec.sh &&
-       svn import -m "import for git-svn" . "$svnrepo" >/dev/null &&
+       svn import -m "import for git svn" . "$svnrepo" >/dev/null &&
        cd .. &&
        rm -rf import &&
-       git-svn init "$svnrepo"'
+       git svn init "$svnrepo"'
 
 test_expect_success \
     'import an SVN revision into git' \
-    'git-svn fetch'
+    'git svn fetch'
 
 test_expect_success "checkout from svn" 'svn co "$svnrepo" "$SVN_TREE"'
 
 name='try a deep --rmdir with a commit'
 test_expect_success "$name" '
-       git checkout -f -b mybranch remotes/git-svn &&
+       git checkout -f -b mybranch ${remotes_git_svn} &&
        mv dir/a/b/c/d/e/file dir/file &&
        cp dir/file file &&
        git update-index --add --remove dir/a/b/c/d/e/file dir/file file &&
        git commit -m "$name" &&
-       git-svn set-tree --find-copies-harder --rmdir \
-               remotes/git-svn..mybranch &&
+       git svn set-tree --find-copies-harder --rmdir \
+               ${remotes_git_svn}..mybranch &&
        svn up "$SVN_TREE" &&
        test -d "$SVN_TREE"/dir && test ! -d "$SVN_TREE"/dir/a'
 
@@ -63,61 +63,61 @@ test_expect_success "$name" "
        git update-index --remove dir/file &&
        git update-index --add dir/file/file &&
        git commit -m '$name' &&
-       test_must_fail git-svn set-tree --find-copies-harder --rmdir \
-               remotes/git-svn..mybranch" || true
+       test_must_fail git svn set-tree --find-copies-harder --rmdir \
+               ${remotes_git_svn}..mybranch" || true
 
 
 name='detect node change from directory to file #1'
 test_expect_success "$name" '
        rm -rf dir "$GIT_DIR"/index &&
-       git checkout -f -b mybranch2 remotes/git-svn &&
+       git checkout -f -b mybranch2 ${remotes_git_svn} &&
        mv bar/zzz zzz &&
        rm -rf bar &&
        mv zzz bar &&
        git update-index --remove -- bar/zzz &&
        git update-index --add -- bar &&
        git commit -m "$name" &&
-       test_must_fail git-svn set-tree --find-copies-harder --rmdir \
-               remotes/git-svn..mybranch2' || true
+       test_must_fail git svn set-tree --find-copies-harder --rmdir \
+               ${remotes_git_svn}..mybranch2' || true
 
 
 name='detect node change from file to directory #2'
 test_expect_success "$name" '
        rm -f "$GIT_DIR"/index &&
-       git checkout -f -b mybranch3 remotes/git-svn &&
+       git checkout -f -b mybranch3 ${remotes_git_svn} &&
        rm bar/zzz &&
        git update-index --remove bar/zzz &&
        mkdir bar/zzz &&
        echo yyy > bar/zzz/yyy &&
        git update-index --add bar/zzz/yyy &&
        git commit -m "$name" &&
-       test_must_fail git-svn set-tree --find-copies-harder --rmdir \
-               remotes/git-svn..mybranch3' || true
+       test_must_fail git svn set-tree --find-copies-harder --rmdir \
+               ${remotes_git_svn}..mybranch3' || true
 
 
 name='detect node change from directory to file #2'
 test_expect_success "$name" '
        rm -f "$GIT_DIR"/index &&
-       git checkout -f -b mybranch4 remotes/git-svn &&
+       git checkout -f -b mybranch4 ${remotes_git_svn} &&
        rm -rf dir &&
        git update-index --remove -- dir/file &&
        touch dir &&
        echo asdf > dir &&
        git update-index --add -- dir &&
        git commit -m "$name" &&
-       test_must_fail git-svn set-tree --find-copies-harder --rmdir \
-               remotes/git-svn..mybranch4' || true
+       test_must_fail git svn set-tree --find-copies-harder --rmdir \
+               ${remotes_git_svn}..mybranch4' || true
 
 
 name='remove executable bit from a file'
 test_expect_success "$name" '
        rm -f "$GIT_DIR"/index &&
-       git checkout -f -b mybranch5 remotes/git-svn &&
+       git checkout -f -b mybranch5 ${remotes_git_svn} &&
        chmod -x exec.sh &&
        git update-index exec.sh &&
        git commit -m "$name" &&
-       git-svn set-tree --find-copies-harder --rmdir \
-               remotes/git-svn..mybranch5 &&
+       git svn set-tree --find-copies-harder --rmdir \
+               ${remotes_git_svn}..mybranch5 &&
        svn up "$SVN_TREE" &&
        test ! -x "$SVN_TREE"/exec.sh'
 
@@ -127,8 +127,8 @@ test_expect_success "$name" '
        chmod +x exec.sh &&
        git update-index exec.sh &&
        git commit -m "$name" &&
-       git-svn set-tree --find-copies-harder --rmdir \
-               remotes/git-svn..mybranch5 &&
+       git svn set-tree --find-copies-harder --rmdir \
+               ${remotes_git_svn}..mybranch5 &&
        svn up "$SVN_TREE" &&
        test -x "$SVN_TREE"/exec.sh'
 
@@ -139,8 +139,8 @@ test_expect_success "$name" '
        ln -s bar/zzz exec.sh &&
        git update-index exec.sh &&
        git commit -m "$name" &&
-       git-svn set-tree --find-copies-harder --rmdir \
-               remotes/git-svn..mybranch5 &&
+       git svn set-tree --find-copies-harder --rmdir \
+               ${remotes_git_svn}..mybranch5 &&
        svn up "$SVN_TREE" &&
        test -L "$SVN_TREE"/exec.sh'
 
@@ -151,8 +151,8 @@ test_expect_success "$name" '
        ln -s bar/zzz exec-2.sh &&
        git update-index --add bar/zzz exec-2.sh &&
        git commit -m "$name" &&
-       git-svn set-tree --find-copies-harder --rmdir \
-               remotes/git-svn..mybranch5 &&
+       git svn set-tree --find-copies-harder --rmdir \
+               ${remotes_git_svn}..mybranch5 &&
        svn up "$SVN_TREE" &&
        test -x "$SVN_TREE"/bar/zzz &&
        test -L "$SVN_TREE"/exec-2.sh'
@@ -164,8 +164,8 @@ test_expect_success "$name" '
        cp help exec-2.sh &&
        git update-index exec-2.sh &&
        git commit -m "$name" &&
-       git-svn set-tree --find-copies-harder --rmdir \
-               remotes/git-svn..mybranch5 &&
+       git svn set-tree --find-copies-harder --rmdir \
+               ${remotes_git_svn}..mybranch5 &&
        svn up "$SVN_TREE" &&
        test -f "$SVN_TREE"/exec-2.sh &&
        test ! -L "$SVN_TREE"/exec-2.sh &&
@@ -180,7 +180,7 @@ then
                echo '# hello' >> exec-2.sh &&
                git update-index exec-2.sh &&
                git commit -m 'éï∏' &&
-               git-svn set-tree HEAD"
+               git svn set-tree HEAD"
        unset LC_ALL
 else
        say "UTF-8 locale not set, test skipped ($GIT_SVN_LC_ALL)"
@@ -190,8 +190,8 @@ name='test fetch functionality (svn => git) with alternate GIT_SVN_ID'
 GIT_SVN_ID=alt
 export GIT_SVN_ID
 test_expect_success "$name" \
-    'git-svn init "$svnrepo" && git-svn fetch &&
-     git rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a &&
+    'git svn init "$svnrepo" && git svn fetch &&
+     git rev-list --pretty=raw ${remotes_git_svn} | grep ^tree | uniq > a &&
      git rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&
      test_cmp a b'
 
@@ -215,45 +215,45 @@ test_expect_success "$name" "test_cmp a expected"
 
 test_expect_success 'exit if remote refs are ambigious' "
         git config --add svn-remote.svn.fetch \
-                              bar:refs/remotes/git-svn &&
-       test_must_fail git-svn migrate
+                              bar:refs/${remotes_git_svn} &&
+       test_must_fail git svn migrate
 "
 
 test_expect_success 'exit if init-ing a would clobber a URL' '
         svnadmin create "${PWD}/svnrepo2" &&
         svn mkdir -m "mkdir bar" "${svnrepo}2/bar" &&
         git config --unset svn-remote.svn.fetch \
-                                "^bar:refs/remotes/git-svn$" &&
-       test_must_fail git-svn init "${svnrepo}2/bar"
+                                "^bar:refs/${remotes_git_svn}$" &&
+       test_must_fail git svn init "${svnrepo}2/bar"
         '
 
 test_expect_success \
   'init allows us to connect to another directory in the same repo' '
-        git-svn init --minimize-url -i bar "$svnrepo/bar" &&
+        git svn init --minimize-url -i bar "$svnrepo/bar" &&
         git config --get svn-remote.svn.fetch \
                               "^bar:refs/remotes/bar$" &&
         git config --get svn-remote.svn.fetch \
-                              "^:refs/remotes/git-svn$"
+                              "^:refs/${remotes_git_svn}$"
         '
 
 test_expect_success 'able to dcommit to a subdirectory' "
-       git-svn fetch -i bar &&
+       git svn fetch -i bar &&
        git checkout -b my-bar refs/remotes/bar &&
        echo abc > d &&
        git update-index --add d &&
        git commit -m '/bar/d should be in the log' &&
-       git-svn dcommit -i bar &&
+       git svn dcommit -i bar &&
        test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" &&
        mkdir newdir &&
        echo new > newdir/dir &&
        git update-index --add newdir/dir &&
        git commit -m 'add a new directory' &&
-       git-svn dcommit -i bar &&
+       git svn dcommit -i bar &&
        test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" &&
        echo foo >> newdir/dir &&
        git update-index newdir/dir &&
        git commit -m 'modify a file in new directory' &&
-       git-svn dcommit -i bar &&
+       git svn dcommit -i bar &&
        test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\"
        "
 
@@ -261,7 +261,7 @@ test_expect_success 'able to set-tree to a subdirectory' "
        echo cba > d &&
        git update-index d &&
        git commit -m 'update /bar/d' &&
-       git-svn set-tree -i bar HEAD &&
+       git svn set-tree -i bar HEAD &&
        test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\"
        "
 
index f420796c31db2746b71ba9d7090f37363eba214a..1e31d6ea7253ee4216347fd707d1408f97af32fa 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2006 Eric Wong
 #
 
-test_description='git-svn property tests'
+test_description='git svn property tests'
 . ./lib-git-svn.sh
 
 mkdir import
@@ -26,29 +26,29 @@ cd import
 EOF
 
        printf "Hello\r\nWorld\r\n" > crlf
-       a_crlf=`git-hash-object -w crlf`
+       a_crlf=`git hash-object -w crlf`
        printf "Hello\rWorld\r" > cr
-       a_cr=`git-hash-object -w cr`
+       a_cr=`git hash-object -w cr`
        printf "Hello\nWorld\n" > lf
-       a_lf=`git-hash-object -w lf`
+       a_lf=`git hash-object -w lf`
 
        printf "Hello\r\nWorld" > ne_crlf
-       a_ne_crlf=`git-hash-object -w ne_crlf`
+       a_ne_crlf=`git hash-object -w ne_crlf`
        printf "Hello\nWorld" > ne_lf
-       a_ne_lf=`git-hash-object -w ne_lf`
+       a_ne_lf=`git hash-object -w ne_lf`
        printf "Hello\rWorld" > ne_cr
-       a_ne_cr=`git-hash-object -w ne_cr`
+       a_ne_cr=`git hash-object -w ne_cr`
 
        touch empty
-       a_empty=`git-hash-object -w empty`
+       a_empty=`git hash-object -w empty`
        printf "\n" > empty_lf
-       a_empty_lf=`git-hash-object -w empty_lf`
+       a_empty_lf=`git hash-object -w empty_lf`
        printf "\r" > empty_cr
-       a_empty_cr=`git-hash-object -w empty_cr`
+       a_empty_cr=`git hash-object -w empty_cr`
        printf "\r\n" > empty_crlf
-       a_empty_crlf=`git-hash-object -w empty_crlf`
+       a_empty_crlf=`git hash-object -w empty_crlf`
 
-       svn import --no-auto-props -m 'import for git-svn' . "$svnrepo" >/dev/null
+       svn import --no-auto-props -m 'import for git svn' . "$svnrepo" >/dev/null
 cd ..
 
 rm -rf import
@@ -66,16 +66,16 @@ test_expect_success 'setup some commits to svn' \
                svn commit -m "Propset Id" &&
        cd ..'
 
-test_expect_success 'initialize git-svn' 'git-svn init "$svnrepo"'
-test_expect_success 'fetch revisions from svn' 'git-svn fetch'
+test_expect_success 'initialize git svn' 'git svn init "$svnrepo"'
+test_expect_success 'fetch revisions from svn' 'git svn fetch'
 
 name='test svn:keywords ignoring'
 test_expect_success "$name" \
-       'git checkout -b mybranch remotes/git-svn &&
+       'git checkout -b mybranch ${remotes_git_svn} &&
        echo Hi again >> kw.c &&
        git commit -a -m "test keywords ignoring" &&
-       git-svn set-tree remotes/git-svn..mybranch &&
-       git pull . remotes/git-svn'
+       git svn set-tree ${remotes_git_svn}..mybranch &&
+       git pull . ${remotes_git_svn}'
 
 expect='/* $Id$ */'
 got="`sed -ne 2p kw.c`"
@@ -90,8 +90,8 @@ test_expect_success "propset CR on crlf files" \
         cd ..'
 
 test_expect_success 'fetch and pull latest from svn and checkout a new wc' \
-       'git-svn fetch &&
-        git pull . remotes/git-svn &&
+       'git svn fetch &&
+        git pull . ${remotes_git_svn} &&
         svn co "$svnrepo" new_wc'
 
 for i in crlf ne_crlf lf ne_lf cr ne_cr empty_cr empty_lf empty empty_crlf
@@ -103,8 +103,8 @@ done
 cd test_wc
        printf '$Id$\rHello\rWorld\r' > cr
        printf '$Id$\rHello\rWorld' > ne_cr
-       a_cr=`printf '$Id$\r\nHello\r\nWorld\r\n' | git-hash-object --stdin`
-       a_ne_cr=`printf '$Id$\r\nHello\r\nWorld' | git-hash-object --stdin`
+       a_cr=`printf '$Id$\r\nHello\r\nWorld\r\n' | git hash-object --stdin`
+       a_ne_cr=`printf '$Id$\r\nHello\r\nWorld' | git hash-object --stdin`
        test_expect_success 'Set CRLF on cr files' \
        'svn propset svn:eol-style CRLF cr &&
         svn propset svn:eol-style CRLF ne_cr &&
@@ -113,10 +113,10 @@ cd test_wc
         svn commit -m "propset CRLF on cr files"'
 cd ..
 test_expect_success 'fetch and pull latest from svn' \
-       'git-svn fetch && git pull . remotes/git-svn'
+       'git svn fetch && git pull . ${remotes_git_svn}'
 
-b_cr="`git-hash-object cr`"
-b_ne_cr="`git-hash-object ne_cr`"
+b_cr="`git hash-object cr`"
+b_ne_cr="`git hash-object ne_cr`"
 
 test_expect_success 'CRLF + $Id$' "test '$a_cr' = '$b_cr'"
 test_expect_success 'CRLF + $Id$ (no newline)' "test '$a_ne_cr' = '$b_ne_cr'"
@@ -145,7 +145,7 @@ test_expect_success 'test show-ignore' "
        svn propset -R svn:ignore 'no-such-file*' .
        svn commit -m 'propset svn:ignore'
        cd .. &&
-       git-svn show-ignore > show-ignore.got &&
+       git svn show-ignore > show-ignore.got &&
        cmp show-ignore.expect show-ignore.got
        "
 
@@ -161,8 +161,8 @@ cat >create-ignore-index.expect <<\EOF
 EOF
 
 test_expect_success 'test create-ignore' "
-       git-svn fetch && git pull . remotes/git-svn &&
-       git-svn create-ignore &&
+       git svn fetch && git pull . ${remotes_git_svn} &&
+       git svn create-ignore &&
        cmp ./.gitignore create-ignore.expect &&
        cmp ./deeply/.gitignore create-ignore.expect &&
        cmp ./deeply/nested/.gitignore create-ignore.expect &&
@@ -182,15 +182,15 @@ EOF
 # pattern, it can pass even though the propget did not execute on the
 # right directory.
 test_expect_success 'test propget' "
-       git-svn propget svn:ignore . | cmp - prop.expect &&
+       git svn propget svn:ignore . | cmp - prop.expect &&
        cd deeply &&
-       git-svn propget svn:ignore . | cmp - ../prop.expect &&
-       git-svn propget svn:entry:committed-rev nested/directory/.keep \
+       git svn propget svn:ignore . | cmp - ../prop.expect &&
+       git svn propget svn:entry:committed-rev nested/directory/.keep \
          | cmp - ../prop2.expect &&
-       git-svn propget svn:ignore .. | cmp - ../prop.expect &&
-       git-svn propget svn:ignore nested/ | cmp - ../prop.expect &&
-       git-svn propget svn:ignore ./nested | cmp - ../prop.expect &&
-       git-svn propget svn:ignore .././deeply/nested | cmp - ../prop.expect
+       git svn propget svn:ignore .. | cmp - ../prop.expect &&
+       git svn propget svn:ignore nested/ | cmp - ../prop.expect &&
+       git svn propget svn:ignore ./nested | cmp - ../prop.expect &&
+       git svn propget svn:ignore .././deeply/nested | cmp - ../prop.expect
        "
 
 cat >prop.expect <<\EOF
@@ -210,8 +210,8 @@ Properties on 'nested/directory/.keep':
 EOF
 
 test_expect_success 'test proplist' "
-       git-svn proplist . | cmp - prop.expect &&
-       git-svn proplist nested/directory/.keep | cmp - prop2.expect
+       git svn proplist . | cmp - prop.expect &&
+       git svn proplist nested/directory/.keep | cmp - prop2.expect
        "
 
 test_done
index 0e7ce34b9b1e254873a2700cf58095318b49b15c..e2232180158cfb0e523c8ffdd3ac10bf61c8f4ee 100755 (executable)
@@ -1,5 +1,5 @@
 #!/bin/sh
-test_description='git-svn rmdir'
+test_description='git svn rmdir'
 . ./lib-git-svn.sh
 
 test_expect_success 'initialize repo' '
@@ -9,20 +9,20 @@ test_expect_success 'initialize repo' '
        mkdir -p deeply/nested/directory/number/2 &&
        echo foo > deeply/nested/directory/number/1/file &&
        echo foo > deeply/nested/directory/number/2/another &&
-       svn import -m "import for git-svn" . "$svnrepo" &&
+       svn import -m "import for git svn" . "$svnrepo" &&
        cd ..
        '
 
-test_expect_success 'mirror via git-svn' '
-       git-svn init "$svnrepo" &&
-       git-svn fetch &&
-       git checkout -f -b test-rmdir remotes/git-svn
+test_expect_success 'mirror via git svn' '
+       git svn init "$svnrepo" &&
+       git svn fetch &&
+       git checkout -f -b test-rmdir ${remotes_git_svn}
        '
 
 test_expect_success 'Try a commit on rmdir' '
        git rm -f deeply/nested/directory/number/2/another &&
        git commit -a -m "remove another" &&
-       git-svn set-tree --rmdir HEAD &&
+       git svn set-tree --rmdir HEAD &&
        svn ls -R "$svnrepo" | grep ^deeply/nested/directory/number/1
        '
 
index 9ffd8458ef9d58fa5d3c42fd61f4629219b4d80a..963dd95e4a71ccefa64b9500c490f44ea6d7c789 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2007 Eric Wong
 #
 
-test_description='git-svn tracking removed top-level path'
+test_description='git svn tracking removed top-level path'
 . ./lib-git-svn.sh
 
 test_expect_success 'make history for tracking' '
index 4d964e2db7cc3c96fc64911bd58c4f2f9679a6cd..0a091e048e1f94eac751ba2b5c22fb8bf436e717 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2006 Eric Wong
 #
 
-test_description='git-svn fetching'
+test_description='git svn fetching'
 . ./lib-git-svn.sh
 
 test_expect_success 'initialize repo' '
@@ -27,8 +27,8 @@ test_expect_success 'initialize repo' '
        '
 
 test_expect_success 'init and fetch a moved directory' '
-       git-svn init --minimize-url -i thunk "$svnrepo"/thunk &&
-       git-svn fetch -i thunk &&
+       git svn init --minimize-url -i thunk "$svnrepo"/thunk &&
+       git svn fetch -i thunk &&
        test "`git rev-parse --verify refs/remotes/thunk@2`" \
            = "`git rev-parse --verify refs/remotes/thunk~1`" &&
         test "`git cat-file blob refs/remotes/thunk:readme |\
@@ -43,7 +43,7 @@ test_expect_success 'init and fetch from one svn-remote' '
           trunk:refs/remotes/svn/trunk &&
         git config --add svn-remote.svn.fetch \
           thunk:refs/remotes/svn/thunk &&
-        git-svn fetch -i svn/thunk &&
+        git svn fetch -i svn/thunk &&
        test "`git rev-parse --verify refs/remotes/svn/trunk`" \
            = "`git rev-parse --verify refs/remotes/svn/thunk~1`" &&
         test "`git cat-file blob refs/remotes/svn/thunk:readme |\
@@ -57,8 +57,8 @@ test_expect_success 'follow deleted parent' '
                -r2 "$svnrepo"/trunk "$svnrepo"/junk) &&
         git config --add svn-remote.svn.fetch \
           junk:refs/remotes/svn/junk &&
-        git-svn fetch -i svn/thunk &&
-        git-svn fetch -i svn/junk &&
+        git svn fetch -i svn/thunk &&
+        git svn fetch -i svn/junk &&
         test -z "`git diff svn/junk svn/trunk`" &&
         test "`git merge-base svn/junk svn/trunk`" \
            = "`git rev-parse svn/trunk`"
@@ -69,9 +69,9 @@ test_expect_success 'follow larger parent' '
         echo hi > import/trunk/thunk/bump/thud/file &&
         svn import -m "import a larger parent" import "$svnrepo"/larger-parent &&
         svn cp -m "hi" "$svnrepo"/larger-parent "$svnrepo"/another-larger &&
-        git-svn init --minimize-url -i larger \
+        git svn init --minimize-url -i larger \
           "$svnrepo"/another-larger/trunk/thunk/bump/thud &&
-        git-svn fetch -i larger &&
+        git svn fetch -i larger &&
         git rev-parse --verify refs/remotes/larger &&
         git rev-parse --verify \
            refs/remotes/larger-parent/trunk/thunk/bump/thud &&
@@ -92,15 +92,15 @@ test_expect_success 'follow higher-level parent' '
                 cd ..
         svn mkdir -m "new glob at top level" "$svnrepo"/glob &&
         svn mv -m "move blob down a level" "$svnrepo"/blob "$svnrepo"/glob/blob &&
-        git-svn init --minimize-url -i blob "$svnrepo"/glob/blob &&
-        git-svn fetch -i blob
+        git svn init --minimize-url -i blob "$svnrepo"/glob/blob &&
+        git svn fetch -i blob
         '
 
 test_expect_success 'follow deleted directory' '
        svn mv -m "bye!" "$svnrepo"/glob/blob/hi "$svnrepo"/glob/blob/bye &&
        svn rm -m "remove glob" "$svnrepo"/glob &&
-       git-svn init --minimize-url -i glob "$svnrepo"/glob &&
-       git-svn fetch -i glob &&
+       git svn init --minimize-url -i glob "$svnrepo"/glob &&
+       git svn fetch -i glob &&
        test "`git cat-file blob refs/remotes/glob:blob/bye`" = hi &&
        test "`git ls-tree refs/remotes/glob | wc -l `" -eq 1
        '
@@ -129,9 +129,9 @@ test_expect_success 'follow-parent avoids deleting relevant info' '
          poke native/t/c.t &&
          svn commit -m "reorg test" &&
        cd .. &&
-       git-svn init --minimize-url -i r9270-t \
+       git svn init --minimize-url -i r9270-t \
          "$svnrepo"/r9270/trunk/subversion/bindings/swig/perl/native/t &&
-       git-svn fetch -i r9270-t &&
+       git svn fetch -i r9270-t &&
        test `git rev-list r9270-t | wc -l` -eq 2 &&
        test "`git ls-tree --name-only r9270-t~1`" = \
             "`git ls-tree --name-only r9270-t`"
@@ -139,9 +139,9 @@ test_expect_success 'follow-parent avoids deleting relevant info' '
 
 test_expect_success "track initial change if it was only made to parent" '
        svn cp -m "wheee!" "$svnrepo"/r9270/trunk "$svnrepo"/r9270/drunk &&
-       git-svn init --minimize-url -i r9270-d \
+       git svn init --minimize-url -i r9270-d \
          "$svnrepo"/r9270/drunk/subversion/bindings/swig/perl/native/t &&
-       git-svn fetch -i r9270-d &&
+       git svn fetch -i r9270-d &&
        test `git rev-list r9270-d | wc -l` -eq 3 &&
        test "`git ls-tree --name-only r9270-t`" = \
             "`git ls-tree --name-only r9270-d`" &&
@@ -151,19 +151,19 @@ test_expect_success "track initial change if it was only made to parent" '
 
 test_expect_success "track multi-parent paths" '
        svn cp -m "resurrect /glob" "$svnrepo"/r9270 "$svnrepo"/glob &&
-       git-svn multi-fetch &&
+       git svn multi-fetch &&
        test `git cat-file commit refs/remotes/glob | \
               grep "^parent " | wc -l` -eq 2
        '
 
 test_expect_success "multi-fetch continues to work" "
-       git-svn multi-fetch
+       git svn multi-fetch
        "
 
 test_expect_success "multi-fetch works off a 'clean' repository" '
        rm -r "$GIT_DIR/svn" "$GIT_DIR/refs/remotes" "$GIT_DIR/logs" &&
        mkdir "$GIT_DIR/svn" &&
-       git-svn multi-fetch
+       git svn multi-fetch
        '
 
 test_debug 'gitk --all &'
index 63230367bb1566384e66e1b5ddd6a68e1ae98c8f..ba99abb6d975351cf2725e757a588a26109a9723 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 #
 # Copyright (c) 2006 Eric Wong
-test_description='git-svn commit-diff'
+test_description='git svn commit-diff'
 . ./lib-git-svn.sh
 
 test_expect_success 'initialize repo' '
@@ -26,16 +26,16 @@ prev=`git rev-parse --verify HEAD^1`
 
 test_expect_success 'test the commit-diff command' '
        test -n "$prev" && test -n "$head" &&
-       git-svn commit-diff -r1 "$prev" "$head" "$svnrepo" &&
+       git svn commit-diff -r1 "$prev" "$head" "$svnrepo" &&
        svn co "$svnrepo" wc &&
        cmp readme wc/readme
        '
 
-test_expect_success 'commit-diff to a sub-directory (with git-svn config)' '
+test_expect_success 'commit-diff to a sub-directory (with git svn config)' '
        svn import -m "sub-directory" import "$svnrepo"/subdir &&
-       git-svn init --minimize-url "$svnrepo"/subdir &&
-       git-svn fetch &&
-       git-svn commit-diff -r3 "$prev" "$head" &&
+       git svn init --minimize-url "$svnrepo"/subdir &&
+       git svn fetch &&
+       git svn commit-diff -r3 "$prev" "$head" &&
        svn cat "$svnrepo"/subdir/readme > readme.2 &&
        cmp readme readme.2
        '
index 83896e96876d8cca24496c7cb278732a308e3d92..6eb0fd85c86ec194062af7da8c7002f0819bcc07 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 #
 # Copyright (c) 2006 Eric Wong
-test_description='git-svn commit-diff clobber'
+test_description='git svn commit-diff clobber'
 . ./lib-git-svn.sh
 
 test_expect_success 'initialize repo' '
@@ -27,7 +27,7 @@ test_expect_success 'commit change from svn side' '
 test_expect_success 'commit conflicting change from git' '
        echo second line from git >> file &&
        git commit -a -m "second line from git" &&
-       test_must_fail git-svn commit-diff -r1 HEAD~1 HEAD "$svnrepo"
+       test_must_fail git svn commit-diff -r1 HEAD~1 HEAD "$svnrepo"
 '
 
 test_expect_success 'commit complementing change from git' '
@@ -36,13 +36,13 @@ test_expect_success 'commit complementing change from git' '
        git commit -a -m "second line from svn" &&
        echo third line from git >> file &&
        git commit -a -m "third line from git" &&
-       git-svn commit-diff -r2 HEAD~1 HEAD "$svnrepo"
+       git svn commit-diff -r2 HEAD~1 HEAD "$svnrepo"
        '
 
 test_expect_success 'dcommit fails to commit because of conflict' '
-       git-svn init "$svnrepo" &&
-       git-svn fetch &&
-       git reset --hard refs/remotes/git-svn &&
+       git svn init "$svnrepo" &&
+       git svn fetch &&
+       git reset --hard refs/${remotes_git_svn} &&
        svn co "$svnrepo" t.svn &&
        cd t.svn &&
        echo fourth line from svn >> file &&
@@ -52,18 +52,18 @@ test_expect_success 'dcommit fails to commit because of conflict' '
        rm -rf t.svn &&
        echo "fourth line from git" >> file &&
        git commit -a -m "fourth line from git" &&
-       test_must_fail git-svn dcommit
+       test_must_fail git svn dcommit
        '
 
 test_expect_success 'dcommit does the svn equivalent of an index merge' "
-       git reset --hard refs/remotes/git-svn &&
+       git reset --hard refs/${remotes_git_svn} &&
        echo 'index merge' > file2 &&
        git update-index --add file2 &&
        git commit -a -m 'index merge' &&
        echo 'more changes' >> file2 &&
        git update-index file2 &&
        git commit -a -m 'more changes' &&
-       git-svn dcommit
+       git svn dcommit
        "
 
 test_expect_success 'commit another change from svn side' '
@@ -76,8 +76,8 @@ test_expect_success 'commit another change from svn side' '
        rm -rf t.svn
        '
 
-test_expect_success 'multiple dcommit from git-svn will not clobber svn' "
-       git reset --hard refs/remotes/git-svn &&
+test_expect_success 'multiple dcommit from git svn will not clobber svn' "
+       git reset --hard refs/${remotes_git_svn} &&
        echo new file >> new-file &&
        git update-index --add new-file &&
        git commit -a -m 'new file' &&
index bc37db9d62071ba92463276524675964c3e91593..fd185011b73a8e45b7863bc336f884dee8d4f980 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 #
 # Copyright (c) 2007 Eric Wong
-test_description='git-svn dcommit clobber series'
+test_description='git svn dcommit clobber series'
 . ./lib-git-svn.sh
 
 test_expect_success 'initialize repo' '
index d9b553ad55b1f7024af0689a450a9c6c65dcb034..acad16a6f0f9b3b45b4766474e15ee5019ec2ce2 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 # Copyright (c) 2006 Eric Wong
-test_description='git-svn metadata migrations from previous versions'
+test_description='git svn metadata migrations from previous versions'
 . ./lib-git-svn.sh
 
 test_expect_success 'setup old-looking metadata' '
@@ -14,34 +14,34 @@ test_expect_success 'setup old-looking metadata' '
                done && \
                svn import -m test . "$svnrepo"
                cd .. &&
-       git-svn init "$svnrepo" &&
-       git-svn fetch &&
+       git svn init "$svnrepo" &&
+       git svn fetch &&
        mv "$GIT_DIR"/svn/* "$GIT_DIR"/ &&
        mv "$GIT_DIR"/svn/.metadata "$GIT_DIR"/ &&
        rmdir "$GIT_DIR"/svn &&
-       git update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn &&
-       git update-ref refs/heads/svn-HEAD refs/remotes/git-svn &&
-       git update-ref -d refs/remotes/git-svn refs/remotes/git-svn
+       git update-ref refs/heads/git-svn-HEAD refs/${remotes_git_svn} &&
+       git update-ref refs/heads/svn-HEAD refs/${remotes_git_svn} &&
+       git update-ref -d refs/${remotes_git_svn} refs/${remotes_git_svn}
        '
 
 head=`git rev-parse --verify refs/heads/git-svn-HEAD^0`
 test_expect_success 'git-svn-HEAD is a real HEAD' "test -n '$head'"
 
-test_expect_success 'initialize old-style (v0) git-svn layout' '
+test_expect_success 'initialize old-style (v0) git svn layout' '
        mkdir -p "$GIT_DIR"/git-svn/info "$GIT_DIR"/svn/info &&
        echo "$svnrepo" > "$GIT_DIR"/git-svn/info/url &&
        echo "$svnrepo" > "$GIT_DIR"/svn/info/url &&
-       git-svn migrate &&
-       ! test -d "$GIT_DIR"/git-svn &&
-       git rev-parse --verify refs/remotes/git-svn^0 &&
+       git svn migrate &&
+       ! test -d "$GIT_DIR"/git svn &&
+       git rev-parse --verify refs/${remotes_git_svn}^0 &&
        git rev-parse --verify refs/remotes/svn^0 &&
        test "$(git config --get svn-remote.svn.url)" = "$svnrepo" &&
        test `git config --get svn-remote.svn.fetch` = \
-             ":refs/remotes/git-svn"
+             ":refs/${remotes_git_svn}"
        '
 
 test_expect_success 'initialize a multi-repository repo' '
-       git-svn init "$svnrepo" -T trunk -t tags -b branches &&
+       git svn init "$svnrepo" -T trunk -t tags -b branches &&
        git config --get-all svn-remote.svn.fetch > fetch.out &&
        grep "^trunk:refs/remotes/trunk$" fetch.out &&
        test -n "`git config --get svn-remote.svn.branches \
@@ -61,7 +61,7 @@ test_expect_success 'initialize a multi-repository repo' '
 
 # refs should all be different, but the trees should all be the same:
 test_expect_success 'multi-fetch works on partial urls + paths' "
-       git-svn multi-fetch &&
+       git svn multi-fetch &&
        for i in trunk a b tags/0.1 tags/0.2 tags/0.3; do
                git rev-parse --verify refs/remotes/\$i^0 >> refs.out || exit 1;
            done &&
@@ -85,7 +85,7 @@ test_expect_success 'migrate --minimize on old inited layout' '
                ( mkdir -p "$GIT_DIR"/svn/$ref/info/ &&
                echo "$svnrepo"$path > "$GIT_DIR"/svn/$ref/info/url ) || exit 1;
        done &&
-       git-svn migrate --minimize &&
+       git svn migrate --minimize &&
        test -z "`git config -l |grep -v "^svn-remote\.git-svn\."`" &&
        git config --get-all svn-remote.svn.fetch > fetch.out &&
        grep "^trunk:refs/remotes/trunk$" fetch.out &&
@@ -94,11 +94,11 @@ test_expect_success 'migrate --minimize on old inited layout' '
        grep "^tags/0\.1:refs/remotes/tags/0\.1$" fetch.out &&
        grep "^tags/0\.2:refs/remotes/tags/0\.2$" fetch.out &&
        grep "^tags/0\.3:refs/remotes/tags/0\.3$" fetch.out
-       grep "^:refs/remotes/git-svn" fetch.out
+       grep "^:refs/${remotes_git_svn}" fetch.out
        '
 
 test_expect_success  ".rev_db auto-converted to .rev_map.UUID" '
-       git-svn fetch -i trunk &&
+       git svn fetch -i trunk &&
        test -z "$(ls "$GIT_DIR"/svn/trunk/.rev_db.* 2>/dev/null)" &&
        expect="$(ls "$GIT_DIR"/svn/trunk/.rev_map.*)" &&
        test -n "$expect" &&
@@ -106,7 +106,7 @@ test_expect_success  ".rev_db auto-converted to .rev_map.UUID" '
        convert_to_rev_db "$expect" "$rev_db" &&
        rm -f "$expect" &&
        test -f "$rev_db" &&
-       git-svn fetch -i trunk &&
+       git svn fetch -i trunk &&
        test -z "$(ls "$GIT_DIR"/svn/trunk/.rev_db.* 2>/dev/null)" &&
        test ! -e "$GIT_DIR"/svn/trunk/.rev_db &&
        test -f "$expect"
index 8b792a1370d093c88a4949e7d33da0085651af14..d8582b1aa5d178778623bfb4386a66f58e165c17 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 # Copyright (c) 2007 Eric Wong
-test_description='git-svn globbing refspecs'
+test_description='git svn globbing refspecs'
 . ./lib-git-svn.sh
 
 cat > expect.end <<EOF
@@ -46,7 +46,7 @@ test_expect_success 'test refspec globbing' '
                         "branches/*/src/a:refs/remotes/branches/*" &&
        git config --add svn-remote.svn.tags\
                         "tags/*/src/a:refs/remotes/tags/*" &&
-       git-svn multi-fetch &&
+       git svn multi-fetch &&
        git log --pretty=oneline refs/remotes/tags/end | \
            sed -e "s/^.\{41\}//" > output.end &&
        test_cmp expect.end output.end &&
@@ -74,7 +74,7 @@ test_expect_success 'test left-hand-side only globbing' '
                poke tags/end/src/b/readme &&
                svn commit -m "try to try"
        ) &&
-       git-svn fetch two &&
+       git svn fetch two &&
        test `git rev-list refs/remotes/two/tags/end | wc -l` -eq 6 &&
        test `git rev-list refs/remotes/two/branches/start | wc -l` -eq 3 &&
        test `git rev-parse refs/remotes/two/branches/start~2` = \
@@ -104,7 +104,7 @@ test_expect_success 'test disallow multi-globs' '
                poke tags/end/src/b/readme &&
                svn commit -m "try to try"
        ) &&
-       test_must_fail git-svn fetch three 2> stderr.three &&
+       test_must_fail git svn fetch three 2> stderr.three &&
        test_cmp expect.three stderr.three
        '
 
index 35837216526749727c4a64450a58e97d87441b07..8f79c3f251aed240e0ba59244a8cfd91db2369d2 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 # Copyright (c) 2007 Eric Wong
-test_description='git-svn globbing refspecs'
+test_description='git svn globbing refspecs'
 . ./lib-git-svn.sh
 
 cat > expect.end <<EOF
@@ -46,7 +46,7 @@ test_expect_success 'test refspec globbing' '
                         "branches/*/*/src/a:refs/remotes/branches/*/*" &&
        git config --add svn-remote.svn.tags\
                         "tags/*/src/a:refs/remotes/tags/*" &&
-       git-svn multi-fetch &&
+       git svn multi-fetch &&
        git log --pretty=oneline refs/remotes/tags/end | \
            sed -e "s/^.\{41\}//" > output.end &&
        test_cmp expect.end output.end &&
@@ -74,7 +74,7 @@ test_expect_success 'test left-hand-side only globbing' '
                poke tags/end/src/b/readme &&
                svn commit -m "try to try"
        ) &&
-       git-svn fetch two &&
+       git svn fetch two &&
        test `git rev-list refs/remotes/two/tags/end | wc -l` -eq 6 &&
        test `git rev-list refs/remotes/two/branches/v1/start | wc -l` -eq 3 &&
        test `git rev-parse refs/remotes/two/branches/v1/start~2` = \
@@ -123,7 +123,7 @@ test_expect_success 'test another branch' '
                         "branches/*/*:refs/remotes/four/branches/*/*" &&
        git config --add svn-remote.four.tags \
                         "tags/*:refs/remotes/four/tags/*" &&
-       git-svn fetch four &&
+       git svn fetch four &&
        test `git rev-list refs/remotes/four/tags/next | wc -l` -eq 5 &&
        test `git rev-list refs/remotes/four/branches/v2/start | wc -l` -eq 3 &&
        test `git rev-parse refs/remotes/four/branches/v2/start~2` = \
@@ -153,7 +153,7 @@ test_expect_success 'test disallow multiple globs' '
                poke tags/end/src/b/readme &&
                svn commit -m "try to try"
        ) &&
-       test_must_fail git-svn fetch three 2> stderr.three &&
+       test_must_fail git svn fetch three 2> stderr.three &&
        test_cmp expect.three stderr.three
        '
 
index 04d2a65c087de78fa8126b68774673532497276e..a06e4c5b8e3fa5d5c0c14afede47c630ccb07712 100755 (executable)
@@ -3,18 +3,18 @@
 # Copyright (c) 2007 Eric Wong
 #
 
-test_description='git-svn useSvmProps test'
+test_description='git svn useSvmProps test'
 
 . ./lib-git-svn.sh
 
 test_expect_success 'load svm repo' '
-       svnadmin load -q "$rawsvnrepo" < ../t9110/svm.dump &&
-       git-svn init --minimize-url -R arr -i bar "$svnrepo"/mirror/arr &&
-       git-svn init --minimize-url -R argh -i dir "$svnrepo"/mirror/argh &&
-       git-svn init --minimize-url -R argh -i e \
+       svnadmin load -q "$rawsvnrepo" < "$TEST_DIRECTORY"/t9110/svm.dump &&
+       git svn init --minimize-url -R arr -i bar "$svnrepo"/mirror/arr &&
+       git svn init --minimize-url -R argh -i dir "$svnrepo"/mirror/argh &&
+       git svn init --minimize-url -R argh -i e \
          "$svnrepo"/mirror/argh/a/b/c/d/e &&
        git config svn.useSvmProps true &&
-       git-svn fetch --all
+       git svn fetch --all
        '
 
 uuid=161ce429-a9dd-4828-af4a-52023f968c89
@@ -22,40 +22,40 @@ uuid=161ce429-a9dd-4828-af4a-52023f968c89
 bar_url=http://mayonaise/svnrepo/bar
 test_expect_success 'verify metadata for /bar' "
        git cat-file commit refs/remotes/bar | \
-          grep '^git-svn-id: $bar_url@12 $uuid$' &&
+          grep '^${git_svn_id}: $bar_url@12 $uuid$' &&
        git cat-file commit refs/remotes/bar~1 | \
-          grep '^git-svn-id: $bar_url@11 $uuid$' &&
+          grep '^${git_svn_id}: $bar_url@11 $uuid$' &&
        git cat-file commit refs/remotes/bar~2 | \
-          grep '^git-svn-id: $bar_url@10 $uuid$' &&
+          grep '^${git_svn_id}: $bar_url@10 $uuid$' &&
        git cat-file commit refs/remotes/bar~3 | \
-          grep '^git-svn-id: $bar_url@9 $uuid$' &&
+          grep '^${git_svn_id}: $bar_url@9 $uuid$' &&
        git cat-file commit refs/remotes/bar~4 | \
-          grep '^git-svn-id: $bar_url@6 $uuid$' &&
+          grep '^${git_svn_id}: $bar_url@6 $uuid$' &&
        git cat-file commit refs/remotes/bar~5 | \
-          grep '^git-svn-id: $bar_url@1 $uuid$'
+          grep '^${git_svn_id}: $bar_url@1 $uuid$'
        "
 
 e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e
 test_expect_success 'verify metadata for /dir/a/b/c/d/e' "
        git cat-file commit refs/remotes/e | \
-          grep '^git-svn-id: $e_url@1 $uuid$'
+          grep '^${git_svn_id}: $e_url@1 $uuid$'
        "
 
 dir_url=http://mayonaise/svnrepo/dir
 test_expect_success 'verify metadata for /dir' "
        git cat-file commit refs/remotes/dir | \
-          grep '^git-svn-id: $dir_url@2 $uuid$' &&
+          grep '^${git_svn_id}: $dir_url@2 $uuid$' &&
        git cat-file commit refs/remotes/dir~1 | \
-          grep '^git-svn-id: $dir_url@1 $uuid$'
+          grep '^${git_svn_id}: $dir_url@1 $uuid$'
        "
 
 test_expect_success 'find commit based on SVN revision number' "
-        git-svn find-rev r12 |
+        git svn find-rev r12 |
            grep `git rev-parse HEAD`
         "
 
 test_expect_success 'empty rebase' "
-       git-svn rebase
+       git svn rebase
        "
 
 test_done
index a8d74dcd3aba7c462d46ea33c722d4307d24bded..bd081c2ec39686196e7b2873610df9a6466355b3 100755 (executable)
@@ -3,17 +3,17 @@
 # Copyright (c) 2007 Eric Wong
 #
 
-test_description='git-svn useSvnsyncProps test'
+test_description='git svn useSvnsyncProps test'
 
 . ./lib-git-svn.sh
 
 test_expect_success 'load svnsync repo' '
-       svnadmin load -q "$rawsvnrepo" < ../t9111/svnsync.dump &&
-       git-svn init --minimize-url -R arr -i bar "$svnrepo"/bar &&
-       git-svn init --minimize-url -R argh -i dir "$svnrepo"/dir &&
-       git-svn init --minimize-url -R argh -i e "$svnrepo"/dir/a/b/c/d/e &&
+       svnadmin load -q "$rawsvnrepo" < "$TEST_DIRECTORY"/t9111/svnsync.dump &&
+       git svn init --minimize-url -R arr -i bar "$svnrepo"/bar &&
+       git svn init --minimize-url -R argh -i dir "$svnrepo"/dir &&
+       git svn init --minimize-url -R argh -i e "$svnrepo"/dir/a/b/c/d/e &&
        git config svn.useSvnsyncProps true &&
-       git-svn fetch --all
+       git svn fetch --all
        '
 
 uuid=161ce429-a9dd-4828-af4a-52023f968c89
@@ -21,31 +21,31 @@ uuid=161ce429-a9dd-4828-af4a-52023f968c89
 bar_url=http://mayonaise/svnrepo/bar
 test_expect_success 'verify metadata for /bar' "
        git cat-file commit refs/remotes/bar | \
-          grep '^git-svn-id: $bar_url@12 $uuid$' &&
+          grep '^${git_svn_id}: $bar_url@12 $uuid$' &&
        git cat-file commit refs/remotes/bar~1 | \
-          grep '^git-svn-id: $bar_url@11 $uuid$' &&
+          grep '^${git_svn_id}: $bar_url@11 $uuid$' &&
        git cat-file commit refs/remotes/bar~2 | \
-          grep '^git-svn-id: $bar_url@10 $uuid$' &&
+          grep '^${git_svn_id}: $bar_url@10 $uuid$' &&
        git cat-file commit refs/remotes/bar~3 | \
-          grep '^git-svn-id: $bar_url@9 $uuid$' &&
+          grep '^${git_svn_id}: $bar_url@9 $uuid$' &&
        git cat-file commit refs/remotes/bar~4 | \
-          grep '^git-svn-id: $bar_url@6 $uuid$' &&
+          grep '^${git_svn_id}: $bar_url@6 $uuid$' &&
        git cat-file commit refs/remotes/bar~5 | \
-          grep '^git-svn-id: $bar_url@1 $uuid$'
+          grep '^${git_svn_id}: $bar_url@1 $uuid$'
        "
 
 e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e
 test_expect_success 'verify metadata for /dir/a/b/c/d/e' "
        git cat-file commit refs/remotes/e | \
-          grep '^git-svn-id: $e_url@1 $uuid$'
+          grep '^${git_svn_id}: $e_url@1 $uuid$'
        "
 
 dir_url=http://mayonaise/svnrepo/dir
 test_expect_success 'verify metadata for /dir' "
        git cat-file commit refs/remotes/dir | \
-          grep '^git-svn-id: $dir_url@2 $uuid$' &&
+          grep '^${git_svn_id}: $dir_url@2 $uuid$' &&
        git cat-file commit refs/remotes/dir~1 | \
-          grep '^git-svn-id: $dir_url@1 $uuid$'
+          grep '^${git_svn_id}: $dir_url@1 $uuid$'
        "
 
 test_done
index d470a920e4864ab0c494da1261fe835ff80474eb..a61d6716d294a460c195ca36d33a9bed17aa5e33 100755 (executable)
@@ -42,6 +42,6 @@ EOF
 
 test_expect_success 'load svn dumpfile' 'svnadmin load "$rawsvnrepo" < dumpfile.svn'
 
-test_expect_success 'initialize git-svn' 'git-svn init "$svnrepo"'
-test_expect_success 'fetch revisions from svn' 'git-svn fetch'
+test_expect_success 'initialize git svn' 'git svn init "$svnrepo"'
+test_expect_success 'fetch revisions from svn' 'git svn fetch'
 test_done
index c2b24a439d8f7aee5431a40dfe1fdb5ec61a53f6..e9b6128b3fa5c3d0cbbe5abb7f3e55267263449d 100755 (executable)
@@ -8,7 +8,7 @@
 # daemon running on a users system if the test fails.
 # Not all git users will need to interact with SVN.
 
-test_description='git-svn dcommit new files over svn:// test'
+test_description='git svn dcommit new files over svn:// test'
 
 . ./lib-git-svn.sh
 
index 61d7781616eed4374c014cabd75a297c2baa348d..17b2855c4f308d463ea239f355549120dab4f80f 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2007 Eric Wong
 # Based on a script by Joakim Tjernlund <joakim.tjernlund@transmode.se>
 
-test_description='git-svn dcommit handles merges'
+test_description='git svn dcommit handles merges'
 
 . ./lib-git-svn.sh
 
index f0fbd3aff7e63f64f8ba388db805013c43b4b22c..9be7aefaeea0af4d988ca656b3df0008f04a58d3 100755 (executable)
@@ -3,12 +3,12 @@
 # Copyright (c) 2007 Eric Wong
 
 
-test_description='git-svn dcommit can commit renames of files with ugly names'
+test_description='git svn dcommit can commit renames of files with ugly names'
 
 . ./lib-git-svn.sh
 
 test_expect_success 'load repository with strange names' '
-       svnadmin load -q "$rawsvnrepo" < ../t9115/funky-names.dump &&
+       svnadmin load -q "$rawsvnrepo" < "$TEST_DIRECTORY"/t9115/funky-names.dump &&
        start_httpd gtk+
        '
 
@@ -75,7 +75,7 @@ test_expect_success 'make a commit to test rebase' '
                git svn dcommit
        '
 
-test_expect_success 'git-svn rebase works inside a fresh-cloned repository' '
+test_expect_success 'git svn rebase works inside a fresh-cloned repository' '
        cd test-rebase &&
                git svn rebase &&
                test -e test-rebase-main &&
index 4b2cc878f685e65b2ccd5d8153efb533320d6ee9..fd6d1d20463f2a4de1bd8ed739c0e977921e037c 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2007 Eric Wong
 #
 
-test_description='git-svn log tests'
+test_description='git svn log tests'
 . ./lib-git-svn.sh
 
 test_expect_success 'setup repository and import' '
@@ -16,8 +16,8 @@ test_expect_success 'setup repository and import' '
                done && \
                svn import -m test . "$svnrepo"
                cd .. &&
-       git-svn init "$svnrepo" -T trunk -b branches -t tags &&
-       git-svn fetch &&
+       git svn init "$svnrepo" -T trunk -b branches -t tags &&
+       git svn fetch &&
        git reset --hard trunk &&
        echo bye >> README &&
        git commit -a -m bye &&
index 7a689bb1cd1d9daa1f17c0dcaaafa4d68ebd78fa..dde46cd92fba24221393b15233ed248997161caf 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2007 Eric Wong
 #
 
-test_description='git-svn init/clone tests'
+test_description='git svn init/clone tests'
 
 . ./lib-git-svn.sh
 
index 43ceb75d59a9abb4375dc33b646e1f272dec20b3..7a7c12868758d6e3bd9d1f8ec4fe32c0663115f4 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2007 Eric Wong
 #
 
-test_description='git-svn funky branch names'
+test_description='git svn funky branch names'
 . ./lib-git-svn.sh
 
 # Abo-Uebernahme (Bug #994)
index 5fd36a148304e4532f4e1b30deac781028c68271..27dd7c273a4a1ae77a8e48b2e4b4ce1a3ae19a56 100755 (executable)
@@ -2,16 +2,14 @@
 #
 # Copyright (c) 2007 David D. Kilzer
 
-test_description='git-svn info'
+test_description='git svn info'
 
 . ./lib-git-svn.sh
 
-set -e
-
 # Tested with: svn, version 1.4.4 (r25188)
-v=`svn --version | sed -n -e 's/^svn, version \(1\.4\.[0-9]\).*$/\1/p'`
+v=`svn --version | sed -n -e 's/^svn, version \(1\.[0-9]*\.[0-9]*\).*$/\1/p'`
 case $v in
-1.4.*)
+1.[45].*)
        ;;
 *)
        say "skipping svn-info test (SVN version: $v not supported)"
@@ -36,6 +34,8 @@ ptouch() {
        ' "`svn info $2 | grep '^Text Last Updated:'`" "$1"
 }
 
+quoted_svnrepo="$(echo $svnrepo | sed 's/ /%20/')"
+
 test_expect_success 'setup repository and import' '
        mkdir info &&
        cd info &&
@@ -47,80 +47,92 @@ test_expect_success 'setup repository and import' '
                ln -s directory symlink-directory &&
                svn import -m "initial" . "$svnrepo" &&
        cd .. &&
+       svn co "$svnrepo" svnwc &&
+       cd svnwc &&
+               echo foo > foo &&
+               svn add foo &&
+               svn commit -m "change outside directory" &&
+               svn update &&
+       cd .. &&
        mkdir gitwc &&
        cd gitwc &&
-               git-svn init "$svnrepo" &&
-               git-svn fetch &&
+               git svn init "$svnrepo" &&
+               git svn fetch &&
        cd .. &&
-       svn co "$svnrepo" svnwc &&
-       ptouch svnwc/file gitwc/file &&
-       ptouch svnwc/directory gitwc/directory &&
-       ptouch svnwc/symlink-file gitwc/symlink-file &&
-       ptouch svnwc/symlink-directory gitwc/symlink-directory
+       ptouch gitwc/file svnwc/file &&
+       ptouch gitwc/directory svnwc/directory &&
+       ptouch gitwc/symlink-file svnwc/symlink-file &&
+       ptouch gitwc/symlink-directory svnwc/symlink-directory
        '
 
 test_expect_success 'info' "
        (cd svnwc; svn info) > expected.info &&
-       (cd gitwc; git-svn info) > actual.info &&
-       git-diff expected.info actual.info
+       (cd gitwc; git svn info) > actual.info &&
+       test_cmp expected.info actual.info
        "
 
 test_expect_success 'info --url' '
-       test "$(cd gitwc; git-svn info --url)" = "$svnrepo"
+       test "$(cd gitwc; git svn info --url)" = "$quoted_svnrepo"
        '
 
 test_expect_success 'info .' "
        (cd svnwc; svn info .) > expected.info-dot &&
-       (cd gitwc; git-svn info .) > actual.info-dot &&
-       git-diff expected.info-dot actual.info-dot
+       (cd gitwc; git svn info .) > actual.info-dot &&
+       test_cmp expected.info-dot actual.info-dot
        "
 
 test_expect_success 'info --url .' '
-       test "$(cd gitwc; git-svn info --url .)" = "$svnrepo"
+       test "$(cd gitwc; git svn info --url .)" = "$quoted_svnrepo"
        '
 
 test_expect_success 'info file' "
        (cd svnwc; svn info file) > expected.info-file &&
-       (cd gitwc; git-svn info file) > actual.info-file &&
-       git-diff expected.info-file actual.info-file
+       (cd gitwc; git svn info file) > actual.info-file &&
+       test_cmp expected.info-file actual.info-file
        "
 
 test_expect_success 'info --url file' '
-       test "$(cd gitwc; git-svn info --url file)" = "$svnrepo/file"
+       test "$(cd gitwc; git svn info --url file)" = "$quoted_svnrepo/file"
        '
 
 test_expect_success 'info directory' "
        (cd svnwc; svn info directory) > expected.info-directory &&
-       (cd gitwc; git-svn info directory) > actual.info-directory &&
-       git-diff expected.info-directory actual.info-directory
+       (cd gitwc; git svn info directory) > actual.info-directory &&
+       test_cmp expected.info-directory actual.info-directory
+       "
+
+test_expect_success 'info inside directory' "
+       (cd svnwc/directory; svn info) > expected.info-inside-directory &&
+       (cd gitwc/directory; git svn info) > actual.info-inside-directory &&
+       test_cmp expected.info-inside-directory actual.info-inside-directory
        "
 
 test_expect_success 'info --url directory' '
-       test "$(cd gitwc; git-svn info --url directory)" = "$svnrepo/directory"
+       test "$(cd gitwc; git svn info --url directory)" = "$quoted_svnrepo/directory"
        '
 
 test_expect_success 'info symlink-file' "
        (cd svnwc; svn info symlink-file) > expected.info-symlink-file &&
-       (cd gitwc; git-svn info symlink-file) > actual.info-symlink-file &&
-       git-diff expected.info-symlink-file actual.info-symlink-file
+       (cd gitwc; git svn info symlink-file) > actual.info-symlink-file &&
+       test_cmp expected.info-symlink-file actual.info-symlink-file
        "
 
 test_expect_success 'info --url symlink-file' '
-       test "$(cd gitwc; git-svn info --url symlink-file)" \
-            = "$svnrepo/symlink-file"
+       test "$(cd gitwc; git svn info --url symlink-file)" \
+            = "$quoted_svnrepo/symlink-file"
        '
 
 test_expect_success 'info symlink-directory' "
        (cd svnwc; svn info symlink-directory) \
                > expected.info-symlink-directory &&
-       (cd gitwc; git-svn info symlink-directory) \
+       (cd gitwc; git svn info symlink-directory) \
                > actual.info-symlink-directory &&
-       git-diff expected.info-symlink-directory actual.info-symlink-directory
+       test_cmp expected.info-symlink-directory actual.info-symlink-directory
        "
 
 test_expect_success 'info --url symlink-directory' '
-       test "$(cd gitwc; git-svn info --url symlink-directory)" \
-            = "$svnrepo/symlink-directory"
+       test "$(cd gitwc; git svn info --url symlink-directory)" \
+            = "$quoted_svnrepo/symlink-directory"
        '
 
 test_expect_success 'info added-file' "
@@ -134,13 +146,13 @@ test_expect_success 'info added-file' "
                svn add added-file > /dev/null &&
        cd .. &&
        (cd svnwc; svn info added-file) > expected.info-added-file &&
-       (cd gitwc; git-svn info added-file) > actual.info-added-file &&
-       git-diff expected.info-added-file actual.info-added-file
+       (cd gitwc; git svn info added-file) > actual.info-added-file &&
+       test_cmp expected.info-added-file actual.info-added-file
        "
 
 test_expect_success 'info --url added-file' '
-       test "$(cd gitwc; git-svn info --url added-file)" \
-            = "$svnrepo/added-file"
+       test "$(cd gitwc; git svn info --url added-file)" \
+            = "$quoted_svnrepo/added-file"
        '
 
 test_expect_success 'info added-directory' "
@@ -155,14 +167,14 @@ test_expect_success 'info added-directory' "
        cd .. &&
        (cd svnwc; svn info added-directory) \
                > expected.info-added-directory &&
-       (cd gitwc; git-svn info added-directory) \
+       (cd gitwc; git svn info added-directory) \
                > actual.info-added-directory &&
-       git-diff expected.info-added-directory actual.info-added-directory
+       test_cmp expected.info-added-directory actual.info-added-directory
        "
 
 test_expect_success 'info --url added-directory' '
-       test "$(cd gitwc; git-svn info --url added-directory)" \
-            = "$svnrepo/added-directory"
+       test "$(cd gitwc; git svn info --url added-directory)" \
+            = "$quoted_svnrepo/added-directory"
        '
 
 test_expect_success 'info added-symlink-file' "
@@ -177,15 +189,15 @@ test_expect_success 'info added-symlink-file' "
        ptouch gitwc/added-symlink-file svnwc/added-symlink-file &&
        (cd svnwc; svn info added-symlink-file) \
                > expected.info-added-symlink-file &&
-       (cd gitwc; git-svn info added-symlink-file) \
+       (cd gitwc; git svn info added-symlink-file) \
                > actual.info-added-symlink-file &&
-       git-diff expected.info-added-symlink-file \
+       test_cmp expected.info-added-symlink-file \
                 actual.info-added-symlink-file
        "
 
 test_expect_success 'info --url added-symlink-file' '
-       test "$(cd gitwc; git-svn info --url added-symlink-file)" \
-            = "$svnrepo/added-symlink-file"
+       test "$(cd gitwc; git svn info --url added-symlink-file)" \
+            = "$quoted_svnrepo/added-symlink-file"
        '
 
 test_expect_success 'info added-symlink-directory' "
@@ -200,15 +212,15 @@ test_expect_success 'info added-symlink-directory' "
        ptouch gitwc/added-symlink-directory svnwc/added-symlink-directory &&
        (cd svnwc; svn info added-symlink-directory) \
                > expected.info-added-symlink-directory &&
-       (cd gitwc; git-svn info added-symlink-directory) \
+       (cd gitwc; git svn info added-symlink-directory) \
                > actual.info-added-symlink-directory &&
-       git-diff expected.info-added-symlink-directory \
+       test_cmp expected.info-added-symlink-directory \
                 actual.info-added-symlink-directory
        "
 
 test_expect_success 'info --url added-symlink-directory' '
-       test "$(cd gitwc; git-svn info --url added-symlink-directory)" \
-            = "$svnrepo/added-symlink-directory"
+       test "$(cd gitwc; git svn info --url added-symlink-directory)" \
+            = "$quoted_svnrepo/added-symlink-directory"
        '
 
 # The next few tests replace the "Text Last Updated" value with a
@@ -226,15 +238,15 @@ test_expect_success 'info deleted-file' "
        (cd svnwc; svn info file) |
        sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
                > expected.info-deleted-file &&
-       (cd gitwc; git-svn info file) |
+       (cd gitwc; git svn info file) |
        sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
                > actual.info-deleted-file &&
-       git-diff expected.info-deleted-file actual.info-deleted-file
+       test_cmp expected.info-deleted-file actual.info-deleted-file
        "
 
 test_expect_success 'info --url file (deleted)' '
-       test "$(cd gitwc; git-svn info --url file)" \
-            = "$svnrepo/file"
+       test "$(cd gitwc; git svn info --url file)" \
+            = "$quoted_svnrepo/file"
        '
 
 test_expect_success 'info deleted-directory' "
@@ -247,15 +259,15 @@ test_expect_success 'info deleted-directory' "
        (cd svnwc; svn info directory) |
        sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
                > expected.info-deleted-directory &&
-       (cd gitwc; git-svn info directory) |
+       (cd gitwc; git svn info directory) |
        sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
                > actual.info-deleted-directory &&
-       git-diff expected.info-deleted-directory actual.info-deleted-directory
+       test_cmp expected.info-deleted-directory actual.info-deleted-directory
        "
 
 test_expect_success 'info --url directory (deleted)' '
-       test "$(cd gitwc; git-svn info --url directory)" \
-            = "$svnrepo/directory"
+       test "$(cd gitwc; git svn info --url directory)" \
+            = "$quoted_svnrepo/directory"
        '
 
 test_expect_success 'info deleted-symlink-file' "
@@ -268,16 +280,16 @@ test_expect_success 'info deleted-symlink-file' "
        (cd svnwc; svn info symlink-file) |
        sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
                > expected.info-deleted-symlink-file &&
-       (cd gitwc; git-svn info symlink-file) |
+       (cd gitwc; git svn info symlink-file) |
        sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
                > actual.info-deleted-symlink-file &&
-       git-diff expected.info-deleted-symlink-file \
+       test_cmp expected.info-deleted-symlink-file \
                 actual.info-deleted-symlink-file
        "
 
 test_expect_success 'info --url symlink-file (deleted)' '
-       test "$(cd gitwc; git-svn info --url symlink-file)" \
-            = "$svnrepo/symlink-file"
+       test "$(cd gitwc; git svn info --url symlink-file)" \
+            = "$quoted_svnrepo/symlink-file"
        '
 
 test_expect_success 'info deleted-symlink-directory' "
@@ -290,16 +302,16 @@ test_expect_success 'info deleted-symlink-directory' "
        (cd svnwc; svn info symlink-directory) |
        sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
                 > expected.info-deleted-symlink-directory &&
-       (cd gitwc; git-svn info symlink-directory) |
+       (cd gitwc; git svn info symlink-directory) |
        sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
                 > actual.info-deleted-symlink-directory &&
-       git-diff expected.info-deleted-symlink-directory \
+       test_cmp expected.info-deleted-symlink-directory \
                 actual.info-deleted-symlink-directory
        "
 
 test_expect_success 'info --url symlink-directory (deleted)' '
-       test "$(cd gitwc; git-svn info --url symlink-directory)" \
-            = "$svnrepo/symlink-directory"
+       test "$(cd gitwc; git svn info --url symlink-directory)" \
+            = "$quoted_svnrepo/symlink-directory"
        '
 
 # NOTE: git does not have the concept of replaced objects,
@@ -307,82 +319,59 @@ test_expect_success 'info --url symlink-directory (deleted)' '
 
 test_expect_success 'info unknown-file' "
        echo two > gitwc/unknown-file &&
-       cp gitwc/unknown-file svnwc/unknown-file &&
-       ptouch gitwc/unknown-file svnwc/unknown-file &&
-       (cd svnwc; svn info unknown-file) 2> expected.info-unknown-file &&
-       (cd gitwc; git-svn info unknown-file) 2> actual.info-unknown-file &&
-       git-diff expected.info-unknown-file actual.info-unknown-file
+       (cd gitwc; test_must_fail git svn info unknown-file) \
+                2> actual.info-unknown-file &&
+       grep unknown-file actual.info-unknown-file
        "
 
 test_expect_success 'info --url unknown-file' '
-       test -z "$(cd gitwc; git-svn info --url unknown-file \
-                       2> ../actual.info--url-unknown-file)" &&
-       git-diff expected.info-unknown-file actual.info--url-unknown-file
+       echo two > gitwc/unknown-file &&
+       (cd gitwc; test_must_fail git svn info --url unknown-file) \
+                2> actual.info-url-unknown-file &&
+       grep unknown-file actual.info-url-unknown-file
        '
 
 test_expect_success 'info unknown-directory' "
        mkdir gitwc/unknown-directory svnwc/unknown-directory &&
-       ptouch gitwc/unknown-directory svnwc/unknown-directory &&
-       touch gitwc/unknown-directory/.placeholder &&
-       (cd svnwc; svn info unknown-directory) \
-               2> expected.info-unknown-directory &&
-       (cd gitwc; git-svn info unknown-directory) \
-               2> actual.info-unknown-directory &&
-       git-diff expected.info-unknown-directory actual.info-unknown-directory
+       (cd gitwc; test_must_fail git svn info unknown-directory) \
+                2> actual.info-unknown-directory &&
+       grep unknown-directory actual.info-unknown-directory
        "
 
 test_expect_success 'info --url unknown-directory' '
-       test -z "$(cd gitwc; git-svn info --url unknown-directory \
-                       2> ../actual.info--url-unknown-directory)" &&
-       git-diff expected.info-unknown-directory \
-                actual.info--url-unknown-directory
+       (cd gitwc; test_must_fail git svn info --url unknown-directory) \
+                2> actual.info-url-unknown-directory &&
+       grep unknown-directory actual.info-url-unknown-directory
        '
 
 test_expect_success 'info unknown-symlink-file' "
        cd gitwc &&
                ln -s unknown-file unknown-symlink-file &&
        cd .. &&
-       cd svnwc &&
-               ln -s unknown-file unknown-symlink-file &&
-       cd .. &&
-       ptouch gitwc/unknown-symlink-file svnwc/unknown-symlink-file &&
-       (cd svnwc; svn info unknown-symlink-file) \
-               2> expected.info-unknown-symlink-file &&
-       (cd gitwc; git-svn info unknown-symlink-file) \
-               2> actual.info-unknown-symlink-file &&
-       git-diff expected.info-unknown-symlink-file \
-                actual.info-unknown-symlink-file
+       (cd gitwc; test_must_fail git svn info unknown-symlink-file) \
+                2> actual.info-unknown-symlink-file &&
+       grep unknown-symlink-file actual.info-unknown-symlink-file
        "
 
 test_expect_success 'info --url unknown-symlink-file' '
-       test -z "$(cd gitwc; git-svn info --url unknown-symlink-file \
-                       2> ../actual.info--url-unknown-symlink-file)" &&
-       git-diff expected.info-unknown-symlink-file \
-                actual.info--url-unknown-symlink-file
+       (cd gitwc; test_must_fail git svn info --url unknown-symlink-file) \
+                2> actual.info-url-unknown-symlink-file &&
+       grep unknown-symlink-file actual.info-url-unknown-symlink-file
        '
 
 test_expect_success 'info unknown-symlink-directory' "
        cd gitwc &&
                ln -s unknown-directory unknown-symlink-directory &&
        cd .. &&
-       cd svnwc &&
-               ln -s unknown-directory unknown-symlink-directory &&
-       cd .. &&
-       ptouch gitwc/unknown-symlink-directory \
-              svnwc/unknown-symlink-directory &&
-       (cd svnwc; svn info unknown-symlink-directory) \
-               2> expected.info-unknown-symlink-directory &&
-       (cd gitwc; git-svn info unknown-symlink-directory) \
-               2> actual.info-unknown-symlink-directory &&
-       git-diff expected.info-unknown-symlink-directory \
-                actual.info-unknown-symlink-directory
+       (cd gitwc; test_must_fail git svn info unknown-symlink-directory) \
+                2> actual.info-unknown-symlink-directory &&
+       grep unknown-symlink-directory actual.info-unknown-symlink-directory
        "
 
 test_expect_success 'info --url unknown-symlink-directory' '
-       test -z "$(cd gitwc; git-svn info --url unknown-symlink-directory \
-                       2> ../actual.info--url-unknown-symlink-directory)" &&
-       git-diff expected.info-unknown-symlink-directory \
-                actual.info--url-unknown-symlink-directory
+       (cd gitwc; test_must_fail git svn info --url unknown-symlink-directory) \
+                2> actual.info-url-unknown-symlink-directory &&
+       grep unknown-symlink-directory actual.info-url-unknown-symlink-directory
        '
 
 test_done
index 5979e133b9d5b9d85ddca31a40763ed4fb6feba3..ef2c0523cd4566b3d1c4925ba608efc16524546a 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2008 Kevin Ballard
 #
 
-test_description='git-svn clone with percent escapes'
+test_description='git svn clone with percent escapes'
 . ./lib-git-svn.sh
 
 test_expect_success 'setup svnrepo' '
@@ -21,7 +21,7 @@ else
        test_expect_success 'test clone with percent escapes' '
                git svn clone "$svnrepo/pr%20ject" clone &&
                cd clone &&
-                       git rev-parse refs/remotes/git-svn &&
+                       git rev-parse refs/${remotes_git_svn} &&
                cd ..
        '
 fi
index 99230b08107102836f752c14e1b0a67804b35ea3..000cad37c63d4d14756deab92a486e96612bce5d 100755 (executable)
@@ -3,12 +3,12 @@
 # Copyright (c) 2008 Santhosh Kumar Mani
 
 
-test_description='git-svn can fetch renamed directories'
+test_description='git svn can fetch renamed directories'
 
 . ./lib-git-svn.sh
 
 test_expect_success 'load repository with renamed directory' '
-       svnadmin load -q "$rawsvnrepo" < ../t9121/renamed-dir.dump
+       svnadmin load -q "$rawsvnrepo" < "$TEST_DIRECTORY"/t9121/renamed-dir.dump
        '
 
 test_expect_success 'init and fetch repository' '
index 1190576a658d08a680e177b748cfc5e69caa3ddb..1b1cf47281d10c64ac57e96701c727f19f189948 100755 (executable)
@@ -13,7 +13,7 @@ test_expect_success 'setup svn repository' '
        )
 '
 
-test_expect_success 'interact with it via git-svn' '
+test_expect_success 'interact with it via git svn' '
        mkdir work.git &&
        (
                cd work.git &&
index c18878fad16a6565fe846cc958417fea73289dce..cf0415274c2d2abae7a6850818f4de8063cb61a7 100755 (executable)
@@ -3,21 +3,21 @@
 # Copyright (c) 2008 Jan Krüger
 #
 
-test_description='git-svn respects rewriteRoot during rebuild'
+test_description='git svn respects rewriteRoot during rebuild'
 
 . ./lib-git-svn.sh
 
 mkdir import
 cd import
        touch foo
-       svn import -m 'import for git-svn' . "$svnrepo" >/dev/null
+       svn import -m 'import for git svn' . "$svnrepo" >/dev/null
 cd ..
 rm -rf import
 
 test_expect_success 'init, fetch and checkout repository' '
        git svn init --rewrite-root=http://invalid.invalid/ "$svnrepo" &&
        git svn fetch
-       git checkout -b mybranch remotes/git-svn
+       git checkout -b mybranch ${remotes_git_svn}
        '
 
 test_expect_success 'remove rev_map' '
index 8223c5909e6ff6936cb0ccf4d0badfe43491af46..263dbf5fc276ffa052096810f59565a851245b7f 100755 (executable)
@@ -2,7 +2,7 @@
 #
 # Copyright (c) 2008 Brad King
 
-test_description='git-svn dcommit honors auto-props'
+test_description='git svn dcommit honors auto-props'
 
 . ./lib-git-svn.sh
 
@@ -16,26 +16,24 @@ enable-auto-props=$1
 EOF
 }
 
-test_expect_success 'initialize git-svn' '
+test_expect_success 'initialize git svn' '
        mkdir import &&
        (
                cd import &&
                echo foo >foo &&
-               svn import -m "import for git-svn" . "$svnrepo"
+               svn import -m "import for git svn" . "$svnrepo"
        ) &&
        rm -rf import &&
-       git-svn init "$svnrepo"
-       git-svn fetch
+       git svn init "$svnrepo"
+       git svn fetch
 '
 
 test_expect_success 'enable auto-props config' '
-       cd "$gittestrepo" &&
        mkdir user &&
        generate_auto_props yes >user/config
 '
 
 test_expect_success 'add files matching auto-props' '
-       cd "$gittestrepo" &&
        echo "#!$SHELL_PATH" >exec1.sh &&
        chmod +x exec1.sh &&
        echo "hello" >hello.txt &&
@@ -46,12 +44,10 @@ test_expect_success 'add files matching auto-props' '
 '
 
 test_expect_success 'disable auto-props config' '
-       cd "$gittestrepo" &&
        generate_auto_props no >user/config
 '
 
 test_expect_success 'add files matching disabled auto-props' '
-       cd "$gittestrepo" &&
        echo "#$SHELL_PATH" >exec2.sh &&
        chmod +x exec2.sh &&
        echo "world" >world.txt &&
@@ -62,6 +58,7 @@ test_expect_success 'add files matching disabled auto-props' '
 '
 
 test_expect_success 'check resulting svn repository' '
+(
        mkdir work &&
        cd work &&
        svn co "$svnrepo" &&
@@ -81,6 +78,24 @@ test_expect_success 'check resulting svn repository' '
        test "x$(svn propget svn:mime-type world.txt)" = "x" &&
        test "x$(svn propget svn:eol-style world.txt)" = "x" &&
        test "x$(svn propget svn:mime-type zot)" = "x"
+)
+'
+
+test_expect_success 'check renamed file' '
+       test -d user &&
+       generate_auto_props yes > user/config &&
+       git mv foo foo.sh &&
+       git commit -m "foo => foo.sh" &&
+       git svn dcommit --config-dir=user &&
+       (
+               cd work/svnrepo &&
+               svn up &&
+               test ! -e foo &&
+               test -e foo.sh &&
+               test "x$(svn propget svn:mime-type foo.sh)" = \
+                    "xapplication/x-shellscript" &&
+               test "x$(svn propget svn:eol-style foo.sh)" = "xLF"
+       )
 '
 
 test_done
index 6b62b52f54498ce9a329047714b370edcc0681f2..475c751c1cb88ce92a18beb2f9c7362a29ae4a5b 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 # Copyright (c) 2008 Marcus Griep
 
-test_description='git-svn multi-glob branch names'
+test_description='git svn multi-glob branch names'
 . ./lib-git-svn.sh
 
 test_expect_success 'setup svnrepo' '
diff --git a/t/t9127-git-svn-partial-rebuild.sh b/t/t9127-git-svn-partial-rebuild.sh
new file mode 100755 (executable)
index 0000000..87696a9
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Deskin Miller
+#
+
+test_description='git svn partial-rebuild tests'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize svnrepo' '
+       mkdir import &&
+       (
+               cd import &&
+               mkdir trunk branches tags &&
+               cd trunk &&
+               echo foo > foo &&
+               cd .. &&
+               svn import -m "import for git-svn" . "$svnrepo" >/dev/null &&
+               svn copy "$svnrepo"/trunk "$svnrepo"/branches/a \
+                       -m "created branch a" &&
+               cd .. &&
+               rm -rf import &&
+               svn co "$svnrepo"/trunk trunk &&
+               cd trunk &&
+               echo bar >> foo &&
+               svn ci -m "updated trunk" &&
+               cd .. &&
+               svn co "$svnrepo"/branches/a a &&
+               cd a &&
+               echo baz >> a &&
+               svn add a &&
+               svn ci -m "updated a" &&
+               cd .. &&
+               git svn init --stdlayout "$svnrepo"
+       )
+'
+
+test_expect_success 'import an early SVN revision into git' '
+       git svn fetch -r1:2
+'
+
+test_expect_success 'make full git mirror of SVN' '
+       mkdir mirror &&
+       (
+               cd mirror &&
+               git init &&
+               git svn init --stdlayout "$svnrepo" &&
+               git svn fetch &&
+               cd ..
+       )
+'
+
+test_expect_success 'fetch from git mirror and partial-rebuild' '
+       git config --add remote.origin.url "file://$PWD/mirror/.git" &&
+       git config --add remote.origin.fetch refs/remotes/*:refs/remotes/* &&
+       git fetch origin &&
+       git svn fetch
+'
+
+test_done
index 3e32e84e6cd32413f98b5189f869bfb8f0a7f354..245a7c36628e955e04852a8c2beb75b8deaf4d7f 100755 (executable)
@@ -9,7 +9,7 @@ test_description='Test export of commits to CVS'
 cvs >/dev/null 2>&1
 if test $? -ne 1
 then
-    test_expect_success 'skipping git-cvsexportcommit tests, cvs not found' :
+    test_expect_success 'skipping git cvsexportcommit tests, cvs not found' :
     test_done
     exit
 fi
@@ -45,8 +45,8 @@ test_expect_success \
     'mkdir A B C D E F &&
      echo hello1 >A/newfile1.txt &&
      echo hello2 >B/newfile2.txt &&
-     cp ../test9200a.png C/newfile3.png &&
-     cp ../test9200a.png D/newfile4.png &&
+     cp "$TEST_DIRECTORY"/test9200a.png C/newfile3.png &&
+     cp "$TEST_DIRECTORY"/test9200a.png D/newfile4.png &&
      git add A/newfile1.txt &&
      git add B/newfile2.txt &&
      git add C/newfile3.png &&
@@ -71,8 +71,8 @@ test_expect_success \
      rm -f B/newfile2.txt &&
      rm -f C/newfile3.png &&
      echo Hello5  >E/newfile5.txt &&
-     cp ../test9200b.png D/newfile4.png &&
-     cp ../test9200a.png F/newfile6.png &&
+     cp "$TEST_DIRECTORY"/test9200b.png D/newfile4.png &&
+     cp "$TEST_DIRECTORY"/test9200a.png F/newfile6.png &&
      git add E/newfile5.txt &&
      git add F/newfile6.png &&
      git commit -a -m "Test: Remove, add and update" &&
@@ -91,7 +91,7 @@ test_expect_success \
      diff F/newfile6.png ../F/newfile6.png
      )'
 
-# Should fail (but only on the git-cvsexportcommit stage)
+# Should fail (but only on the git cvsexportcommit stage)
 test_expect_success \
     'Fail to change binary more than one generation old' \
     'cat F/newfile6.png >>D/newfile4.png &&
@@ -160,24 +160,24 @@ test_expect_success \
      'mkdir "G g" &&
       echo ok then >"G g/with spaces.txt" &&
       git add "G g/with spaces.txt" && \
-      cp ../test9200a.png "G g/with spaces.png" && \
+      cp "$TEST_DIRECTORY"/test9200a.png "G g/with spaces.png" && \
       git add "G g/with spaces.png" &&
       git commit -a -m "With spaces" &&
       id=$(git rev-list --max-count=1 HEAD) &&
       (cd "$CVSWORK" &&
-      git-cvsexportcommit -c $id &&
+      git cvsexportcommit -c $id &&
       check_entries "G g" "with spaces.png/1.1/-kb|with spaces.txt/1.1/"
       )'
 
 test_expect_success \
      'Update file with spaces in file name' \
      'echo Ok then >>"G g/with spaces.txt" &&
-      cat ../test9200a.png >>"G g/with spaces.png" && \
+      cat "$TEST_DIRECTORY"/test9200a.png >>"G g/with spaces.png" && \
       git add "G g/with spaces.png" &&
       git commit -a -m "Update with spaces" &&
       id=$(git rev-list --max-count=1 HEAD) &&
       (cd "$CVSWORK" &&
-      git-cvsexportcommit -c $id
+      git cvsexportcommit -c $id
       check_entries "G g" "with spaces.png/1.2/-kb|with spaces.txt/1.2/"
       )'
 
@@ -197,12 +197,12 @@ test_expect_success \
      'mkdir -p Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö &&
       echo Foo >Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt &&
       git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt &&
-      cp ../test9200a.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
+      cp "$TEST_DIRECTORY"/test9200a.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
       git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
       git commit -a -m "Går det så går det" && \
       id=$(git rev-list --max-count=1 HEAD) &&
       (cd "$CVSWORK" &&
-      git-cvsexportcommit -v -c $id &&
+      git cvsexportcommit -v -c $id &&
       check_entries \
       "Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö" \
       "gårdetsågårdet.png/1.1/-kb|gårdetsågårdet.txt/1.1/"
@@ -222,7 +222,7 @@ test_expect_success \
       git commit -a -m "Update two" &&
       id=$(git rev-list --max-count=1 HEAD) &&
       (cd "$CVSWORK" &&
-      test_must_fail git-cvsexportcommit -c $id
+      test_must_fail git cvsexportcommit -c $id
       )'
 
 case "$(git config --bool core.filemode)" in
@@ -239,7 +239,7 @@ test_expect_success \
       git add G/off &&
       git commit -a -m "Execute test" &&
       (cd "$CVSWORK" &&
-      git-cvsexportcommit -c HEAD
+      git cvsexportcommit -c HEAD
       test -x G/on &&
       ! test -x G/off
       )'
index c6bc0a607f200fcc8888b66ff1a8f0e324332db8..328444a3068f5083e3d64e92e88660c724acffdc 100755 (executable)
@@ -3,9 +3,9 @@
 # Copyright (c) 2007 Shawn Pearce
 #
 
-test_description='test git-fast-import utility'
+test_description='test git fast-import utility'
 . ./test-lib.sh
-. ../diff-lib.sh ;# test-lib chdir's into trash
+. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
 
 file2_data='file2
 second line of EOF'
@@ -59,7 +59,7 @@ M 755 :4 file4
 INPUT_END
 test_expect_success \
     'A: create pack from stdin' \
-    'git-fast-import --export-marks=marks.out <input &&
+    'git fast-import --export-marks=marks.out <input &&
         git whatchanged master'
 test_expect_success \
        'A: verify pack' \
@@ -113,7 +113,7 @@ test_expect_success \
 
 test_expect_success \
        'A: verify marks import' \
-       'git-fast-import \
+       'git fast-import \
                --import-marks=marks.out \
                --export-marks=marks.new \
                </dev/null &&
@@ -133,7 +133,7 @@ M 755 :2 copy-of-file2
 INPUT_END
 test_expect_success \
        'A: verify marks import does not crash' \
-       'git-fast-import --import-marks=marks.out <input &&
+       'git fast-import --import-marks=marks.out <input &&
         git whatchanged verify--import-marks'
 test_expect_success \
        'A: verify pack' \
@@ -166,7 +166,7 @@ M 755 0000000000000000000000000000000000000001 zero1
 
 INPUT_END
 test_expect_success 'B: fail on invalid blob sha1' '
-    test_must_fail git-fast-import <input
+    test_must_fail git fast-import <input
 '
 rm -f .git/objects/pack_* .git/objects/index_*
 
@@ -181,7 +181,7 @@ from refs/heads/master
 
 INPUT_END
 test_expect_success 'B: fail on invalid branch name ".badbranchname"' '
-    test_must_fail git-fast-import <input
+    test_must_fail git fast-import <input
 '
 rm -f .git/objects/pack_* .git/objects/index_*
 
@@ -196,7 +196,7 @@ from refs/heads/master
 
 INPUT_END
 test_expect_success 'B: fail on invalid branch name "bad[branch]name"' '
-    test_must_fail git-fast-import <input
+    test_must_fail git fast-import <input
 '
 rm -f .git/objects/pack_* .git/objects/index_*
 
@@ -212,7 +212,7 @@ from refs/heads/master
 INPUT_END
 test_expect_success \
     'B: accept branch name "TEMP_TAG"' \
-    'git-fast-import <input &&
+    'git fast-import <input &&
         test -f .git/TEMP_TAG &&
         test `git rev-parse master` = `git rev-parse TEMP_TAG^`'
 rm -f .git/TEMP_TAG
@@ -221,7 +221,7 @@ rm -f .git/TEMP_TAG
 ### series C
 ###
 
-newf=`echo hi newf | git-hash-object -w --stdin`
+newf=`echo hi newf | git hash-object -w --stdin`
 oldf=`git rev-parse --verify master:file2`
 test_tick
 cat >input <<INPUT_END
@@ -239,7 +239,7 @@ D file3
 INPUT_END
 test_expect_success \
     'C: incremental import create pack from stdin' \
-    'git-fast-import <input &&
+    'git fast-import <input &&
         git whatchanged branch'
 test_expect_success \
        'C: verify pack' \
@@ -297,7 +297,7 @@ EOF
 INPUT_END
 test_expect_success \
     'D: inline data in commit' \
-    'git-fast-import <input &&
+    'git fast-import <input &&
         git whatchanged branch'
 test_expect_success \
        'D: verify pack' \
@@ -340,11 +340,11 @@ from refs/heads/branch^0
 
 INPUT_END
 test_expect_success 'E: rfc2822 date, --date-format=raw' '
-    test_must_fail git-fast-import --date-format=raw <input
+    test_must_fail git fast-import --date-format=raw <input
 '
 test_expect_success \
     'E: rfc2822 date, --date-format=rfc2822' \
-    'git-fast-import --date-format=rfc2822 <input'
+    'git fast-import --date-format=rfc2822 <input'
 test_expect_success \
        'E: verify pack' \
        'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
@@ -381,7 +381,7 @@ from refs/heads/branch
 INPUT_END
 test_expect_success \
     'F: non-fast-forward update skips' \
-    'if git-fast-import <input
+    'if git fast-import <input
         then
                echo BAD gfi did not fail
                return 1
@@ -431,7 +431,7 @@ from refs/heads/branch~1
 INPUT_END
 test_expect_success \
     'G: non-fast-forward update forced' \
-    'git-fast-import --force <input'
+    'git fast-import --force <input'
 test_expect_success \
        'G: verify pack' \
        'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
@@ -467,7 +467,7 @@ EOF
 INPUT_END
 test_expect_success \
     'H: deletall, add 1' \
-    'git-fast-import <input &&
+    'git fast-import <input &&
         git whatchanged H'
 test_expect_success \
        'H: verify pack' \
@@ -507,7 +507,7 @@ from refs/heads/branch
 INPUT_END
 test_expect_success \
     'I: export-pack-edges' \
-    'git-fast-import --export-pack-edges=edges.list <input'
+    'git fast-import --export-pack-edges=edges.list <input'
 
 cat >expect <<EOF
 .git/objects/pack/pack-.pack: `git rev-parse --verify export-boundary`
@@ -541,7 +541,7 @@ COMMIT
 INPUT_END
 test_expect_success \
     'J: reset existing branch creates empty commit' \
-    'git-fast-import <input'
+    'git fast-import <input'
 test_expect_success \
        'J: branch has 1 commit, empty tree' \
        'test 1 = `git rev-list J | wc -l` &&
@@ -571,7 +571,7 @@ from refs/heads/branch^1
 INPUT_END
 test_expect_success \
     'K: reinit branch with from' \
-    'git-fast-import <input'
+    'git fast-import <input'
 test_expect_success \
     'K: verify K^1 = branch^1' \
     'test `git rev-parse --verify branch^1` \
@@ -623,7 +623,7 @@ EXPECT_END
 
 test_expect_success \
     'L: verify internal tree sorting' \
-       'git-fast-import <input &&
+       'git fast-import <input &&
         git diff-tree --abbrev --raw L^ L >output &&
         test_cmp expect output'
 
@@ -649,7 +649,7 @@ cat >expect <<EOF
 EOF
 test_expect_success \
        'M: rename file in same subdirectory' \
-       'git-fast-import <input &&
+       'git fast-import <input &&
         git diff-tree -M -r M1^ M1 >actual &&
         compare_diff_raw expect actual'
 
@@ -670,7 +670,7 @@ cat >expect <<EOF
 EOF
 test_expect_success \
        'M: rename file to new subdirectory' \
-       'git-fast-import <input &&
+       'git fast-import <input &&
         git diff-tree -M -r M2^ M2 >actual &&
         compare_diff_raw expect actual'
 
@@ -691,7 +691,7 @@ cat >expect <<EOF
 EOF
 test_expect_success \
        'M: rename subdirectory to new subdirectory' \
-       'git-fast-import <input &&
+       'git fast-import <input &&
         git diff-tree -M -r M3^ M3 >actual &&
         compare_diff_raw expect actual'
 
@@ -717,7 +717,7 @@ cat >expect <<EOF
 EOF
 test_expect_success \
        'N: copy file in same subdirectory' \
-       'git-fast-import <input &&
+       'git fast-import <input &&
         git diff-tree -C --find-copies-harder -r N1^ N1 >actual &&
         compare_diff_raw expect actual'
 
@@ -751,7 +751,7 @@ cat >expect <<EOF
 EOF
 test_expect_success \
        'N: copy then modify subdirectory' \
-       'git-fast-import <input &&
+       'git fast-import <input &&
         git diff-tree -C --find-copies-harder -r N2^^ N2 >actual &&
         compare_diff_raw expect actual'
 
@@ -775,8 +775,8 @@ INPUT_END
 
 test_expect_success \
        'N: copy dirty subdirectory' \
-       'git-fast-import <input &&
-        test `git-rev-parse N2^{tree}` = `git-rev-parse N3^{tree}`'
+       'git fast-import <input &&
+        test `git rev-parse N2^{tree}` = `git rev-parse N3^{tree}`'
 
 ###
 ### series O
@@ -815,8 +815,8 @@ INPUT_END
 
 test_expect_success \
        'O: comments are all skipped' \
-       'git-fast-import <input &&
-        test `git-rev-parse N3` = `git-rev-parse O1`'
+       'git fast-import <input &&
+        test `git rev-parse N3` = `git rev-parse O1`'
 
 cat >input <<INPUT_END
 commit refs/heads/O2
@@ -836,8 +836,8 @@ INPUT_END
 
 test_expect_success \
        'O: blank lines not necessary after data commands' \
-       'git-fast-import <input &&
-        test `git-rev-parse N3` = `git-rev-parse O2`'
+       'git fast-import <input &&
+        test `git rev-parse N3` = `git rev-parse O2`'
 
 test_expect_success \
        'O: repack before next test' \
@@ -881,7 +881,7 @@ commits
 INPUT_END
 test_expect_success \
        'O: blank lines not necessary after other commands' \
-       'git-fast-import <input &&
+       'git fast-import <input &&
         test 8 = `find .git/objects/pack -type f | wc -l` &&
         test `git rev-parse refs/tags/O3-2nd` = `git rev-parse O3^` &&
         git log --reverse --pretty=oneline O3 | sed s/^.*z// >actual &&
@@ -914,7 +914,7 @@ progress I'm done!
 INPUT_END
 test_expect_success \
        'O: progress outputs as requested by input' \
-       'git-fast-import <input >actual &&
+       'git fast-import <input >actual &&
         grep "progress " <input >expect &&
         test_cmp expect actual'
 
@@ -979,7 +979,7 @@ INPUT_END
 
 test_expect_success \
        'P: supermodule & submodule mix' \
-       'git-fast-import <input &&
+       'git fast-import <input &&
         git checkout subuse1 &&
         rm -rf sub && mkdir sub && cd sub &&
         git init &&
@@ -989,8 +989,8 @@ test_expect_success \
         git submodule init &&
         git submodule update'
 
-SUBLAST=$(git-rev-parse --verify sub)
-SUBPREV=$(git-rev-parse --verify sub^)
+SUBLAST=$(git rev-parse --verify sub)
+SUBPREV=$(git rev-parse --verify sub^)
 
 cat >input <<INPUT_END
 blob
@@ -1024,8 +1024,8 @@ test_expect_success \
        'P: verbatim SHA gitlinks' \
        'git branch -D sub &&
         git gc && git prune &&
-        git-fast-import <input &&
-        test $(git-rev-parse --verify subuse2) = $(git-rev-parse --verify subuse1)'
+        git fast-import <input &&
+        test $(git rev-parse --verify subuse2) = $(git rev-parse --verify subuse1)'
 
 test_tick
 cat >input <<INPUT_END
@@ -1045,7 +1045,7 @@ DATA
 INPUT_END
 
 test_expect_success 'P: fail on inline gitlink' '
-    test_must_fail git-fast-import <input'
+    test_must_fail git fast-import <input'
 
 test_tick
 cat >input <<INPUT_END
@@ -1068,6 +1068,6 @@ M 160000 :1 sub
 INPUT_END
 
 test_expect_success 'P: fail on blob mark in gitlink' '
-    test_must_fail git-fast-import <input'
+    test_must_fail git fast-import <input'
 
 test_done
index c19b4a2bab586b21da43c7a838ba85626f913568..6ddd7c19fd22d0092829b861900fe462c5ffe73f 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2007 Johannes E. Schindelin
 #
 
-test_description='git-fast-export'
+test_description='git fast-export'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
@@ -67,7 +67,7 @@ test_expect_success 'iso-8859-1' '
 
        git config i18n.commitencoding ISO-8859-1 &&
        # use author and committer name in ISO-8859-1 to match it.
-       . ../t3901-8859-1.txt &&
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
        test_tick &&
        echo rosten >file &&
        git commit -s -m den file &&
index ae7082be1d903e1f4d5758610d5166152f2847cc..46ba19be7da754ffa14321eabac9ba0a985e6d94 100755 (executable)
@@ -25,9 +25,9 @@ our \$site_name = "[localhost]";
 our \$site_header = "";
 our \$site_footer = "";
 our \$home_text = "indextext.html";
-our @stylesheets = ("file:///$safe_pwd/../../gitweb/gitweb.css");
-our \$logo = "file:///$safe_pwd/../../gitweb/git-logo.png";
-our \$favicon = "file:///$safe_pwd/../../gitweb/git-favicon.png";
+our @stylesheets = ("file:///$TEST_DIRECTORY/../gitweb/gitweb.css");
+our \$logo = "file:///$TEST_DIRECTORY/../gitweb/git-logo.png";
+our \$favicon = "file:///$TEST_DIRECTORY/../gitweb/git-favicon.png";
 our \$projects_list = "";
 our \$export_ok = "";
 our \$strict_export = "";
@@ -54,7 +54,7 @@ gitweb_run () {
        # written to web server logs, so we are not interested in that:
        # we are interested only in properly formatted errors/warnings
        rm -f gitweb.log &&
-       perl -- "$(pwd)/../../gitweb/gitweb.perl" \
+       perl -- "$TEST_DIRECTORY/../gitweb/gitweb.perl" \
                >/dev/null 2>gitweb.log &&
        if grep -q -s "^[[]" gitweb.log >/dev/null; then false; else true; fi
 
@@ -525,20 +525,20 @@ test_debug 'cat gitweb.log'
 
 test_expect_success \
        'encode(commit): utf8' \
-       '. ../t3901-utf8.txt &&
+       '. "$TEST_DIRECTORY"/t3901-utf8.txt &&
         echo "UTF-8" >> file &&
         git add file &&
-        git commit -F ../t3900/1-UTF-8.txt &&
+        git commit -F "$TEST_DIRECTORY"/t3900/1-UTF-8.txt &&
         gitweb_run "p=.git;a=commit"'
 test_debug 'cat gitweb.log'
 
 test_expect_success \
        'encode(commit): iso-8859-1' \
-       '. ../t3901-8859-1.txt &&
+       '. "$TEST_DIRECTORY"/t3901-8859-1.txt &&
         echo "ISO-8859-1" >> file &&
         git add file &&
         git config i18n.commitencoding ISO-8859-1 &&
-        git commit -F ../t3900/ISO-8859-1.txt &&
+        git commit -F "$TEST_DIRECTORY"/t3900/ISO-8859-1.txt &&
         git config --unset i18n.commitencoding &&
         gitweb_run "p=.git;a=commit"'
 test_debug 'cat gitweb.log'
index 0d7786a8c730d17fa194346f1da2978d23256da9..d2379e7f62a4da76791e65dbc2c70f4dfe14ff3b 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-cvsimport basic tests'
+test_description='git cvsimport basic tests'
 . ./test-lib.sh
 
 CVSROOT=$(pwd)/cvsroot
index 9706ee5773692bd8fcfbc9015ef062947c0a2da5..b81d5dfc340e050815ad9b2fd0d0636c529ce8d3 100755 (executable)
@@ -27,18 +27,18 @@ test_expect_success \
      echo "changed file 1" > file1 &&
      git commit -a -m "second commit" &&
 
-     git-config --add color.test.slot1 green &&
-     git-config --add test.string value &&
-     git-config --add test.dupstring value1 &&
-     git-config --add test.dupstring value2 &&
-     git-config --add test.booltrue true &&
-     git-config --add test.boolfalse no &&
-     git-config --add test.boolother other &&
-     git-config --add test.int 2k
+     git config --add color.test.slot1 green &&
+     git config --add test.string value &&
+     git config --add test.dupstring value1 &&
+     git config --add test.dupstring value2 &&
+     git config --add test.booltrue true &&
+     git config --add test.boolfalse no &&
+     git config --add test.boolother other &&
+     git config --add test.int 2k
      '
 
 test_external_without_stderr \
     'Perl API' \
-    perl ../t9700/test.pl
+    perl "$TEST_DIRECTORY"/t9700/test.pl
 
 test_done
index 504f95a47d733e10ca71d0a669cd51e1c6e3601c..697daf3ffd33c27654ce00f780acc2c6db5f9985 100755 (executable)
 BEGIN { use_ok('Git') }
 
 # set up
-our $repo_dir = "trash directory";
 our $abs_repo_dir = Cwd->cwd;
-die "this must be run by calling the t/t97* shell script(s)\n"
-    if basename(Cwd->cwd) ne $repo_dir;
 ok(our $r = Git->repository(Directory => "."), "open repository");
 
 # config
index 11c027571b44c429b4f6fdca88bff9c3360c7545..e2b106cb6a2337f873a7225670392b376f74c6e7 100644 (file)
@@ -406,7 +406,7 @@ test_create_repo () {
        error "bug in the test script: not 1 parameter to test-create-repo"
        owd=`pwd`
        repo="$1"
-       mkdir "$repo"
+       mkdir -p "$repo"
        cd "$repo" || error "Cannot setup test environment"
        "$GIT_EXEC_PATH/git" init "--template=$GIT_EXEC_PATH/templates/blt/" >&3 2>&4 ||
        error "cannot run git init -- have you built things yet?"
@@ -449,6 +449,11 @@ test_done () {
                # we will leave things as they are.
 
                say_color pass "passed all $msg"
+
+               test -d "$remove_trash" &&
+               cd "$(dirname "$remove_trash")" &&
+               rm -rf "$(basename "$remove_trash")"
+
                exit 0 ;;
 
        *)
@@ -485,7 +490,8 @@ fi
 . ../GIT-BUILD-OPTIONS
 
 # Test repository
-test="trash directory"
+test="trash directory.$(basename "$0" .sh)"
+test ! -z "$debug" || remove_trash="$TEST_DIRECTORY/$test"
 rm -fr "$test" || {
        trap - exit
        echo >&5 "FATAL: Cannot prepare test area"
index 71433d99978ddcc53c80fd6de4aee98871c6d8cf..f7db5d91103c09a8cdc9bdbd27de4b0d4a15ea73 100644 (file)
@@ -619,7 +619,7 @@ static struct ref *get_refs_via_connect(struct transport *transport)
        struct ref *refs;
 
        connect_setup(transport);
-       get_remote_heads(data->fd[0], &refs, 0, NULL, 0);
+       get_remote_heads(data->fd[0], &refs, 0, NULL, 0, NULL);
 
        return refs;
 }
@@ -652,7 +652,7 @@ static int fetch_refs_via_pack(struct transport *transport,
 
        if (!data->conn) {
                connect_setup(transport);
-               get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0);
+               get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL);
        }
 
        refs = fetch_pack(&args, data->fd, data->conn,
index 889e50f89fc24984f700d14f7033600fa9fdf642..7cf890f2433bf622fe8c18faa0b0c89be60ef1db 100644 (file)
@@ -22,12 +22,6 @@ static char wt_status_colors[][COLOR_MAXLEN] = {
        "\033[31m", /* WT_STATUS_NOBRANCH: red */
 };
 
-static const char use_add_msg[] =
-"use \"git add <file>...\" to update what will be committed";
-static const char use_add_rm_msg[] =
-"use \"git add/rm <file>...\" to update what will be committed";
-static const char use_add_to_include_msg[] =
-"use \"git add <file>...\" to include in what will be committed";
 enum untracked_status_type show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
 
 static int parse_status_slot(const char *var, int offset)
@@ -76,12 +70,24 @@ static void wt_status_print_cached_header(struct wt_status *s)
        color_fprintf_ln(s->fp, c, "#");
 }
 
-static void wt_status_print_header(struct wt_status *s,
-                                  const char *main, const char *sub)
+static void wt_status_print_dirty_header(struct wt_status *s,
+                                        int has_deleted)
 {
        const char *c = color(WT_STATUS_HEADER);
-       color_fprintf_ln(s->fp, c, "# %s:", main);
-       color_fprintf_ln(s->fp, c, "#   (%s)", sub);
+       color_fprintf_ln(s->fp, c, "# Changed but not updated:");
+       if (!has_deleted)
+               color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to update what will be committed)");
+       else
+               color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" to update what will be committed)");
+       color_fprintf_ln(s->fp, c, "#   (use \"git checkout -- <file>...\" to discard changes in working directory)");
+       color_fprintf_ln(s->fp, c, "#");
+}
+
+static void wt_status_print_untracked_header(struct wt_status *s)
+{
+       const char *c = color(WT_STATUS_HEADER);
+       color_fprintf_ln(s->fp, c, "# Untracked files:");
+       color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to include in what will be committed)");
        color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -166,14 +172,14 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q,
        struct wt_status *s = data;
        int i;
        if (q->nr) {
-               const char *msg = use_add_msg;
+               int has_deleted = 0;
                s->workdir_dirty = 1;
                for (i = 0; i < q->nr; i++)
                        if (q->queue[i]->status == DIFF_STATUS_DELETED) {
-                               msg = use_add_rm_msg;
+                               has_deleted = 1;
                                break;
                        }
-               wt_status_print_header(s, "Changed but not updated", msg);
+               wt_status_print_dirty_header(s, has_deleted);
        }
        for (i = 0; i < q->nr; i++)
                wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]);
@@ -291,8 +297,7 @@ static void wt_status_print_untracked(struct wt_status *s)
                }
                if (!shown_header) {
                        s->workdir_untracked = 1;
-                       wt_status_print_header(s, "Untracked files",
-                                              use_add_to_include_msg);
+                       wt_status_print_untracked_header(s);
                        shown_header = 1;
                }
                color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
index 61dc5c547019776b971dc89d009f628bbac134fd..944ad9887f5c94ca0e63f9a6c901447634f871ce 100644 (file)
@@ -1,5 +1,12 @@
 #include "cache.h"
 #include "xdiff-interface.h"
+#include "strbuf.h"
+
+struct xdiff_emit_state {
+       xdiff_emit_consume_fn consume;
+       void *consume_callback_data;
+       struct strbuf remainder;
+};
 
 static int parse_num(char **cp_p, int *num_p)
 {
@@ -55,13 +62,13 @@ static void consume_one(void *priv_, char *s, unsigned long size)
                unsigned long this_size;
                ep = memchr(s, '\n', size);
                this_size = (ep == NULL) ? size : (ep - s + 1);
-               priv->consume(priv, s, this_size);
+               priv->consume(priv->consume_callback_data, s, this_size);
                size -= this_size;
                s += this_size;
        }
 }
 
-int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
+static int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
 {
        struct xdiff_emit_state *priv = priv_;
        int i;
@@ -69,36 +76,22 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
        for (i = 0; i < nbuf; i++) {
                if (mb[i].ptr[mb[i].size-1] != '\n') {
                        /* Incomplete line */
-                       priv->remainder = xrealloc(priv->remainder,
-                                                  priv->remainder_size +
-                                                  mb[i].size);
-                       memcpy(priv->remainder + priv->remainder_size,
-                              mb[i].ptr, mb[i].size);
-                       priv->remainder_size += mb[i].size;
+                       strbuf_add(&priv->remainder, mb[i].ptr, mb[i].size);
                        continue;
                }
 
                /* we have a complete line */
-               if (!priv->remainder) {
+               if (!priv->remainder.len) {
                        consume_one(priv, mb[i].ptr, mb[i].size);
                        continue;
                }
-               priv->remainder = xrealloc(priv->remainder,
-                                          priv->remainder_size +
-                                          mb[i].size);
-               memcpy(priv->remainder + priv->remainder_size,
-                      mb[i].ptr, mb[i].size);
-               consume_one(priv, priv->remainder,
-                           priv->remainder_size + mb[i].size);
-               free(priv->remainder);
-               priv->remainder = NULL;
-               priv->remainder_size = 0;
+               strbuf_add(&priv->remainder, mb[i].ptr, mb[i].size);
+               consume_one(priv, priv->remainder.buf, priv->remainder.len);
+               strbuf_reset(&priv->remainder);
        }
-       if (priv->remainder) {
-               consume_one(priv, priv->remainder, priv->remainder_size);
-               free(priv->remainder);
-               priv->remainder = NULL;
-               priv->remainder_size = 0;
+       if (priv->remainder.len) {
+               consume_one(priv, priv->remainder.buf, priv->remainder.len);
+               strbuf_reset(&priv->remainder);
        }
        return 0;
 }
@@ -141,6 +134,25 @@ int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t co
        return xdl_diff(&a, &b, xpp, xecfg, xecb);
 }
 
+int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2,
+                 xdiff_emit_consume_fn fn, void *consume_callback_data,
+                 xpparam_t const *xpp,
+                 xdemitconf_t const *xecfg, xdemitcb_t *xecb)
+{
+       int ret;
+       struct xdiff_emit_state state;
+
+       memset(&state, 0, sizeof(state));
+       state.consume = fn;
+       state.consume_callback_data = consume_callback_data;
+       xecb->outf = xdiff_outf;
+       xecb->priv = &state;
+       strbuf_init(&state.remainder, 0);
+       ret = xdi_diff(mf1, mf2, xpp, xecfg, xecb);
+       strbuf_release(&state.remainder);
+       return ret;
+}
+
 int read_mmfile(mmfile_t *ptr, const char *filename)
 {
        struct stat st;
index f7f791d96b9a34ef0f08db4b007c5309b9adc3d6..558492ba38c50351a98f80c16e83a058ace849ab 100644 (file)
@@ -3,18 +3,13 @@
 
 #include "xdiff/xdiff.h"
 
-struct xdiff_emit_state;
-
 typedef void (*xdiff_emit_consume_fn)(void *, char *, unsigned long);
 
-struct xdiff_emit_state {
-       xdiff_emit_consume_fn consume;
-       char *remainder;
-       unsigned long remainder_size;
-};
-
 int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb);
-int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf);
+int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2,
+                 xdiff_emit_consume_fn fn, void *consume_callback_data,
+                 xpparam_t const *xpp,
+                 xdemitconf_t const *xecfg, xdemitcb_t *xecb);
 int parse_hunk_header(char *line, int len,
                      int *ob, int *on,
                      int *nb, int *nn);