Merge branch 'bc/master-diff-hunk-header-fix'
authorShawn O. Pearce <spearce@spearce.org>
Mon, 29 Sep 2008 18:04:20 +0000 (11:04 -0700)
committerShawn O. Pearce <spearce@spearce.org>
Mon, 29 Sep 2008 18:04:20 +0000 (11:04 -0700)
* bc/master-diff-hunk-header-fix:
  Clarify commit error message for unmerged files
  Use strchrnul() instead of strchr() plus manual workaround
  Use remove_path from dir.c instead of own implementation
  Add remove_path: a function to remove as much as possible of a path
  git-submodule: Fix "Unable to checkout" for the initial 'update'
  Clarify how the user can satisfy stash's 'dirty state' check.
  t4018-diff-funcname: test syntax of builtin xfuncname patterns
  t4018-diff-funcname: test syntax of builtin xfuncname patterns
  make "git remote" report multiple URLs
  diff hunk pattern: fix misconverted "\{" tex macro introducers
  diff: fix "multiple regexp" semantics to find hunk header comment
  diff: use extended regexp to find hunk headers
  diff: use extended regexp to find hunk headers
  diff.*.xfuncname which uses "extended" regex's for hunk header selection
  diff.c: associate a flag with each pattern and use it for compiling regex
  diff.c: return pattern entry pointer rather than just the hunk header pattern

Conflicts:
builtin-merge-recursive.c
t/t7201-co.sh
xdiff-interface.h

139 files changed:
Documentation/Makefile
Documentation/RelNotes-1.6.1.txt
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-apply.txt
Documentation/git-checkout.txt
Documentation/git-for-each-ref.txt
Documentation/git-merge.txt
Documentation/git-rebase.txt
Documentation/git-web--browse.txt
Documentation/pretty-formats.txt
INSTALL
Makefile
abspath.c
builtin-apply.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-http-fetch.c
builtin-log.c
builtin-merge-file.c
builtin-merge-recursive.c
builtin-merge.c
builtin-pack-objects.c
builtin-receive-pack.c [moved from receive-pack.c with 91% similarity]
builtin-revert.c
builtin-send-pack.c
builtin.h
cache.h
combine-diff.c
compat/mingw.c
config.mak.in
configure.ac
connect.c
contrib/completion/git-completion.bash
contrib/rerere-train.sh [new file with mode: 0755]
csum-file.c
daemon.c
diff-lib.c
diff-no-index.c
diff.c
diff.h
git-bisect.sh
git-compat-util.h
git-gui/.gitattributes [new file with mode: 0644]
git-gui/git-gui.sh
git-gui/lib/blame.tcl
git-gui/lib/commit.tcl
git-gui/lib/diff.tcl
git-gui/lib/encoding.tcl
git-gui/lib/mergetool.tcl
git-gui/lib/option.tcl
git-gui/po/de.po
git-submodule.sh
git-svn.perl
git-web--browse.sh
git.c
git.spec.in
gitweb/gitweb.perl
graph.c
graph.h
help.c
help.h
http-push.c
http.c
levenshtein.c [new file with mode: 0644]
levenshtein.h [new file with mode: 0644]
ll-merge.c
log-tree.c
log-tree.h
merge-recursive.c [new file with mode: 0644]
merge-recursive.h
object.h
pretty.c
refs.c
remote.c
remote.h
rerere.c
revision.c
revision.h
sha1_file.c
t/lib-git-svn.sh
t/t5300-pack-object.sh
t/t5510-fetch.sh
t/t6013-rev-list-reverse-parents.sh [new file with mode: 0755]
t/t6023-merge-file.sh
t/t6026-merge-attr.sh
t/t6030-bisect-porcelain.sh
t/t6300-for-each-ref.sh
t/t7201-co.sh
t/t7502-status.sh
t/t7603-merge-reduce-heads.sh
t/t7606-merge-custom.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/t9600-cvsimport.sh
t/t9700-perl-git.sh
transport.c
usage.c
wt-status.c
xdiff-interface.c
xdiff-interface.h
xdiff/xdiff.h
xdiff/xmerge.c

index 62269e3..ded0e40 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
index 609d4ca..421e569 100644 (file)
@@ -32,8 +32,19 @@ on.
 * 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".
@@ -54,6 +65,12 @@ on.
 * "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.
 
@@ -68,6 +85,9 @@ on.
 * "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.
 
@@ -115,6 +135,6 @@ release, unless otherwise noted.
 
 --
 exec >/var/tmp/1
-O=v1.6.0.1-266-gaf9552f
+O=v1.6.0.2-295-g34a5d35
 echo O=$(git describe master)
 git shortlog --no-merges $O..master ^maint
index 5dda70d..bbe38cc 100644 (file)
@@ -594,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'.
@@ -799,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
@@ -902,6 +927,14 @@ man.<tool>.path::
        Override the path for the given tool that may be used to
        display help in the 'man' format. See linkgit:git-help[1].
 
+merge.conflictstyle::
+       Specify the style in which conflicted hunks are written out to
+       working tree files upon merge.  The default is "merge", which
+       shows `<<<<<<<` conflict marker, change made by one side,
+       `=======` marker, change made by the other side, and then
+       `>>>>>>>` marker.  An alternate style, "diff3", adds `|||||||`
+       marker and the original text before `=======` marker.
+
 mergetool.<tool>.path::
        Override the path for the given tool.  This is useful in case
        your tool is not in the PATH.
index 6e26832..7788d4f 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.
index feb51f1..e726510 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 be54a02..82e154d 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git checkout' [-q] [-f] [--track | --no-track] [-b <new_branch> [-l]] [-m] [<branch>]
-'git checkout' [<tree-ish>] [--] <paths>...
+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
 
 DESCRIPTION
 -----------
@@ -27,14 +27,20 @@ the first namespace level.
 
 When <paths> are given, this command does *not* switch
 branches.  It updates the named paths in the working tree from
-the index file (i.e. it runs `git checkout-index -f -u`), or
-from a named commit.  In
-this case, the `-f` and `-b` options are meaningless and giving
+the index file, or from a named commit.  In
+this case, the `-b` options is meaningless and giving
 either of them results in an error.  <tree-ish> argument can be
 used to specify a specific tree-ish (i.e. commit, tag or tree)
 to update the index for the given paths before updating the
 working tree.
 
+The index may contain unmerged entries after a failed merge.  By
+default, if you try to check out such an entry from the index, the
+checkout operation will fail and nothing will be checked out.
+Using -f will ignore these unmerged entries.  The contents from a
+specific side of the merge can be checked out of the index by
+using --ours or --theirs.  With -m, changes made to the working tree
+file can be discarded to recreate the original conflicted merge result.
 
 OPTIONS
 -------
@@ -42,8 +48,17 @@ OPTIONS
        Quiet, suppress feedback messages.
 
 -f::
-       Proceed even if the index or the working tree differs
-       from HEAD.  This is used to throw away local changes.
+       When switching branches, proceed even if the index or the
+       working tree differs from HEAD.  This is used to throw away
+       local changes.
++
+When checking out paths from the index, do not fail upon unmerged
+entries; instead, unmerged entries are ignored.
+
+--ours::
+--theirs::
+       When checking out paths from the index, check out stage #2
+       ('ours') or #3 ('theirs') for unmerged paths.
 
 -b::
        Create a new branch named <new_branch> and start it at
@@ -84,7 +99,9 @@ exlicitly give a name with '-b' in such a case.
        based sha1 expressions such as "<branchname>@\{yesterday}".
 
 -m::
-       If you have local modifications to one or more files that
+--merge::
+       When switching branches,
+       if you have local modifications to one or more files that
        are different between the current branch and the branch to
        which you are switching, the command refuses to switch
        branches in order to preserve your modifications in context.
@@ -96,6 +113,16 @@ When a merge conflict happens, the index entries for conflicting
 paths are left unmerged, and you need to resolve the conflicts
 and mark the resolved paths with `git add` (or `git rm` if the merge
 should result in deletion of the path).
++
+When checking out paths from the index, this option lets you recreate
+the conflicted merge in the specified paths.
+
+--conflict=<style>::
+       The same as --merge option above, but changes the way the
+       conflicting hunks are presented, overriding the
+       merge.conflictstyle configuration variable.  Possible values are
+       "merge" (default) and "diff3" (in addition to what is shown by
+       "merge" style, shows the original contents).
 
 <new_branch>::
        Name for the new branch.
index ebd7c5f..5061d3e 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 685e1fe..1f30830 100644 (file)
@@ -119,6 +119,71 @@ When there are conflicts, these things happen:
    same and the index entries for them stay as they were,
    i.e. matching `HEAD`.
 
+HOW CONFLICTS ARE PRESENTED
+---------------------------
+
+During a merge, the working tree files are updated to reflect the result
+of the merge.  Among the changes made to the common ancestor's version,
+non-overlapping ones (that is, you changed an area of the file while the
+other side left that area intact, or vice versa) are incorporated in the
+final result verbatim.  When both sides made changes to the same area,
+however, git cannot randomly pick one side over the other, and asks you to
+resolve it by leaving what both sides did to that area.
+
+By default, git uses the same style as that is used by "merge" program
+from the RCS suite to present such a conflicted hunk, like this:
+
+------------
+Here are lines that are either unchanged from the common
+ancestor, or cleanly resolved because only one side changed.
+<<<<<<< yours:sample.txt
+Conflict resolution is hard;
+let's go shopping.
+=======
+Git makes conflict resolution easy.
+>>>>>>> theirs:sample.txt
+And here is another line that is cleanly resolved or unmodified.
+------------
+
+The area a pair of conflicting changes happened is marked with markers
+"`<<<<<<<`", "`=======`", and "`>>>>>>>`".  The part before the "`=======`"
+is typically your side, and the part after it is typically their side.
+
+The default format does not show what the original said in the conflicted
+area.  You cannot tell how many lines are deleted and replaced with the
+Barbie's remark by your side.  The only thing you can tell is that your
+side wants to say it is hard and you'd prefer to go shopping, while the
+other side wants to claim it is easy.
+
+An alternative style can be used by setting the "merge.conflictstyle"
+configuration variable to "diff3".  In "diff3" style, the above conflict
+may look like this:
+
+------------
+Here are lines that are either unchanged from the common
+ancestor, or cleanly resolved because only one side changed.
+<<<<<<< yours:sample.txt
+Conflict resolution is hard;
+let's go shopping.
+|||||||
+Conflict resolution is hard.
+=======
+Git makes conflict resolution easy.
+>>>>>>> theirs:sample.txt
+And here is another line that is cleanly resolved or unmodified.
+------------
+
+In addition to the "`<<<<<<<`", "`=======`", and "`>>>>>>>`" markers, it uses
+another "`|||||||`" marker that is followed by the original text.  You can
+tell that the original just stated a fact, and your side simply gave in to
+that statement and gave up, while the other side tried to have a more
+positive attitude.  You can sometimes come up with a better resolution by
+viewing the original.
+
+
+HOW TO RESOLVE CONFLICTS
+------------------------
+
 After seeing a conflict, you can do two things:
 
  * Decide not to merge.  The only clean-up you need are to reset
index 59c1b02..32f0f12 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 7f7a45b..278cf73 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 388d492..f18d33e 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
diff --git a/INSTALL b/INSTALL
index 2bae53f..a4fd862 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 615d1ba..e0c03c3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -294,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
@@ -358,6 +358,7 @@ 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
@@ -434,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
@@ -441,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
@@ -543,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
@@ -701,8 +705,7 @@ ifeq ($(uname_S),NetBSD)
                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)
@@ -799,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
@@ -840,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
@@ -1268,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
 
@@ -1371,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 git-shell$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
@@ -1404,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 0d56124..8194ce1 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 70c9f93..e2c611b 100644 (file)
@@ -2987,29 +2987,45 @@ static int write_out_results(struct patch *list, int skipped_patch)
 
 static struct lock_file lock_file;
 
-static struct excludes {
-       struct excludes *next;
-       const char *path;
-} *excludes;
+static struct string_list limit_by_name;
+static int has_include;
+static void add_name_limit(const char *name, int exclude)
+{
+       struct string_list_item *it;
+
+       it = string_list_append(name, &limit_by_name);
+       it->util = exclude ? NULL : (void *) 1;
+}
 
 static int use_patch(struct patch *p)
 {
        const char *pathname = p->new_name ? p->new_name : p->old_name;
-       struct excludes *x = excludes;
-       while (x) {
-               if (fnmatch(x->path, pathname, 0) == 0)
-                       return 0;
-               x = x->next;
-       }
+       int i;
+
+       /* Paths outside are not touched regardless of "--include" */
        if (0 < prefix_length) {
                int pathlen = strlen(pathname);
                if (pathlen <= prefix_length ||
                    memcmp(prefix, pathname, prefix_length))
                        return 0;
        }
-       return 1;
+
+       /* See if it matches any of exclude/include rule */
+       for (i = 0; i < limit_by_name.nr; i++) {
+               struct string_list_item *it = &limit_by_name.items[i];
+               if (!fnmatch(it->string, pathname, 0))
+                       return (it->util != NULL);
+       }
+
+       /*
+        * If we had any include, a path that does not match any rule is
+        * not used.  Otherwise, we saw bunch of exclude rules (or none)
+        * and such a path is used.
+        */
+       return !has_include;
 }
 
+
 static void prefix_one(char **name)
 {
        char *old_name = *name;
@@ -3150,10 +3166,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 4ca76a6..b572b3b 100644 (file)
@@ -13,6 +13,9 @@
 #include "diff.h"
 #include "revision.h"
 #include "remote.h"
+#include "blob.h"
+#include "xdiff-interface.h"
+#include "ll-merge.h"
 
 static const char * const checkout_usage[] = {
        "git checkout [options] <branch>",
@@ -20,6 +23,18 @@ static const char * const checkout_usage[] = {
        NULL,
 };
 
+struct checkout_opts {
+       int quiet;
+       int merge;
+       int force;
+       int writeout_stage;
+       int writeout_error;
+
+       const char *new_branch;
+       int new_branch_log;
+       enum branch_track track;
+};
+
 static int post_checkout_hook(struct commit *old, struct commit *new,
                              int changed)
 {
@@ -84,8 +99,119 @@ static int skip_same_name(struct cache_entry *ce, int pos)
        return pos;
 }
 
+static int check_stage(int stage, struct cache_entry *ce, int pos)
+{
+       while (pos < active_nr &&
+              !strcmp(active_cache[pos]->name, ce->name)) {
+               if (ce_stage(active_cache[pos]) == stage)
+                       return 0;
+               pos++;
+       }
+       return error("path '%s' does not have %s version",
+                    ce->name,
+                    (stage == 2) ? "our" : "their");
+}
+
+static int check_all_stages(struct cache_entry *ce, int pos)
+{
+       if (ce_stage(ce) != 1 ||
+           active_nr <= pos + 2 ||
+           strcmp(active_cache[pos+1]->name, ce->name) ||
+           ce_stage(active_cache[pos+1]) != 2 ||
+           strcmp(active_cache[pos+2]->name, ce->name) ||
+           ce_stage(active_cache[pos+2]) != 3)
+               return error("path '%s' does not have all three versions",
+                            ce->name);
+       return 0;
+}
+
+static int checkout_stage(int stage, struct cache_entry *ce, int pos,
+                         struct checkout *state)
+{
+       while (pos < active_nr &&
+              !strcmp(active_cache[pos]->name, ce->name)) {
+               if (ce_stage(active_cache[pos]) == stage)
+                       return checkout_entry(active_cache[pos], state, NULL);
+               pos++;
+       }
+       return error("path '%s' does not have %s version",
+                    ce->name,
+                    (stage == 2) ? "our" : "their");
+}
+
+/* NEEDSWORK: share with merge-recursive */
+static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
+{
+       unsigned long size;
+       enum object_type type;
+
+       if (!hashcmp(sha1, null_sha1)) {
+               mm->ptr = xstrdup("");
+               mm->size = 0;
+               return;
+       }
+
+       mm->ptr = read_sha1_file(sha1, &type, &size);
+       if (!mm->ptr || type != OBJ_BLOB)
+               die("unable to read blob object %s", sha1_to_hex(sha1));
+       mm->size = size;
+}
+
+static int checkout_merged(int pos, struct checkout *state)
+{
+       struct cache_entry *ce = active_cache[pos];
+       const char *path = ce->name;
+       mmfile_t ancestor, ours, theirs;
+       int status;
+       unsigned char sha1[20];
+       mmbuffer_t result_buf;
+
+       if (ce_stage(ce) != 1 ||
+           active_nr <= pos + 2 ||
+           strcmp(active_cache[pos+1]->name, path) ||
+           ce_stage(active_cache[pos+1]) != 2 ||
+           strcmp(active_cache[pos+2]->name, path) ||
+           ce_stage(active_cache[pos+2]) != 3)
+               return error("path '%s' does not have all 3 versions", path);
+
+       fill_mm(active_cache[pos]->sha1, &ancestor);
+       fill_mm(active_cache[pos+1]->sha1, &ours);
+       fill_mm(active_cache[pos+2]->sha1, &theirs);
+
+       status = ll_merge(&result_buf, path, &ancestor,
+                         &ours, "ours", &theirs, "theirs", 1);
+       free(ancestor.ptr);
+       free(ours.ptr);
+       free(theirs.ptr);
+       if (status < 0 || !result_buf.ptr) {
+               free(result_buf.ptr);
+               return error("path '%s': cannot merge", path);
+       }
+
+       /*
+        * NEEDSWORK:
+        * There is absolutely no reason to write this as a blob object
+        * and create a phoney cache entry just to leak.  This hack is
+        * primarily to get to the write_entry() machinery that massages
+        * the contents to work-tree format and writes out which only
+        * allows it for a cache entry.  The code in write_entry() needs
+        * to be refactored to allow us to feed a <buffer, size, mode>
+        * instead of a cache entry.  Such a refactoring would help
+        * merge_recursive as well (it also writes the merge result to the
+        * object database even when it may contain conflicts).
+        */
+       if (write_sha1_file(result_buf.ptr, result_buf.size,
+                           blob_type, sha1))
+               die("Unable to add merge result for '%s'", path);
+       ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode),
+                             sha1,
+                             path, 2, 0);
+       status = checkout_entry(ce, state, NULL);
+       return status;
+}
 
-static int checkout_paths(struct tree *source_tree, const char **pathspec)
+static int checkout_paths(struct tree *source_tree, const char **pathspec,
+                         struct checkout_opts *opts)
 {
        int pos;
        struct checkout state;
@@ -94,7 +220,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
        int flag;
        struct commit *head;
        int errs = 0;
-
+       int stage = opts->writeout_stage;
+       int merge = opts->merge;
        int newfd;
        struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
 
@@ -122,8 +249,16 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
                if (pathspec_match(pathspec, NULL, ce->name, 0)) {
                        if (!ce_stage(ce))
                                continue;
-                       errs = 1;
-                       error("path '%s' is unmerged", ce->name);
+                       if (opts->force) {
+                               warning("path '%s' is unmerged", ce->name);
+                       } else if (stage) {
+                               errs |= check_stage(stage, ce, pos);
+                       } else if (opts->merge) {
+                               errs |= check_all_stages(ce, pos);
+                       } else {
+                               errs = 1;
+                               error("path '%s' is unmerged", ce->name);
+                       }
                        pos = skip_same_name(ce, pos) - 1;
                }
        }
@@ -141,6 +276,10 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
                                errs |= checkout_entry(ce, &state, NULL);
                                continue;
                        }
+                       if (stage)
+                               errs |= checkout_stage(stage, ce, pos, &state);
+                       else if (merge)
+                               errs |= checkout_merged(pos, &state);
                        pos = skip_same_name(ce, pos) - 1;
                }
        }
@@ -178,17 +317,6 @@ static void describe_detached_head(char *msg, struct commit *commit)
        strbuf_release(&sb);
 }
 
-struct checkout_opts {
-       int quiet;
-       int merge;
-       int force;
-       int writeout_error;
-
-       const char *new_branch;
-       int new_branch_log;
-       enum branch_track track;
-};
-
 static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
 {
        struct unpack_trees_options opts;
@@ -293,6 +421,7 @@ static int merge_working_tree(struct checkout_opts *opts,
                         */
                        struct tree *result;
                        struct tree *work;
+                       struct merge_options o;
                        if (!opts->merge)
                                return 1;
                        parse_commit(old->commit);
@@ -311,13 +440,17 @@ static int merge_working_tree(struct checkout_opts *opts,
                         */
 
                        add_files_to_cache(NULL, NULL, 0);
-                       work = write_tree_from_memory();
+                       init_merge_options(&o);
+                       o.verbosity = 0;
+                       work = write_tree_from_memory(&o);
 
                        ret = reset_tree(new->commit->tree, opts, 1);
                        if (ret)
                                return ret;
-                       merge_trees(new->commit->tree, work, old->commit->tree,
-                                   new->name, "local", &result);
+                       o.branch1 = new->name;
+                       o.branch2 = "local";
+                       merge_trees(&o, new->commit->tree, work,
+                               old->commit->tree, &result);
                        ret = reset_tree(new->commit->tree, opts, 0);
                        if (ret)
                                return ret;
@@ -440,6 +573,11 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
        return ret || opts->writeout_error;
 }
 
+static int git_checkout_config(const char *var, const char *value, void *cb)
+{
+       return git_xmerge_config(var, value, cb);
+}
+
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
        struct checkout_opts opts;
@@ -447,14 +585,21 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        const char *arg;
        struct branch_info new;
        struct tree *source_tree = NULL;
+       char *conflict_style = NULL;
        struct option options[] = {
                OPT__QUIET(&opts.quiet),
                OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
                OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
                OPT_SET_INT('t', "track",  &opts.track, "track",
                        BRANCH_TRACK_EXPLICIT),
+               OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
+                           2),
+               OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
+                           3),
                OPT_BOOLEAN('f', NULL, &opts.force, "force"),
-               OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
+               OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
+               OPT_STRING(0, "conflict", &conflict_style, "style",
+                          "conflict style (merge or diff3)"),
                OPT_END(),
        };
        int has_dash_dash;
@@ -462,7 +607,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        memset(&opts, 0, sizeof(opts));
        memset(&new, 0, sizeof(new));
 
-       git_config(git_default_config, NULL);
+       git_config(git_checkout_config, NULL);
 
        opts.track = BRANCH_TRACK_UNSPECIFIED;
 
@@ -486,6 +631,13 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
        if (opts.track == BRANCH_TRACK_UNSPECIFIED)
                opts.track = git_branch_track;
+       if (conflict_style) {
+               opts.merge = 1; /* implied */
+               git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+       }
+
+       if (!opts.new_branch && (opts.track != git_branch_track))
+               die("git checkout: --track and --no-track require -b");
 
        if (opts.force && opts.merge)
                die("git checkout: -f and -m are incompatible");
@@ -569,20 +721,37 @@ no_reference:
                        die("invalid path specification");
 
                /* Checkout paths */
-               if (opts.new_branch || opts.force || opts.merge) {
+               if (opts.new_branch) {
                        if (argc == 1) {
-                               die("git checkout: updating paths is incompatible with switching branches/forcing\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
+                               die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
                        } else {
-                               die("git checkout: updating paths is incompatible with switching branches/forcing");
+                               die("git checkout: updating paths is incompatible with switching branches.");
                        }
                }
 
-               return checkout_paths(source_tree, pathspec);
+               if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+                       die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
+
+               return checkout_paths(source_tree, pathspec, &opts);
+       }
+
+       if (opts.new_branch) {
+               struct strbuf buf;
+               strbuf_init(&buf, 0);
+               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.");
        }
+       if (opts.writeout_stage)
+               die("--ours/--theirs is incompatible with switching branches.");
 
        return switch_branches(&opts, &new);
 }
index 5b40e07..49d2eb9 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 f2684bb..0453425 100644 (file)
@@ -46,7 +46,8 @@ 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;
@@ -74,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);
@@ -123,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 6b23143..b920257 100644 (file)
@@ -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;
@@ -929,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);
 
@@ -971,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)";
@@ -991,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)
@@ -1007,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");
@@ -1037,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");
        }
@@ -1055,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 6e80fe7..ab35b65 100644 (file)
@@ -126,6 +126,6 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
        }
        else
                printf("%lu objects, %lu kilobytes\n",
-                      loose, loose_size / 2);
+                      loose, loose_size / 1024);
        return 0;
 }
index 1138c2d..8ecefd4 100644 (file)
@@ -71,8 +71,9 @@ static int diff_tree_stdin(char *line)
        line[len-1] = 0;
        if (get_sha1_hex(line, sha1))
                return -1;
-       obj = lookup_object(sha1);
-       obj = obj ? obj : parse_object(sha1);
+       obj = lookup_unknown_object(sha1);
+       if (!obj || !obj->parsed)
+               obj = parse_object(sha1);
        if (!obj)
                return -1;
        if (obj->type == OBJ_COMMIT)
index d5fe775..35da366 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 4dfef29..fa3c936 100644 (file)
@@ -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 72c0878..fa6c1ed 100644 (file)
@@ -543,6 +543,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.
  */
@@ -568,13 +669,33 @@ static void populate_value(struct refinfo *ref)
        for (i = 0; i < used_atom_cnt; i++) {
                const char *name = used_atom[i];
                struct atom_value *v = &ref->value[i];
-               if (!strcmp(name, "refname"))
-                       v->s = ref->refname;
-               else if (!strcmp(name, "*refname")) {
-                       int len = strlen(ref->refname);
-                       char *s = xmalloc(len + 4);
-                       sprintf(s, "%s^{}", ref->refname);
-                       v->s = s;
+               int deref = 0;
+               if (*name == '*') {
+                       deref = 1;
+                       name++;
+               }
+               if (!prefixcmp(name, "refname")) {
+                       const char *formatp = strchr(name, ':');
+                       const char *refname = ref->refname;
+
+                       /* look for "short" refname format */
+                       if (formatp) {
+                               formatp++;
+                               if (!strcmp(formatp, "short"))
+                                       refname = get_short_ref(ref);
+                               else
+                                       die("unknown refname format %s",
+                                           formatp);
+                       }
+
+                       if (!deref)
+                               v->s = refname;
+                       else {
+                               int len = strlen(refname);
+                               char *s = xmalloc(len + 4);
+                               sprintf(s, "%s^{}", refname);
+                               v->s = s;
+                       }
                }
        }
 
index 03f34d7..f3e63d7 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 d4aaddc..fc5e4da 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);
index 3605960..45c9853 100644 (file)
@@ -4,7 +4,7 @@
 #include "xdiff-interface.h"
 
 static const char merge_file_usage[] =
-"git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2";
+"git merge-file [-p | --stdout] [--diff3] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2";
 
 int cmd_merge_file(int argc, const char **argv, const char *prefix)
 {
@@ -13,6 +13,17 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
        mmbuffer_t result = {NULL, 0};
        xpparam_t xpp = {XDF_NEED_MINIMAL};
        int ret = 0, i = 0, to_stdout = 0;
+       int merge_level = XDL_MERGE_ZEALOUS_ALNUM;
+       int merge_style = 0;
+       int nongit;
+
+       prefix = setup_git_directory_gently(&nongit);
+       if (!nongit) {
+               /* Read the configuration file */
+               git_config(git_xmerge_config, NULL);
+               if (0 <= git_xmerge_style)
+                       merge_style = git_xmerge_style;
+       }
 
        while (argc > 4) {
                if (!strcmp(argv[1], "-L") && i < 3) {
@@ -25,6 +36,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
                else if (!strcmp(argv[1], "-q") ||
                                !strcmp(argv[1], "--quiet"))
                        freopen("/dev/null", "w", stderr);
+               else if (!strcmp(argv[1], "--diff3"))
+                       merge_style = XDL_MERGE_DIFF3;
                else
                        usage(merge_file_usage);
                argc--;
@@ -46,7 +59,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
        }
 
        ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
-                       &xpp, XDL_MERGE_ZEALOUS_ALNUM, &result);
+                       &xpp, merge_level | merge_style, &result);
 
        for (i = 0; i < 3; i++)
                free(mmfs[i].ptr);
index 36aa798..6b534c1 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 "dir.h"
 #include "merge-recursive.h"
 
-static int subtree_merge;
-
-static struct tree *shift_tree_object(struct tree *one, struct tree *two)
-{
-       unsigned char shifted[20];
-
-       /*
-        * NEEDSWORK: this limits the recursion depth to hardcoded
-        * value '2' to avoid excessive overhead.
-        */
-       shift_tree(one->object.sha1, two->object.sha1, shifted, 2);
-       if (!hashcmp(two->object.sha1, shifted))
-               return two;
-       return lookup_tree(shifted);
-}
-
-/*
- * A virtual commit has
- * - (const char *)commit->util set to the name, and
- * - *(int *)commit->object.sha1 set to the virtual id.
- */
-
-static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
-{
-       struct commit *commit = xcalloc(1, sizeof(struct commit));
-       static unsigned virtual_id = 1;
-       commit->tree = tree;
-       commit->util = (void*)comment;
-       *(int*)commit->object.sha1 = virtual_id++;
-       /* avoid warnings */
-       commit->object.parsed = 1;
-       return commit;
-}
-
-/*
- * Since we use get_tree_entry(), which does not put the read object into
- * the object pool, we cannot rely on a == b.
- */
-static int sha_eq(const unsigned char *a, const unsigned char *b)
-{
-       if (!a && !b)
-               return 2;
-       return a && b && hashcmp(a, b) == 0;
-}
-
-/*
- * Since we want to write the index eventually, we cannot reuse the index
- * for these (temporary) data.
- */
-struct stage_data
-{
-       struct
-       {
-               unsigned mode;
-               unsigned char sha[20];
-       } stages[4];
-       unsigned processed:1;
-};
-
-static struct string_list current_file_set = {NULL, 0, 0, 1};
-static struct string_list current_directory_set = {NULL, 0, 0, 1};
-
-static int call_depth = 0;
-static int verbosity = 2;
-static int diff_rename_limit = -1;
-static int merge_rename_limit = -1;
-static int buffer_output = 1;
-static struct strbuf obuf = STRBUF_INIT;
-
-static int show(int v)
-{
-       return (!call_depth && verbosity >= v) || verbosity >= 5;
-}
-
-static void flush_output(void)
-{
-       if (obuf.len) {
-               fputs(obuf.buf, stdout);
-               strbuf_reset(&obuf);
-       }
-}
-
-static void output(int v, const char *fmt, ...)
-{
-       int len;
-       va_list ap;
-
-       if (!show(v))
-               return;
-
-       strbuf_grow(&obuf, call_depth * 2 + 2);
-       memset(obuf.buf + obuf.len, ' ', call_depth * 2);
-       strbuf_setlen(&obuf, obuf.len + call_depth * 2);
-
-       va_start(ap, fmt);
-       len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap);
-       va_end(ap);
-
-       if (len < 0)
-               len = 0;
-       if (len >= strbuf_avail(&obuf)) {
-               strbuf_grow(&obuf, len + 2);
-               va_start(ap, fmt);
-               len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap);
-               va_end(ap);
-               if (len >= strbuf_avail(&obuf)) {
-                       die("this should not happen, your snprintf is broken");
-               }
-       }
-       strbuf_setlen(&obuf, obuf.len + len);
-       strbuf_add(&obuf, "\n", 1);
-       if (!buffer_output)
-               flush_output();
-}
-
-static void output_commit_title(struct commit *commit)
-{
-       int i;
-       flush_output();
-       for (i = call_depth; i--;)
-               fputs("  ", stdout);
-       if (commit->util)
-               printf("virtual %s\n", (char *)commit->util);
-       else {
-               printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
-               if (parse_commit(commit) != 0)
-                       printf("(bad commit)\n");
-               else {
-                       const char *s;
-                       int len;
-                       for (s = commit->buffer; *s; s++)
-                               if (*s == '\n' && s[1] == '\n') {
-                                       s += 2;
-                                       break;
-                               }
-                       for (len = 0; s[len] && '\n' != s[len]; len++)
-                               ; /* do nothing */
-                       printf("%.*s\n", len, s);
-               }
-       }
-}
-
-static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
-               const char *path, int stage, int refresh, int options)
-{
-       struct cache_entry *ce;
-       ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh);
-       if (!ce)
-               return error("addinfo_cache failed for path '%s'", path);
-       return add_cache_entry(ce, options);
-}
-
-/*
- * This is a global variable which is used in a number of places but
- * only written to in the 'merge' function.
- *
- * index_only == 1    => Don't leave any non-stage 0 entries in the cache and
- *                       don't update the working directory.
- *               0    => Leave unmerged entries in the cache and update
- *                       the working directory.
- */
-static int index_only = 0;
-
-static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)
-{
-       parse_tree(tree);
-       init_tree_desc(desc, tree->buffer, tree->size);
-}
-
-static int git_merge_trees(int index_only,
-                          struct tree *common,
-                          struct tree *head,
-                          struct tree *merge)
-{
-       int rc;
-       struct tree_desc t[3];
-       struct unpack_trees_options opts;
-
-       memset(&opts, 0, sizeof(opts));
-       if (index_only)
-               opts.index_only = 1;
-       else
-               opts.update = 1;
-       opts.merge = 1;
-       opts.head_idx = 2;
-       opts.fn = threeway_merge;
-       opts.src_index = &the_index;
-       opts.dst_index = &the_index;
-
-       init_tree_desc_from_tree(t+0, common);
-       init_tree_desc_from_tree(t+1, head);
-       init_tree_desc_from_tree(t+2, merge);
-
-       rc = unpack_trees(3, t, &opts);
-       cache_tree_free(&active_cache_tree);
-       return rc;
-}
-
-struct tree *write_tree_from_memory(void)
-{
-       struct tree *result = NULL;
-
-       if (unmerged_cache()) {
-               int i;
-               output(0, "There are unmerged index entries:");
-               for (i = 0; i < active_nr; i++) {
-                       struct cache_entry *ce = active_cache[i];
-                       if (ce_stage(ce))
-                               output(0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name);
-               }
-               return NULL;
-       }
-
-       if (!active_cache_tree)
-               active_cache_tree = cache_tree();
-
-       if (!cache_tree_fully_valid(active_cache_tree) &&
-           cache_tree_update(active_cache_tree,
-                             active_cache, active_nr, 0, 0) < 0)
-               die("error building trees");
-
-       result = lookup_tree(active_cache_tree->sha1);
-
-       return result;
-}
-
-static int save_files_dirs(const unsigned char *sha1,
-               const char *base, int baselen, const char *path,
-               unsigned int mode, int stage, void *context)
-{
-       int len = strlen(path);
-       char *newpath = xmalloc(baselen + len + 1);
-       memcpy(newpath, base, baselen);
-       memcpy(newpath + baselen, path, len);
-       newpath[baselen + len] = '\0';
-
-       if (S_ISDIR(mode))
-               string_list_insert(newpath, &current_directory_set);
-       else
-               string_list_insert(newpath, &current_file_set);
-       free(newpath);
-
-       return READ_TREE_RECURSIVE;
-}
-
-static int get_files_dirs(struct tree *tree)
-{
-       int n;
-       if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, NULL))
-               return 0;
-       n = current_file_set.nr + current_directory_set.nr;
-       return n;
-}
-
-/*
- * Returns an index_entry instance which doesn't have to correspond to
- * a real cache entry in Git's index.
- */
-static struct stage_data *insert_stage_data(const char *path,
-               struct tree *o, struct tree *a, struct tree *b,
-               struct string_list *entries)
-{
-       struct string_list_item *item;
-       struct stage_data *e = xcalloc(1, sizeof(struct stage_data));
-       get_tree_entry(o->object.sha1, path,
-                       e->stages[1].sha, &e->stages[1].mode);
-       get_tree_entry(a->object.sha1, path,
-                       e->stages[2].sha, &e->stages[2].mode);
-       get_tree_entry(b->object.sha1, path,
-                       e->stages[3].sha, &e->stages[3].mode);
-       item = string_list_insert(path, entries);
-       item->util = e;
-       return e;
-}
-
-/*
- * Create a dictionary mapping file names to stage_data objects. The
- * dictionary contains one entry for every path with a non-zero stage entry.
- */
-static struct string_list *get_unmerged(void)
-{
-       struct string_list *unmerged = xcalloc(1, sizeof(struct string_list));
-       int i;
-
-       unmerged->strdup_strings = 1;
-
-       for (i = 0; i < active_nr; i++) {
-               struct string_list_item *item;
-               struct stage_data *e;
-               struct cache_entry *ce = active_cache[i];
-               if (!ce_stage(ce))
-                       continue;
-
-               item = string_list_lookup(ce->name, unmerged);
-               if (!item) {
-                       item = string_list_insert(ce->name, unmerged);
-                       item->util = xcalloc(1, sizeof(struct stage_data));
-               }
-               e = item->util;
-               e->stages[ce_stage(ce)].mode = ce->ce_mode;
-               hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1);
-       }
-
-       return unmerged;
-}
-
-struct rename
-{
-       struct diff_filepair *pair;
-       struct stage_data *src_entry;
-       struct stage_data *dst_entry;
-       unsigned processed:1;
-};
-
-/*
- * Get information of all renames which occurred between 'o_tree' and
- * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and
- * 'b_tree') to be able to associate the correct cache entries with
- * the rename information. 'tree' is always equal to either a_tree or b_tree.
- */
-static struct string_list *get_renames(struct tree *tree,
-                                       struct tree *o_tree,
-                                       struct tree *a_tree,
-                                       struct tree *b_tree,
-                                       struct string_list *entries)
-{
-       int i;
-       struct string_list *renames;
-       struct diff_options opts;
-
-       renames = xcalloc(1, sizeof(struct string_list));
-       diff_setup(&opts);
-       DIFF_OPT_SET(&opts, RECURSIVE);
-       opts.detect_rename = DIFF_DETECT_RENAME;
-       opts.rename_limit = merge_rename_limit >= 0 ? merge_rename_limit :
-                           diff_rename_limit >= 0 ? diff_rename_limit :
-                           500;
-       opts.warn_on_too_large_rename = 1;
-       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-       if (diff_setup_done(&opts) < 0)
-               die("diff setup failed");
-       diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts);
-       diffcore_std(&opts);
-       for (i = 0; i < diff_queued_diff.nr; ++i) {
-               struct string_list_item *item;
-               struct rename *re;
-               struct diff_filepair *pair = diff_queued_diff.queue[i];
-               if (pair->status != 'R') {
-                       diff_free_filepair(pair);
-                       continue;
-               }
-               re = xmalloc(sizeof(*re));
-               re->processed = 0;
-               re->pair = pair;
-               item = string_list_lookup(re->pair->one->path, entries);
-               if (!item)
-                       re->src_entry = insert_stage_data(re->pair->one->path,
-                                       o_tree, a_tree, b_tree, entries);
-               else
-                       re->src_entry = item->util;
-
-               item = string_list_lookup(re->pair->two->path, entries);
-               if (!item)
-                       re->dst_entry = insert_stage_data(re->pair->two->path,
-                                       o_tree, a_tree, b_tree, entries);
-               else
-                       re->dst_entry = item->util;
-               item = string_list_insert(pair->one->path, renames);
-               item->util = re;
-       }
-       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-       diff_queued_diff.nr = 0;
-       diff_flush(&opts);
-       return renames;
-}
-
-static int update_stages(const char *path, struct diff_filespec *o,
-                        struct diff_filespec *a, struct diff_filespec *b,
-                        int clear)
-{
-       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
-       if (clear)
-               if (remove_file_from_cache(path))
-                       return -1;
-       if (o)
-               if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))
-                       return -1;
-       if (a)
-               if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options))
-                       return -1;
-       if (b)
-               if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options))
-                       return -1;
-       return 0;
-}
-
-static int remove_file(int clean, const char *path, int no_wd)
-{
-       int update_cache = index_only || clean;
-       int update_working_directory = !index_only && !no_wd;
-
-       if (update_cache) {
-               if (remove_file_from_cache(path))
-                       return -1;
-       }
-       if (update_working_directory) {
-               if (remove_path(path))
-                       return -1;
-       }
-       return 0;
-}
-
-static char *unique_path(const char *path, const char *branch)
-{
-       char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1);
-       int suffix = 0;
-       struct stat st;
-       char *p = newpath + strlen(path);
-       strcpy(newpath, path);
-       *(p++) = '~';
-       strcpy(p, branch);
-       for (; *p; ++p)
-               if ('/' == *p)
-                       *p = '_';
-       while (string_list_has_string(&current_file_set, newpath) ||
-              string_list_has_string(&current_directory_set, newpath) ||
-              lstat(newpath, &st) == 0)
-               sprintf(p, "_%d", suffix++);
-
-       string_list_insert(newpath, &current_file_set);
-       return newpath;
-}
-
-static void flush_buffer(int fd, const char *buf, unsigned long size)
-{
-       while (size > 0) {
-               long ret = write_in_full(fd, buf, size);
-               if (ret < 0) {
-                       /* Ignore epipe */
-                       if (errno == EPIPE)
-                               break;
-                       die("merge-recursive: %s", strerror(errno));
-               } else if (!ret) {
-                       die("merge-recursive: disk full?");
-               }
-               size -= ret;
-               buf += ret;
-       }
-}
-
-static int make_room_for_path(const char *path)
-{
-       int status;
-       const char *msg = "failed to create path '%s'%s";
-
-       status = safe_create_leading_directories_const(path);
-       if (status) {
-               if (status == -3) {
-                       /* something else exists */
-                       error(msg, path, ": perhaps a D/F conflict?");
-                       return -1;
-               }
-               die(msg, path, "");
-       }
-
-       /* Successful unlink is good.. */
-       if (!unlink(path))
-               return 0;
-       /* .. and so is no existing file */
-       if (errno == ENOENT)
-               return 0;
-       /* .. but not some other error (who really cares what?) */
-       return error(msg, path, ": perhaps a D/F conflict?");
-}
-
-static void update_file_flags(const unsigned char *sha,
-                             unsigned mode,
-                             const char *path,
-                             int update_cache,
-                             int update_wd)
-{
-       if (index_only)
-               update_wd = 0;
-
-       if (update_wd) {
-               enum object_type type;
-               void *buf;
-               unsigned long size;
-
-               if (S_ISGITLINK(mode))
-                       die("cannot read object %s '%s': It is a submodule!",
-                           sha1_to_hex(sha), path);
-
-               buf = read_sha1_file(sha, &type, &size);
-               if (!buf)
-                       die("cannot read object %s '%s'", sha1_to_hex(sha), path);
-               if (type != OBJ_BLOB)
-                       die("blob expected for %s '%s'", sha1_to_hex(sha), path);
-               if (S_ISREG(mode)) {
-                       struct strbuf strbuf;
-                       strbuf_init(&strbuf, 0);
-                       if (convert_to_working_tree(path, buf, size, &strbuf)) {
-                               free(buf);
-                               size = strbuf.len;
-                               buf = strbuf_detach(&strbuf, NULL);
-                       }
-               }
-
-               if (make_room_for_path(path) < 0) {
-                       update_wd = 0;
-                       free(buf);
-                       goto update_index;
-               }
-               if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
-                       int fd;
-                       if (mode & 0100)
-                               mode = 0777;
-                       else
-                               mode = 0666;
-                       fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
-                       if (fd < 0)
-                               die("failed to open %s: %s", path, strerror(errno));
-                       flush_buffer(fd, buf, size);
-                       close(fd);
-               } else if (S_ISLNK(mode)) {
-                       char *lnk = xmemdupz(buf, size);
-                       safe_create_leading_directories_const(path);
-                       unlink(path);
-                       symlink(lnk, path);
-                       free(lnk);
-               } else
-                       die("do not know what to do with %06o %s '%s'",
-                           mode, sha1_to_hex(sha), path);
-               free(buf);
-       }
- update_index:
-       if (update_cache)
-               add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
-}
-
-static void update_file(int clean,
-                       const unsigned char *sha,
-                       unsigned mode,
-                       const char *path)
-{
-       update_file_flags(sha, mode, path, index_only || clean, !index_only);
-}
-
-/* Low level file merging, update and removal */
-
-struct merge_file_info
-{
-       unsigned char sha[20];
-       unsigned mode;
-       unsigned clean:1,
-                merge:1;
-};
-
-static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
-{
-       unsigned long size;
-       enum object_type type;
-
-       if (!hashcmp(sha1, null_sha1)) {
-               mm->ptr = xstrdup("");
-               mm->size = 0;
-               return;
-       }
-
-       mm->ptr = read_sha1_file(sha1, &type, &size);
-       if (!mm->ptr || type != OBJ_BLOB)
-               die("unable to read blob object %s", sha1_to_hex(sha1));
-       mm->size = size;
-}
-
-static int merge_3way(mmbuffer_t *result_buf,
-                     struct diff_filespec *o,
-                     struct diff_filespec *a,
-                     struct diff_filespec *b,
-                     const char *branch1,
-                     const char *branch2)
-{
-       mmfile_t orig, src1, src2;
-       char *name1, *name2;
-       int merge_status;
-
-       name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
-       name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
-
-       fill_mm(o->sha1, &orig);
-       fill_mm(a->sha1, &src1);
-       fill_mm(b->sha1, &src2);
-
-       merge_status = ll_merge(result_buf, a->path, &orig,
-                               &src1, name1, &src2, name2,
-                               index_only);
-
-       free(name1);
-       free(name2);
-       free(orig.ptr);
-       free(src1.ptr);
-       free(src2.ptr);
-       return merge_status;
-}
-
-static struct merge_file_info merge_file(struct diff_filespec *o,
-               struct diff_filespec *a, struct diff_filespec *b,
-               const char *branch1, const char *branch2)
-{
-       struct merge_file_info result;
-       result.merge = 0;
-       result.clean = 1;
-
-       if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
-               result.clean = 0;
-               if (S_ISREG(a->mode)) {
-                       result.mode = a->mode;
-                       hashcpy(result.sha, a->sha1);
-               } else {
-                       result.mode = b->mode;
-                       hashcpy(result.sha, b->sha1);
-               }
-       } else {
-               if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1))
-                       result.merge = 1;
-
-               /*
-                * Merge modes
-                */
-               if (a->mode == b->mode || a->mode == o->mode)
-                       result.mode = b->mode;
-               else {
-                       result.mode = a->mode;
-                       if (b->mode != o->mode) {
-                               result.clean = 0;
-                               result.merge = 1;
-                       }
-               }
-
-               if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, o->sha1))
-                       hashcpy(result.sha, b->sha1);
-               else if (sha_eq(b->sha1, o->sha1))
-                       hashcpy(result.sha, a->sha1);
-               else if (S_ISREG(a->mode)) {
-                       mmbuffer_t result_buf;
-                       int merge_status;
-
-                       merge_status = merge_3way(&result_buf, o, a, b,
-                                                 branch1, branch2);
-
-                       if ((merge_status < 0) || !result_buf.ptr)
-                               die("Failed to execute internal merge");
-
-                       if (write_sha1_file(result_buf.ptr, result_buf.size,
-                                           blob_type, result.sha))
-                               die("Unable to add %s to database",
-                                   a->path);
-
-                       free(result_buf.ptr);
-                       result.clean = (merge_status == 0);
-               } else if (S_ISGITLINK(a->mode)) {
-                       result.clean = 0;
-                       hashcpy(result.sha, a->sha1);
-               } else if (S_ISLNK(a->mode)) {
-                       hashcpy(result.sha, a->sha1);
-
-                       if (!sha_eq(a->sha1, b->sha1))
-                               result.clean = 0;
-               } else {
-                       die("unsupported object type in the tree");
-               }
-       }
-
-       return result;
-}
-
-static void conflict_rename_rename(struct rename *ren1,
-                                  const char *branch1,
-                                  struct rename *ren2,
-                                  const char *branch2)
-{
-       char *del[2];
-       int delp = 0;
-       const char *ren1_dst = ren1->pair->two->path;
-       const char *ren2_dst = ren2->pair->two->path;
-       const char *dst_name1 = ren1_dst;
-       const char *dst_name2 = ren2_dst;
-       if (string_list_has_string(&current_directory_set, ren1_dst)) {
-               dst_name1 = del[delp++] = unique_path(ren1_dst, branch1);
-               output(1, "%s is a directory in %s adding 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 adding 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, "Renaming %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, "Renaming %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, "Renaming %s->%s", src, ren1_dst);
-
-                               if (mfi.merge)
-                                       output(2, "Auto-merging %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): Rename %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): Rename %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): Rename %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, "Adding 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): 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(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, "Renaming %s => %s", ren1_src, ren1_dst);
-                                       if (mfi.merge)
-                                               output(2, "Auto-merging %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, "Removing %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. "
-                              "Adding %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, "Adding %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-merging %s", path);
-               o.path = a.path = b.path = (char *)path;
-               hashcpy(o.sha1, o_sha);
-               o.mode = o_mode;
-               hashcpy(a.sha1, a_sha);
-               a.mode = a_mode;
-               hashcpy(b.sha1, b_sha);
-               b.mode = b_mode;
-
-               mfi = merge_file(&o, &a, &b,
-                                branch1, branch2);
-
-               clean_merge = mfi.clean;
-               if (mfi.clean)
-                       update_file(1, mfi.sha, mfi.mode, path);
-               else if (S_ISGITLINK(mfi.mode))
-                       output(1, "CONFLICT (submodule): Merge conflict in %s "
-                              "- needs %s", path, sha1_to_hex(b.sha1));
-               else {
-                       output(1, "CONFLICT (%s): Merge conflict in %s",
-                                       reason, path);
-
-                       if (index_only)
-                               update_file(0, mfi.sha, mfi.mode, path);
-                       else
-                               update_file_flags(mfi.sha, mfi.mode, path,
-                                             0 /* update_cache */, 1 /* update_working_directory */);
-               }
-       } else if (!o_sha && !a_sha && !b_sha) {
-               /*
-                * this entry was deleted altogether. a_mode == 0 means
-                * we had that path and want to actively remove it.
-                */
-               remove_file(1, path, !a_mode);
-       } else
-               die("Fatal merge failure, shouldn't happen.");
-
-       return clean_merge;
-}
-
-int merge_trees(struct tree *head,
-               struct tree *merge,
-               struct tree *common,
-               const char *branch1,
-               const char *branch2,
-               struct tree **result)
-{
-       int code, clean;
-
-       if (subtree_merge) {
-               merge = shift_tree_object(head, merge);
-               common = shift_tree_object(head, common);
-       }
-
-       if (sha_eq(common->object.sha1, merge->object.sha1)) {
-               output(0, "Already uptodate!");
-               *result = head;
-               return 1;
-       }
-
-       code = git_merge_trees(index_only, common, head, merge);
-
-       if (code != 0)
-               die("merging of trees %s and %s failed",
-                   sha1_to_hex(head->object.sha1),
-                   sha1_to_hex(merge->object.sha1));
-
-       if (unmerged_cache()) {
-               struct string_list *entries, *re_head, *re_merge;
-               int i;
-               string_list_clear(&current_file_set, 1);
-               string_list_clear(&current_directory_set, 1);
-               get_files_dirs(head);
-               get_files_dirs(merge);
-
-               entries = get_unmerged();
-               re_head  = get_renames(head, common, head, merge, entries);
-               re_merge = get_renames(merge, common, head, merge, entries);
-               clean = process_renames(re_head, re_merge,
-                               branch1, branch2);
-               for (i = 0; i < entries->nr; i++) {
-                       const char *path = entries->items[i].string;
-                       struct stage_data *e = entries->items[i].util;
-                       if (!e->processed
-                               && !process_entry(path, e, branch1, branch2))
-                               clean = 0;
-               }
-
-               string_list_clear(re_merge, 0);
-               string_list_clear(re_head, 0);
-               string_list_clear(entries, 1);
-
-       }
-       else
-               clean = 1;
-
-       if (index_only)
-               *result = write_tree_from_memory();
-
-       return clean;
-}
-
-static struct commit_list *reverse_commit_list(struct commit_list *list)
-{
-       struct commit_list *next = NULL, *current, *backup;
-       for (current = list; current; current = backup) {
-               backup = current->next;
-               current->next = next;
-               next = current;
-       }
-       return next;
-}
-
-/*
- * Merge the commits h1 and h2, return the resulting virtual
- * commit object and a flag indicating the cleanness of the merge.
- */
-int merge_recursive(struct commit *h1,
-                   struct commit *h2,
-                   const char *branch1,
-                   const char *branch2,
-                   struct commit_list *ca,
-                   struct commit **result)
-{
-       struct commit_list *iter;
-       struct commit *merged_common_ancestors;
-       struct tree *mrtree = mrtree;
-       int clean;
-
-       if (show(4)) {
-               output(4, "Merging:");
-               output_commit_title(h1);
-               output_commit_title(h2);
-       }
-
-       if (!ca) {
-               ca = get_merge_bases(h1, h2, 1);
-               ca = reverse_commit_list(ca);
-       }
-
-       if (show(5)) {
-               output(5, "found %u common ancestor(s):", commit_list_count(ca));
-               for (iter = ca; iter; iter = iter->next)
-                       output_commit_title(iter->item);
-       }
-
-       merged_common_ancestors = pop_commit(&ca);
-       if (merged_common_ancestors == NULL) {
-               /* if there is no common ancestor, make an empty tree */
-               struct tree *tree = xcalloc(1, sizeof(struct tree));
-
-               tree->object.parsed = 1;
-               tree->object.type = OBJ_TREE;
-               pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
-               merged_common_ancestors = make_virtual_commit(tree, "ancestor");
-       }
-
-       for (iter = ca; iter; iter = iter->next) {
-               call_depth++;
-               /*
-                * When the merge fails, the result contains files
-                * with conflict markers. The cleanness flag is
-                * ignored, it was never actually used, as result of
-                * merge_trees has always overwritten it: the committed
-                * "conflicts" were already resolved.
-                */
-               discard_cache();
-               merge_recursive(merged_common_ancestors, iter->item,
-                               "Temporary merge branch 1",
-                               "Temporary merge branch 2",
-                               NULL,
-                               &merged_common_ancestors);
-               call_depth--;
-
-               if (!merged_common_ancestors)
-                       die("merge returned no commit");
-       }
-
-       discard_cache();
-       if (!call_depth) {
-               read_cache();
-               index_only = 0;
-       } else
-               index_only = 1;
-
-       clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree,
-                           branch1, branch2, &mrtree);
-
-       if (index_only) {
-               *result = make_virtual_commit(mrtree, "merged tree");
-               commit_list_insert(h1, &(*result)->parents);
-               commit_list_insert(h2, &(*result)->parents->next);
-       }
-       flush_output();
-       return clean;
-}
-
 static const char *better_branch_name(const char *branch)
 {
        static char githead_env[8 + 40 + 1];
@@ -1295,103 +15,58 @@ static const char *better_branch_name(const char *branch)
        return name ? name : branch;
 }
 
-static struct commit *get_ref(const char *ref)
-{
-       unsigned char sha1[20];
-       struct object *object;
-
-       if (get_sha1(ref, sha1))
-               die("Could not resolve ref '%s'", ref);
-       object = deref_tag(parse_object(sha1), ref, strlen(ref));
-       if (!object)
-               return NULL;
-       if (object->type == OBJ_TREE)
-               return make_virtual_commit((struct tree*)object,
-                       better_branch_name(ref));
-       if (object->type != OBJ_COMMIT)
-               return NULL;
-       if (parse_commit((struct commit *)object))
-               die("Could not parse commit '%s'", sha1_to_hex(object->sha1));
-       return (struct commit *)object;
-}
-
-static int merge_config(const char *var, const char *value, void *cb)
-{
-       if (!strcasecmp(var, "merge.verbosity")) {
-               verbosity = git_config_int(var, value);
-               return 0;
-       }
-       if (!strcasecmp(var, "diff.renamelimit")) {
-               diff_rename_limit = git_config_int(var, value);
-               return 0;
-       }
-       if (!strcasecmp(var, "merge.renamelimit")) {
-               merge_rename_limit = git_config_int(var, value);
-               return 0;
-       }
-       return git_default_config(var, value, cb);
-}
-
 int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
 {
-       static const char *bases[20];
-       static unsigned bases_count = 0;
-       int i, clean;
-       const char *branch1, *branch2;
-       struct commit *result, *h1, *h2;
-       struct commit_list *ca = NULL;
-       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-       int index_fd;
+       const unsigned char *bases[21];
+       unsigned bases_count = 0;
+       int i, failed;
+       unsigned char h1[20], h2[20];
+       struct merge_options o;
+       struct commit *result;
 
+       init_merge_options(&o);
        if (argv[0]) {
                int namelen = strlen(argv[0]);
                if (8 < namelen &&
                    !strcmp(argv[0] + namelen - 8, "-subtree"))
-                       subtree_merge = 1;
+                       o.subtree_merge = 1;
        }
 
-       git_config(merge_config, NULL);
-       if (getenv("GIT_MERGE_VERBOSITY"))
-               verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10);
-
        if (argc < 4)
                die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]);
 
        for (i = 1; i < argc; ++i) {
                if (!strcmp(argv[i], "--"))
                        break;
-               if (bases_count < sizeof(bases)/sizeof(*bases))
-                       bases[bases_count++] = argv[i];
+               if (bases_count < ARRAY_SIZE(bases)-1) {
+                       unsigned char *sha = xmalloc(20);
+                       if (get_sha1(argv[i], sha))
+                               die("Could not parse object '%s'", argv[i]);
+                       bases[bases_count++] = sha;
+               }
+               else
+                       warning("Cannot handle more than %zu bases. "
+                               "Ignoring %s.", ARRAY_SIZE(bases)-1, argv[i]);
        }
        if (argc - i != 3) /* "--" "<head>" "<remote>" */
                die("Not handling anything other than two heads merge.");
-       if (verbosity >= 5)
-               buffer_output = 0;
 
-       branch1 = argv[++i];
-       branch2 = argv[++i];
+       o.branch1 = argv[++i];
+       o.branch2 = argv[++i];
 
-       h1 = get_ref(branch1);
-       h2 = get_ref(branch2);
+       if (get_sha1(o.branch1, h1))
+               die("Could not resolve ref '%s'", o.branch1);
+       if (get_sha1(o.branch2, h2))
+               die("Could not resolve ref '%s'", o.branch2);
 
-       branch1 = better_branch_name(branch1);
-       branch2 = better_branch_name(branch2);
-
-       if (show(3))
-               printf("Merging %s with %s\n", branch1, branch2);
-
-       index_fd = hold_locked_index(lock, 1);
-
-       for (i = 0; i < bases_count; i++) {
-               struct commit *ancestor = get_ref(bases[i]);
-               ca = commit_list_insert(ancestor, &ca);
-       }
-       clean = merge_recursive(h1, h2, branch1, branch2, ca, &result);
+       o.branch1 = better_branch_name(o.branch1);
+       o.branch2 = better_branch_name(o.branch2);
 
-       if (active_cache_changed &&
-           (write_cache(index_fd, active_cache, active_nr) ||
-            commit_locked_index(lock)))
-                       die ("unable to write %s", get_index_file());
+       if (o.verbosity >= 3)
+               printf("Merging %s with %s\n", o.branch1, o.branch2);
 
-       return clean ? 0: 1;
+       failed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result);
+       if (failed < 0)
+               return 128; /* die() error code */
+       return failed;
 }
index 1094c5f..5c65a58 100644 (file)
@@ -23,6 +23,7 @@
 #include "color.h"
 #include "rerere.h"
 #include "help.h"
+#include "merge-recursive.h"
 
 #define DEFAULT_TWOHEAD (1<<0)
 #define DEFAULT_OCTOPUS (1<<1)
@@ -547,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,
@@ -693,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;
@@ -722,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);
@@ -779,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);
@@ -916,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);
@@ -1115,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 4004e73..1158e42 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;
similarity index 91%
rename from receive-pack.c
rename to builtin-receive-pack.c
index b81678a..45e3cd9 100644 (file)
@@ -6,6 +6,8 @@
 #include "exec_cmd.h"
 #include "commit.h"
 #include "object.h"
+#include "remote.h"
+#include "transport.h"
 
 static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
 
@@ -462,14 +464,48 @@ static int delete_only(struct command *cmd)
        return 1;
 }
 
-int main(int argc, char **argv)
+static int add_refs_from_alternate(struct alternate_object_database *e, void *unused)
+{
+       char *other = 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++) {
-               char *arg = *argv++;
+               const char *arg = *argv++;
 
                if (*arg == '-') {
                        /* Do flag handling here */
@@ -477,7 +513,7 @@ int main(int argc, char **argv)
                }
                if (dir)
                        usage(receive_pack_usage);
-               dir = arg;
+               dir = xstrdup(arg);
        }
        if (!dir)
                usage(receive_pack_usage);
@@ -497,7 +533,9 @@ int main(int argc, char **argv)
        else if (0 <= receive_unpack_limit)
                unpack_limit = receive_unpack_limit;
 
+       add_alternate_refs();
        write_head_info();
+       clear_extra_refs();
 
        /* EOF */
        packet_flush(1);
index 3667705..8486539 100644 (file)
@@ -12,6 +12,7 @@
 #include "diff.h"
 #include "revision.h"
 #include "rerere.h"
+#include "merge-recursive.h"
 
 /*
  * This implements the builtins revert and cherry-pick.
@@ -201,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];
@@ -263,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";
@@ -281,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
@@ -293,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)
@@ -332,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"
@@ -374,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)) {
index 2af9f29..910db92 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 f3502d3..1495cf6 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 de8c2b6..99af83a 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -533,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);
@@ -640,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;
@@ -708,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 dcb90b1..de83c69 100644 (file)
@@ -683,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);
@@ -861,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 ccfa2a0..fc45d24 100644 (file)
@@ -31,7 +31,6 @@ static inline time_t filetime_to_time_t(const FILETIME *ft)
        return (time_t)winTime;
 }
 
-extern int _getdrive( void );
 /* We keep the do_lstat code in a separate function to avoid recursion.
  * When a path ends with a slash, the stat will fail with ENOENT. In
  * this case, we strip the trailing slashes and stat again.
@@ -55,7 +54,7 @@ static int do_lstat(const char *file_name, struct stat *buf)
                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_dev = buf->st_rdev = (_getdrive() - 1);
+               buf->st_dev = buf->st_rdev = 0; /* not used by Git */
                buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
                buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
                buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
@@ -145,7 +144,7 @@ int mingw_fstat(int fd, struct stat *buf)
                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_dev = buf->st_rdev = (_getdrive() - 1);
+               buf->st_dev = buf->st_rdev = 0; /* not used by Git */
                buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
                buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
                buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
@@ -263,8 +262,13 @@ int poll(struct pollfd *ufds, unsigned int nfds, int timeout)
 {
        int i, pending;
 
-       if (timeout != -1)
+       if (timeout >= 0) {
+               if (nfds == 0) {
+                       Sleep(timeout);
+                       return 0;
+               }
                return errno = EINVAL, error("poll timeout not supported");
+       }
 
        /* When there is only one fd to wait for, then we pretend that
         * input is available and let the actual wait happen when the
index f67d673..17e9861 100644 (file)
@@ -4,6 +4,7 @@
 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 7c2856e..27bab00 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 dd96f8e..67d2cd8 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 2d8d1c3..93f0881 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
diff --git a/contrib/rerere-train.sh b/contrib/rerere-train.sh
new file mode 100755 (executable)
index 0000000..2cfe1b9
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/sh
+# Copyright (c) 2008, Nanako Shiraishi
+# Prime rerere database from existing merge commits
+
+me=rerere-train
+USAGE="$me rev-list-args"
+
+SUBDIRECTORY_OK=Yes
+OPTIONS_SPEC=
+. git-sh-setup
+require_work_tree
+cd_to_toplevel
+
+# Remember original branch
+branch=$(git symbolic-ref -q HEAD) ||
+original_HEAD=$(git rev-parse --verify HEAD) || {
+       echo >&2 "Not on any branch and no commit yet?"
+       exit 1
+}
+
+mkdir -p "$GIT_DIR/rr-cache" || exit
+
+git rev-list --parents "$@" |
+while read commit parent1 other_parents
+do
+       if test -z "$other_parents"
+       then
+               # Skip non-merges
+               continue
+       fi
+       git checkout -q "$parent1^0"
+       if git merge $other_parents >/dev/null 2>&1
+       then
+               # Cleanly merges
+               continue
+       fi
+       if test -s "$GIT_DIR/MERGE_RR"
+       then
+               git show -s --pretty=format:"Learning from %h %s" "$commit"
+               git rerere
+               git checkout -q $commit -- .
+               git rerere
+       fi
+       git reset -q --hard
+done
+
+if test -z "$branch"
+then
+       git checkout "$original_HEAD"
+else
+       git checkout "${branch#refs/heads/}"
+fi
index 2838954..bb70c75 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;
index c315932..3e5582d 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -1083,7 +1083,8 @@ int main(int argc, char **argv)
                openlog("git-daemon", LOG_PID, LOG_DAEMON);
                set_die_routine(daemon_die);
        } else
-               setlinebuf(stderr); /* avoid splitting a message in the middle */
+               /* 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");
@@ -1115,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;
index e7eaff9..ae96c64 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 7d68b7f..b60d345 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 a3793f6..b001d7b 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -24,6 +24,7 @@ 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 */
@@ -155,6 +156,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.")) {
@@ -323,6 +328,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 == '/');
@@ -331,8 +345,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);
@@ -1128,9 +1142,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;
@@ -1461,6 +1479,14 @@ static const struct funcname_pattern_entry *diff_funcname_pattern(struct diff_fi
        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,
@@ -1474,9 +1500,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);
@@ -2333,8 +2369,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)
@@ -2491,6 +2529,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;
diff --git a/diff.h b/diff.h
index 7f53beb..a49d865 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,
index 97ac600..79de701 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 db2836f..2ac832f 100644 (file)
@@ -154,10 +154,7 @@ extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1,
 extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
 extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
 
-extern void set_usage_routine(void (*routine)(const char *err) NORETURN);
 extern void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN);
-extern void set_error_routine(void (*routine)(const char *err, va_list params));
-extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
 
 extern int prefixcmp(const char *str, const char *prefix);
 extern time_t tm_to_time_t(const struct tm *tm);
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 10d8a44..4085e8f 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'"
@@ -665,6 +678,7 @@ 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
@@ -948,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]
+       }
 }
 }
 
@@ -1023,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
@@ -1105,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
@@ -1131,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]} {
                }
@@ -1230,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
 
@@ -1751,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
@@ -1810,6 +1937,7 @@ proc do_quit {} {
                }
        }
 
+       set ret_code $rc
        destroy .
 }
 
@@ -1951,19 +2079,21 @@ 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
 
-       # Do not stage files with conflicts
+       # Determine the state of the file
        if {[info exists file_states($path)]} {
                set state [lindex $file_states($path) 0]
        } else {
                set state {__}
        }
 
-       if {[string first {U} $state] >= 0} {
-               set col 1
-       }
-
        # Restage the file, or simply show the diff
        if {$col == 0 && $y > 1} {
+               # Conflicts need special handling
+               if {[string first {U} $state] >= 0} {
+                       merge_stage_workdir $path $w $lno
+                       return
+               }
+
                if {[string index $state 1] eq {O}} {
                        set mmask {}
                } else {
@@ -2212,26 +2342,36 @@ 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 ui_do_rescan \
@@ -2273,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 \
@@ -2601,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
 #
@@ -2621,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
@@ -2652,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 \
@@ -2860,6 +3013,14 @@ proc create_common_diff_popup {ctxm} {
                -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
 }
@@ -3191,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 827c85d..eb61374 100644 (file)
@@ -256,9 +256,16 @@ constructor new {i_commit i_path i_jump} {
        $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]
@@ -399,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
 }
@@ -508,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
@@ -788,6 +798,16 @@ 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]
@@ -881,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]
                        }
@@ -942,9 +956,20 @@ method _format_offset_date {base offset} {
 }
 
 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]
 
@@ -981,12 +1006,20 @@ method _gitkcommit {} {
 }
 
 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]
 
-               if {[catch {set cparent [git rev-parse --verify "$cmit^"]}]} {
+               # 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;
                }
@@ -996,8 +1029,12 @@ method _blameparent {} {
                # 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 {[catch {set fd [eval git_read diff-tree \
-                               --unified=0 $cparent $cmit $new_path]} err]} {
+               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
index 2977315..3345149 100644 (file)
@@ -168,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.
@@ -177,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]]
@@ -212,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]
@@ -410,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
        #
index a30c80a..abe502d 100644 (file)
@@ -40,6 +40,15 @@ proc reshow_diff {} {
        }
 }
 
+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
 
@@ -60,9 +69,9 @@ 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
@@ -83,36 +92,42 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
 
        set s $file_states($path)
        set m [lindex $s 0]
+       set is_conflict_diff 0
        set current_diff_path $path
        set current_diff_side $w
        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 $scroll_pos]
+               merge_load_stages $path [list show_unmerged_diff $cont_info]
        } elseif {$m eq {_O}} {
-               show_other_diff $path $w $m $scroll_pos
+               show_other_diff $path $w $m $cont_info
        } else {
-               start_show_diff $scroll_pos
+               start_show_diff $cont_info
        }
 }
 
-proc show_unmerged_diff {scroll_pos} {
+proc show_unmerged_diff {cont_info} {
        global current_diff_path current_diff_side
-       global merge_stages ui_diff
+       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"]]
@@ -120,14 +135,14 @@ proc show_unmerged_diff {scroll_pos} {
                        [list "REMOTE:\n" d======= \
                            [list ":1:$current_diff_path" ":3:$current_diff_path"]]
        } else {
-               start_show_diff $scroll_pos
+               start_show_diff $cont_info
                return
        }
 
-       advance_diff_queue $scroll_pos
+       advance_diff_queue $cont_info
 }
 
-proc advance_diff_queue {scroll_pos} {
+proc advance_diff_queue {cont_info} {
        global current_diff_queue ui_diff
 
        set item [lindex $current_diff_queue 0]
@@ -137,10 +152,10 @@ proc advance_diff_queue {scroll_pos} {
        $ui_diff insert end [lindex $item 0] [lindex $item 1]
        $ui_diff conf -state disabled
 
-       start_show_diff $scroll_pos [lindex $item 2]
+       start_show_diff $cont_info [lindex $item 2]
 }
 
-proc show_other_diff {path w m scroll_pos} {
+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
@@ -165,7 +180,9 @@ proc show_other_diff {path w m 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]
@@ -217,16 +234,21 @@ proc show_other_diff {path w m 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 {scroll_pos {add_opts {}}} {
+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
@@ -279,14 +301,14 @@ proc start_show_diff {scroll_pos {add_opts {}}} {
        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
@@ -334,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 {
@@ -353,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 {
@@ -377,12 +401,13 @@ proc read_diff {fd scroll_pos} {
                close $fd
 
                if {$current_diff_queue ne {}} {
-                       advance_diff_queue $scroll_pos
+                       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
@@ -392,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
+               }
        }
 }
 
@@ -432,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]} {
@@ -601,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 7f06b0d..32668fc 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 79c58bc..6ab5701 100644 (file)
@@ -5,24 +5,51 @@ proc merge_resolve_one {stage} {
        global current_diff_path
 
        switch -- $stage {
-               1 { set target [mc "the base version"] }
-               2 { set target [mc "this branch"] }
-               3 { set target [mc "the other branch"] }
+               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 [mc "Force resolution to %s?
-Note that the diff shows only conflicting changes.
+       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." \
-               $target [short_path $current_diff_path]]
+               [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
 
index 9b865f6..c80c939 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
@@ -130,6 +152,7 @@ proc do_options {} {
                {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]
@@ -159,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:"
@@ -171,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
                        }
                        }
@@ -275,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 fa43947..793cca1 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 c3d0d24..65178ae 100755 (executable)
@@ -639,6 +639,14 @@ cmd_sync()
        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
                (
index af8279a..80a5728 100755 (executable)
@@ -2626,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) {
@@ -2638,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;
@@ -2664,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: $!";
@@ -2809,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: $!";
index 384148a..78d236b 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 5582c51..f4b0cf6 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 c6492e5..6733b6f 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 da474d0..18e70a3 100755 (executable)
@@ -5498,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 e2633f8..5f82170 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 eab4e3d..bc30d68 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.
  *
diff --git a/help.c b/help.c
index b278257..fd87bb5 100644 (file)
--- a/help.c
+++ b/help.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "builtin.h"
 #include "exec_cmd.h"
+#include "levenshtein.h"
 #include "help.h"
 
 /* most GUI terminals set COLUMNS (although some don't export it) */
@@ -37,6 +38,16 @@ 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_;
@@ -193,8 +204,8 @@ void load_command_list(const char *prefix,
                while (1) {
                        if ((colon = strchr(path, PATH_SEP)))
                                *colon = 0;
-
-                       list_commands_in_dir(other_cmds, path, prefix);
+                       if (!exec_path || strcmp(path, exec_path))
+                               list_commands_in_dir(other_cmds, path, prefix);
 
                        if (!colon)
                                break;
@@ -250,9 +261,101 @@ int is_in_cmdlist(struct cmdnames *c, const char *s)
        return 0;
 }
 
-void help_unknown_cmd(const char *cmd)
+static int autocorrect;
+static struct cmdnames aliases;
+
+static int git_unknown_cmd_config(const char *var, const char *value, void *cb)
+{
+       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));
+
+       return git_default_config(var, value, cb);
+}
+
+static int levenshtein_compare(const void *p1, const void *p2)
+{
+       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 add_cmd_list(struct cmdnames *cmds, struct cmdnames *old)
 {
+       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;
+}
+
+const char *help_unknown_cmd(const char *cmd)
+{
+       int i, n, best_similarity = 0;
+       struct cmdnames main_cmds, other_cmds;
+
+       memset(&main_cmds, 0, sizeof(main_cmds));
+       memset(&other_cmds, 0, sizeof(main_cmds));
+       memset(&aliases, 0, sizeof(aliases));
+
+       git_config(git_unknown_cmd_config, NULL);
+
+       load_command_list("git-", &main_cmds, &other_cmds);
+
+       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);
+
+       /* 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);
+
+       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;
+       }
+
        fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
+
+       if (best_similarity < 6) {
+               fprintf(stderr, "\nDid you mean %s?\n",
+                       n < 2 ? "this": "one of these");
+
+               for (i = 0; i < n; i++)
+                       fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
+       }
+
        exit(1);
 }
 
diff --git a/help.h b/help.h
index 2733433..56bc154 100644 (file)
--- a/help.h
+++ b/help.h
@@ -5,7 +5,7 @@ struct cmdnames {
        int alloc;
        int cnt;
        struct cmdname {
-               size_t len;
+               size_t len; /* also used for similarity index in help.c */
                char name[FLEX_ARRAY];
        } **names;
 };
index 6805288..c9dd9a1 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 a97fdf5..ed59b79 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;
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 9837c84..4a71614 100644 (file)
@@ -63,6 +63,7 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
                        int virtual_ancestor)
 {
        xpparam_t xpp;
+       int style = 0;
 
        if (buffer_is_binary(orig->ptr, orig->size) ||
            buffer_is_binary(src1->ptr, src1->size) ||
@@ -77,10 +78,12 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
        }
 
        memset(&xpp, 0, sizeof(xpp));
+       if (git_xmerge_style >= 0)
+               style = git_xmerge_style;
        return xdl_merge(orig,
                         src1, name1,
                         src2, name2,
-                        &xpp, XDL_MERGE_ZEALOUS,
+                        &xpp, XDL_MERGE_ZEALOUS | style,
                         result);
 }
 
@@ -95,10 +98,15 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
        char *src, *dst;
        long size;
        const int marker_size = 7;
-
-       int status = ll_xdl_merge(drv_unused, result, path_unused,
-                                 orig, src1, NULL, src2, NULL,
-                                 virtual_ancestor);
+       int status, saved_style;
+
+       /* We have to force the RCS "merge" style */
+       saved_style = git_xmerge_style;
+       git_xmerge_style = 0;
+       status = ll_xdl_merge(drv_unused, result, path_unused,
+                             orig, src1, NULL, src2, NULL,
+                             virtual_ancestor);
+       git_xmerge_style = saved_style;
        if (status <= 0)
                return status;
        size = result->size;
index 30cd5bb..2c1f3e6 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 cha