Merge branch 'jm/free'
authorJunio C Hamano <gitster@pobox.com>
Wed, 27 Feb 2008 21:03:50 +0000 (13:03 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 27 Feb 2008 21:03:50 +0000 (13:03 -0800)
* jm/free:
Avoid unnecessary "if-before-free" tests.

Conflicts:

builtin-branch.c

106 files changed:
Documentation/RelNotes-1.5.4.3.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-am.txt
Documentation/git-branch.txt
Documentation/git-bundle.txt
Documentation/git-checkout.txt
Documentation/git-describe.txt
Documentation/git-filter-branch.txt
Documentation/git-format-patch.txt
Documentation/git-pack-objects.txt
Documentation/git-rev-list.txt
Documentation/git.txt
Documentation/rev-list-options.txt
Documentation/urls.txt
GIT-VERSION-GEN
Makefile
alias.c [new file with mode: 0644]
branch.c [new file with mode: 0644]
branch.h [new file with mode: 0644]
builtin-apply.c
builtin-branch.c
builtin-checkout.c [new file with mode: 0644]
builtin-commit.c
builtin-describe.c
builtin-diff.c
builtin-fast-export.c
builtin-fetch-pack.c
builtin-for-each-ref.c
builtin-init-db.c
builtin-log.c
builtin-merge-file.c
builtin-merge-recursive.c [new file with mode: 0644]
builtin-pack-objects.c
builtin-push.c
builtin-read-tree.c
builtin-reflog.c
builtin-rerere.c
builtin-reset.c
builtin-rev-parse.c
builtin-send-pack.c
builtin-shortlog.c
builtin-tag.c
builtin-verify-tag.c
builtin.h
bundle.c
cache.h
commit.h
config.c
contrib/completion/git-completion.bash
contrib/examples/git-checkout.sh [new file with mode: 0755]
copy.c
date.c
diff-lib.c
diff.c
diff.h
environment.c
git-bisect.sh
git-checkout.sh [deleted file]
git-gui/Makefile
git-gui/git-gui.sh
git-gui/lib/choose_repository.tcl
git-gui/lib/error.tcl
git-pull.sh
git.c
gitweb/gitweb.perl
hash-object.c
help.c
log-tree.c
log-tree.h
merge-recursive.c [deleted file]
merge-recursive.h [new file with mode: 0644]
pretty.c
read-cache.c
receive-pack.c
refs.c
remote.c
remote.h
revision.c
revision.h
run-command.c
run-command.h
shortlog.h [new file with mode: 0644]
t/t0050-filesystem.sh [new file with mode: 0755]
t/t1502-rev-parse-parseopt.sh [new file with mode: 0755]
t/t3200-branch.sh
t/t4013-diff-various.sh
t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ [new file with mode: 0644]
t/t4014-format-patch.sh
t/t4019-diff-wserror.sh
t/t4105-apply-fuzz.sh [new file with mode: 0755]
t/t4125-apply-ws-fuzz.sh [new file with mode: 0755]
t/t5303-hash-object.sh [new file with mode: 0755]
t/t5516-fetch-push.sh
t/t6023-merge-file.sh
t/t6029-merge-subtree.sh [new file with mode: 0755]
t/t6030-bisect-porcelain.sh
t/t7201-co.sh
t/t9001-send-email.sh
thread-utils.c [new file with mode: 0644]
thread-utils.h [new file with mode: 0644]
unpack-trees.c
unpack-trees.h
ws.c
xdiff/xdiff.h
xdiff/xmerge.c
diff --git a/Documentation/RelNotes-1.5.4.3.txt b/Documentation/RelNotes-1.5.4.3.txt
new file mode 100644 (file)
index 0000000..b0fc67f
--- /dev/null
@@ -0,0 +1,27 @@
+GIT v1.5.4.3 Release Notes
+==========================
+
+Fixes since v1.5.4.2
+--------------------
+
+ * RPM spec used to pull in everything with 'git'.  This has been
+   changed so that 'git' package contains just the core parts,
+   and we now supply 'git-all' metapackage to slurp in everything.
+   This should match end user's expectation better.
+
+ * When some refs failed to update, git-push reported "failure"
+   which was unclear if some other refs were updated or all of
+   them failed atomically (the answer is the former).  Reworded
+   the message to clarify this.
+
+ * "git clone" from a repository whose HEAD was misconfigured
+   did not set up the remote properly.  Now it tries to do
+   better.
+
+ * Updated git-push documentation to clarify what "matching"
+   means, in order to reduce user confusion.
+
+ * Updated git-add documentation to clarify "add -u" operates in
+   the current subdirectory you are in, just like other commands.
+
+ * git-gui updates to work on OSX and Windows better.
index 7b676710bab0709970abe7411d2246c0eb2b34b8..4027726f2ee66ebad69412a5c8c6d1aef7f7463f 100644 (file)
@@ -353,6 +353,10 @@ core.whitespace::
   error (enabled by default).
 * `indent-with-non-tab` treats a line that is indented with 8 or more
   space characters as an error (not enabled by default).
+* `cr-at-eol` treats a carriage-return at the end of line as
+  part of the line terminator, i.e. with it, `trailing-space`
+  does not trigger if the character before such a carriage-return
+  is not a whitespace (not enabled by default).
 
 alias.*::
        Command aliases for the linkgit:git[1] command wrapper - e.g.
@@ -375,10 +379,14 @@ apply.whitespace::
 
 branch.autosetupmerge::
        Tells `git-branch` and `git-checkout` to setup new branches
-       so that linkgit:git-pull[1] will appropriately merge from that
-       remote branch.  Note that even if this option is not set,
+       so that linkgit:git-pull[1] will appropriately merge from the
+       starting point branch. Note that even if this option is not set,
        this behavior can be chosen per-branch using the `--track`
-       and `--no-track` options.  This option defaults to true.
+       and `--no-track` options. The valid settings are: `false` -- no
+       automatic setup is done; `true` -- automatic setup is done when the
+       starting point is a remote branch; `always` -- automatic setup is
+       done when the starting point is either a local branch or remote
+       branch. This option defaults to true.
 
 branch.<name>.remote::
        When in branch <name>, it tells `git fetch` which remote to fetch.
@@ -808,6 +816,8 @@ pack.threads::
        warning. This is meant to reduce packing time on multiprocessor
        machines. The required amount of memory for the delta search window
        is however multiplied by the number of threads.
+       Specifying 0 will cause git to auto-detect the number of CPU's
+       and set the number of threads accordingly.
 
 pack.indexVersion::
        Specify the default pack index version.  Valid values are 1 for
@@ -893,6 +903,17 @@ tar.umask::
        archiving user's umask will be used instead.  See umask(2) and
        linkgit:git-archive[1].
 
+url.<base>.insteadOf::
+       Any URL that starts with this value will be rewritten to
+       start, instead, with <base>. In cases where some site serves a
+       large number of repositories, and serves them with multiple
+       access methods, and some users need to use different access
+       methods, this feature allows people to specify any of the
+       equivalent URLs and have git automatically rewrite the URL to
+       the best alternative for the particular user, even for a
+       never-before-seen repository on the site.  When more than one
+       insteadOf strings match a given URL, the longest match is used.
+
 user.email::
        Your email address to be recorded in any newly created commits.
        Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and
index 8d35cbd60d9d6fb2a0aeef8a6b956e099743cb28..8dc5b001c42376be1b9f814d516ea43f5c711071 100644 (file)
@@ -170,6 +170,14 @@ endif::git-format-patch[]
        Swap two inputs; that is, show differences from index or
        on-disk file to tree contents.
 
+--relative[=<path>]::
+       When run from a subdirectory of the project, it can be
+       told to exclude changes outside the directory and show
+       pathnames relative to it with this option.  When you are
+       not in a subdirectory (e.g. in a bare repository), you
+       can name which subdirectory to make the output relative
+       to by giving a <path> as an argument.
+
 --text::
        Treat all files as text.
 
index 2ffba2102b1c2a7dbd093fed89a15b2e644aa398..e640fc75cd3c463370f62b623a4060b014d9371a 100644 (file)
@@ -138,7 +138,7 @@ aborts in the middle,.  You can recover from this in one of two ways:
 
 The command refuses to process new mailboxes while `.dotest`
 directory exists, so if you decide to start over from scratch,
-run `rm -f .dotest` before running the command with mailbox
+run `rm -f -r .dotest` before running the command with mailbox
 names.
 
 
index 7e8874acaacbd6990ccaf66d5bddbff6e4c1e5ba..6f07a17a2c310a67f23034c520ab10670407c006 100644 (file)
@@ -35,11 +35,10 @@ working tree to it; use "git checkout <newbranch>" to switch to the
 new branch.
 
 When a local branch is started off a remote branch, git sets up the
-branch so that linkgit:git-pull[1] will appropriately merge from that
-remote branch.  If this behavior is not desired, it is possible to
-disable it using the global `branch.autosetupmerge` configuration
-flag.  That setting can be overridden by using the `--track`
-and `--no-track` options.
+branch so that linkgit:git-pull[1] will appropriately merge from
+the remote branch. This behavior may be changed via the global
+`branch.autosetupmerge` configuration flag. That setting can be
+overridden by using the `--track` and `--no-track` options.
 
 With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.
 If <oldbranch> had a corresponding reflog, it is renamed to match
@@ -105,20 +104,19 @@ OPTIONS
        Display the full sha1s in output listing rather than abbreviating them.
 
 --track::
-       Set up configuration so that git-pull will automatically
-       retrieve data from the remote branch.  Use this if you always
-       pull from the same remote branch into the new branch, or if you
-       don't want to use "git pull <repository> <refspec>" explicitly.
-       This behavior is the default.  Set the
-       branch.autosetupmerge configuration variable to false if you
-       want git-checkout and git-branch to always behave as if
-       '--no-track' were given.
+       When creating a new branch, set up configuration so that git-pull
+       will automatically retrieve data from the start point, which must be
+       a branch. Use this if you always pull from the same upstream branch
+       into the new branch, and if you don't want to use "git pull
+       <repository> <refspec>" explicitly. This behavior is the default
+       when the start point is a remote branch. Set the
+       branch.autosetupmerge configuration variable to `false` if you want
+       git-checkout and git-branch to always behave as if '--no-track' were
+       given. Set it to `always` if you want this behavior when the
+       start-point is either a local or remote branch.
 
 --no-track::
-       When a branch is created off a remote branch,
-       set up configuration so that git-pull will not retrieve data
-       from the remote branch, ignoring the branch.autosetupmerge
-       configuration variable.
+       Ignore the branch.autosetupmerge configuration variable.
 
 <branchname>::
        The name of the branch to create or delete.
index 72f080a9728e076de21c19e77fc760a038e9885e..505ac056e6586e0b4b19e5a8bbbb4bef86b6faf0 100644 (file)
@@ -99,36 +99,62 @@ Assume two repositories exist as R1 on machine A, and R2 on machine B.
 For whatever reason, direct connection between A and B is not allowed,
 but we can move data from A to B via some mechanism (CD, email, etc).
 We want to update R2 with developments made on branch master in R1.
+
+To create the bundle you have to specify the basis. You have some options:
+
+- Without basis.
++
+This is useful when sending the whole history.
+
+------------
+$ git bundle create mybundle master
+------------
+
+- Using temporally tags.
++
 We set a tag in R1 (lastR2bundle) after the previous such transport,
 and move it afterwards to help build the bundle.
 
-in R1 on A:
-
 ------------
 $ git-bundle create mybundle master ^lastR2bundle
 $ git tag -f lastR2bundle master
 ------------
 
-(move mybundle from A to B by some mechanism)
+- Using a tag present in both repositories
+
+------------
+$ git bundle create mybundle master ^v1.0.0
+------------
+
+- A basis based on time.
+
+------------
+$ git bundle create mybundle master --since=10.days.ago
+------------
 
-in R2 on B:
+- With a limit on the number of commits
 
 ------------
-$ git-bundle verify mybundle
-$ git-fetch mybundle  refspec
+$ git bundle create mybundle master -n 10
 ------------
 
-where refspec is refInBundle:localRef
+Then you move mybundle from A to B, and in R2 on B:
 
+------------
+$ git-bundle verify mybundle
+$ git-fetch mybundle master:localRef
+------------
 
-Also, with something like this in your config:
+With something like this in the config in R2:
 
+------------------------
 [remote "bundle"]
     url = /home/me/tmp/file.bdl
     fetch = refs/heads/*:refs/remotes/origin/*
+------------------------
 
 You can first sneakernet the bundle file to ~/tmp/file.bdl and
-then these commands:
+then these commands on machine B:
 
 ------------
 $ git ls-remote bundle
index b4cfa044bbb969add6f434070a8666fc0c325d15..4014e7256d48262c97a38a543415d5a53f5407b9 100644 (file)
@@ -48,21 +48,19 @@ OPTIONS
        may restrict the characters allowed in a branch name.
 
 --track::
-       When -b is given and a branch is created off a remote branch,
-       set up configuration so that git-pull will automatically
-       retrieve data from the remote branch.  Use this if you always
-       pull from the same remote branch into the new branch, or if you
-       don't want to use "git pull <repository> <refspec>" explicitly.
-       This behavior is the default.  Set the
-       branch.autosetupmerge configuration variable to false if you
-       want git-checkout and git-branch to always behave as if
-       '--no-track' were given.
+       When creating a new branch, set up configuration so that git-pull
+       will automatically retrieve data from the start point, which must be
+       a branch. Use this if you always pull from the same upstream branch
+       into the new branch, and if you don't want to use "git pull
+       <repository> <refspec>" explicitly. This behavior is the default
+       when the start point is a remote branch. Set the
+       branch.autosetupmerge configuration variable to `false` if you want
+       git-checkout and git-branch to always behave as if '--no-track' were
+       given. Set it to `always` if you want this behavior when the
+       start-point is either a local or remote branch.
 
 --no-track::
-       When -b is given and a branch is created off a remote branch,
-       set up configuration so that git-pull will not retrieve data
-       from the remote branch, ignoring the branch.autosetupmerge
-       configuration variable.
+       Ignore the branch.autosetupmerge configuration variable.
 
 -l::
        Create the new branch's reflog.  This activates recording of
index 1c3dfb40c63350651efb570ed540b69c34ccbc81..fbb40a29165a47e627358b5b9f8afb50e84958cd 100644 (file)
@@ -45,6 +45,11 @@ OPTIONS
        candidates to describe the input committish consider
        up to <n> candidates.  Increasing <n> above 10 will take
        slightly longer but may produce a more accurate result.
+       An <n> of 0 will cause only exact matches to be output.
+
+--exact-match::
+       Only output exact matches (a tag directly references the
+       supplied commit).  This is a synonym for --candidates=0.
 
 --debug::
        Verbosely display information about the searching strategy
index e22dfa580383c0a7af00d9d56e01a1869fb6ce75..543a1cf105b6df5953e529ecb4f9f88db273164f 100644 (file)
@@ -56,7 +56,9 @@ notable exception of the commit filter, for technical reasons).
 Prior to that, the $GIT_COMMIT environment variable will be set to contain
 the id of the commit being rewritten.  Also, GIT_AUTHOR_NAME,
 GIT_AUTHOR_EMAIL, GIT_AUTHOR_DATE, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL,
-and GIT_COMMITTER_DATE are set according to the current commit.
+and GIT_COMMITTER_DATE are set according to the current commit. If any
+evaluation of <command> returns a non-zero exit status, the whole operation
+will be aborted.
 
 A 'map' function is available that takes an "original sha1 id" argument
 and outputs a "rewritten sha1 id" if the commit has been already
@@ -197,7 +199,7 @@ happened).  If this is not the case, use:
 
 --------------------------------------------------------------------------
 git filter-branch --parent-filter \
-       'cat; test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>"' HEAD
+       'test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>" || cat' HEAD
 --------------------------------------------------------------------------
 
 or even simpler:
@@ -240,6 +242,15 @@ committed a merge between P1 and P2, it will be propagated properly
 and all children of the merge will become merge commits with P1,P2
 as their parents instead of the merge commit.
 
+You can rewrite the commit log messages using `--message-filter`.  For
+example, `git-svn-id` strings in a repository created by `git-svn` can
+be removed this way:
+
+-------------------------------------------------------
+git filter-branch --message-filter '
+       sed -e "/^git-svn-id:/d"
+'
+-------------------------------------------------------
 
 To restrict rewriting to only part of the history, specify a revision
 range in addition to the new branch name.  The new branch name will
index 651efe6ca16a02841c49f4b6a57ae2cf5ae8183d..b5207b76040d02e34452175779340658b943a84e 100644 (file)
@@ -10,13 +10,15 @@ SYNOPSIS
 --------
 [verse]
 'git-format-patch' [-k] [-o <dir> | --stdout] [--thread]
-                   [--attach[=<boundary>] | --inline[=<boundary>]]
-                   [-s | --signoff] [<common diff options>]
-                   [-n | --numbered | -N | --no-numbered]
-                   [--start-number <n>] [--numbered-files]
-                   [--in-reply-to=Message-Id] [--suffix=.<sfx>]
-                   [--ignore-if-in-upstream]
-                   [--subject-prefix=Subject-Prefix]
+                  [--attach[=<boundary>] | --inline[=<boundary>]]
+                  [-s | --signoff] [<common diff options>]
+                  [-n | --numbered | -N | --no-numbered]
+                  [--start-number <n>] [--numbered-files]
+                  [--in-reply-to=Message-Id] [--suffix=.<sfx>]
+                  [--ignore-if-in-upstream]
+                  [--subject-prefix=Subject-Prefix]
+                  [--cc=<email>]
+                  [--cover-letter]
                   [ <since> | <revision range> ]
 
 DESCRIPTION
@@ -135,6 +137,15 @@ include::diff-options.txt[]
        allows for useful naming of a patch series, and can be
        combined with the --numbered option.
 
+--cc=<email>::
+       Add a "Cc:" header to the email headers. This is in addition
+       to any configured headers, and may be used multiple times.
+
+--cover-letter::
+       Generate a cover letter template.  You still have to fill in
+       a description, but the shortlog and the diffstat will be
+       generated for you.
+
 --suffix=.<sfx>::
        Instead of using `.patch` as the suffix for generated
        filenames, use specified suffix.  A common alternative is
index 8353be186fcc83092acac16b4fc164d6ea669621..5c1bd3b0813a95ee0c1831a2e10f5512a9330793 100644 (file)
@@ -177,6 +177,8 @@ base-name::
        This is meant to reduce packing time on multiprocessor machines.
        The required amount of memory for the delta search window is
        however multiplied by the number of threads.
+       Specifying 0 will cause git to auto-detect the number of CPU's
+       and set the number of threads accordingly.
 
 --index-version=<version>[,<offset>]::
        This is intended to be used by the test suite only. It allows
index 5b96eabfce9eecbfccaff16096c616105a040c3d..a8d489f9f29034d732a67375b617f3315109aa05 100644 (file)
@@ -31,6 +31,7 @@ SYNOPSIS
             [ \--(author|committer|grep)=<pattern> ]
             [ \--regexp-ignore-case | \-i ]
             [ \--extended-regexp | \-E ]
+            [ \--fixed-strings | \-F ]
             [ \--date={local|relative|default|iso|rfc|short} ]
             [ [\--objects | \--objects-edge] [ \--unpacked ] ]
             [ \--pretty | \--header ]
index d57bed618fa6be93be64c19a9f6d8a6bc51a5d49..741ae0e4c8ec9a3aaaef19430b0f76e4baedb546 100644 (file)
@@ -43,9 +43,10 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.5.4.2/git.html[documentation for release 1.5.4.2]
+* link:v1.5.4.3/git.html[documentation for release 1.5.4.3]
 
 * release notes for
+  link:RelNotes-1.5.4.3.txt[1.5.4.3],
   link:RelNotes-1.5.4.2.txt[1.5.4.2],
   link:RelNotes-1.5.4.1.txt[1.5.4.1],
   link:RelNotes-1.5.4.txt[1.5.4].
index a8138e27a1a10fb0c93608af149ef22323ba9dc5..259072c07883cf15db4162737a63a6506b7443b4 100644 (file)
@@ -153,6 +153,11 @@ limiting may be applied.
        Consider the limiting patterns to be extended regular expressions
        instead of the default basic regular expressions.
 
+-F, --fixed-strings::
+
+       Consider the limiting patterns to be fixed strings (don't interpret
+       pattern as a regular expression).
+
 --remove-empty::
 
        Stop when a given path disappears from the tree.
index 81ac17f32a0587e3d2d41eb8ee89dd85e13f1802..fa34c6747194aaecf9e8124462129b8bbc9ae7d4 100644 (file)
@@ -44,3 +44,26 @@ endif::git-clone[]
 ifdef::git-clone[]
 They are equivalent, except the former implies --local option.
 endif::git-clone[]
+
+
+If there are a large number of similarly-named remote repositories and
+you want to use a different format for them (such that the URLs you
+use will be rewritten into URLs that work), you can create a
+configuration section of the form:
+
+------------
+       [url "<actual url base>"]
+               insteadOf = <other url base>
+------------
+
+For example, with this:
+
+------------
+       [url "git://git.host.xz/"]
+               insteadOf = host.xz:/path/to/
+               insteadOf = work:
+------------
+
+a URL like "work:repo.git" or like "host.xz:/path/to/repo.git" will be
+rewritten in any context that takes a URL to be "git://git.host.xz/repo.git".
+
index 1ad324e23696f66451a9ffc05d77f459bf106e83..6ddf04d2161e5fd959c2fd69a7331520e8075d98 100755 (executable)
@@ -16,7 +16,8 @@ elif test -d .git &&
        case "$VN" in
        *$LF*) (exit 1) ;;
        v[0-9]*)
-               git diff-index --quiet HEAD || VN="$VN-dirty" ;;
+               test -z "$(git diff-index --name-only HEAD)" ||
+               VN="$VN-dirty" ;;
        esac
 then
        VN=$(echo "$VN" | sed -e 's/-/./g');
index d33a556ffed5c2cddb9577ea014d3c017082abb8..a5b6eebf1a0b65b481f6dd8f1e38006b46e87c83 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -226,7 +226,7 @@ BASIC_CFLAGS =
 BASIC_LDFLAGS =
 
 SCRIPT_SH = \
-       git-bisect.sh git-checkout.sh \
+       git-bisect.sh \
        git-clone.sh \
        git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
        git-pull.sh git-rebase.sh git-rebase--interactive.sh \
@@ -265,23 +265,23 @@ PROGRAMS = \
        git-upload-pack$X \
        git-pack-redundant$X git-var$X \
        git-merge-tree$X git-imap-send$X \
-       git-merge-recursive$X \
        $(EXTRA_PROGRAMS)
 
 # Empty...
 EXTRA_PROGRAMS =
 
+# List built-in command $C whose implementation cmd_$C() is not in
+# builtin-$C.o but is linked in as part of some other command.
 BUILT_INS = \
        git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
        git-get-tar-commit-id$X git-init$X git-repo-config$X \
        git-fsck-objects$X git-cherry-pick$X git-peek-remote$X git-status$X \
+       git-merge-subtree$X \
        $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
 
-ALL_PROGRAMS += git-merge-subtree$X
-
 # what 'all' will build but not install in gitexecdir
 OTHER_PROGRAMS = git$X gitweb/gitweb.cgi
 
@@ -327,7 +327,8 @@ LIB_OBJS = \
        alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
        color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
        convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
-       transport.o bundle.o walker.o parse-options.o ws.o archive.o
+       transport.o bundle.o walker.o parse-options.o ws.o archive.o branch.o \
+       alias.o
 
 BUILTIN_OBJS = \
        builtin-add.o \
@@ -339,6 +340,7 @@ BUILTIN_OBJS = \
        builtin-bundle.o \
        builtin-cat-file.o \
        builtin-check-attr.o \
+       builtin-checkout.o \
        builtin-checkout-index.o \
        builtin-check-ref-format.o \
        builtin-clean.o \
@@ -369,6 +371,7 @@ BUILTIN_OBJS = \
        builtin-merge-base.o \
        builtin-merge-file.o \
        builtin-merge-ours.o \
+       builtin-merge-recursive.o \
        builtin-mv.o \
        builtin-name-rev.o \
        builtin-pack-objects.o \
@@ -741,6 +744,7 @@ endif
 ifdef THREADED_DELTA_SEARCH
        BASIC_CFLAGS += -DTHREADED_DELTA_SEARCH
        EXTLIBS += -lpthread
+       LIB_OBJS += thread-utils.o
 endif
 
 ifeq ($(TCLTK_PATH),)
@@ -838,9 +842,6 @@ help.o: help.c common-cmds.h GIT-CFLAGS
                '-DGIT_MAN_PATH="$(mandir_SQ)"' \
                '-DGIT_INFO_PATH="$(infodir_SQ)"' $<
 
-git-merge-subtree$X: git-merge-recursive$X
-       $(QUIET_BUILT_IN)$(RM) $@ && ln git-merge-recursive$X $@
-
 $(BUILT_INS): git$X
        $(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@
 
@@ -1102,7 +1103,7 @@ git.spec: git.spec.in
        mv $@+ $@
 
 GIT_TARNAME=git-$(GIT_VERSION)
-dist: git.spec git-archive configure
+dist: git.spec git-archive$(X) configure
        ./git-archive --format=tar \
                --prefix=$(GIT_TARNAME)/ HEAD^{tree} > $(GIT_TARNAME).tar
        @mkdir -p $(GIT_TARNAME)
diff --git a/alias.c b/alias.c
new file mode 100644 (file)
index 0000000..116cac8
--- /dev/null
+++ b/alias.c
@@ -0,0 +1,22 @@
+#include "cache.h"
+
+static const char *alias_key;
+static char *alias_val;
+static int alias_lookup_cb(const char *k, const char *v)
+{
+       if (!prefixcmp(k, "alias.") && !strcmp(k+6, alias_key)) {
+               if (!v)
+                       return config_error_nonbool(k);
+               alias_val = xstrdup(v);
+               return 0;
+       }
+       return 0;
+}
+
+char *alias_lookup(const char *alias)
+{
+       alias_key = alias;
+       alias_val = NULL;
+       git_config(alias_lookup_cb);
+       return alias_val;
+}
diff --git a/branch.c b/branch.c
new file mode 100644 (file)
index 0000000..daf862e
--- /dev/null
+++ b/branch.c
@@ -0,0 +1,152 @@
+#include "cache.h"
+#include "branch.h"
+#include "refs.h"
+#include "remote.h"
+#include "commit.h"
+
+struct tracking {
+       struct refspec spec;
+       char *src;
+       const char *remote;
+       int matches;
+};
+
+static int find_tracked_branch(struct remote *remote, void *priv)
+{
+       struct tracking *tracking = priv;
+
+       if (!remote_find_tracking(remote, &tracking->spec)) {
+               if (++tracking->matches == 1) {
+                       tracking->src = tracking->spec.src;
+                       tracking->remote = remote->name;
+               } else {
+                       free(tracking->spec.src);
+                       if (tracking->src) {
+                               free(tracking->src);
+                               tracking->src = NULL;
+                       }
+               }
+               tracking->spec.src = NULL;
+       }
+
+       return 0;
+}
+
+/*
+ * This is called when new_ref is branched off of orig_ref, and tries
+ * to infer the settings for branch.<new_ref>.{remote,merge} from the
+ * config.
+ */
+static int setup_tracking(const char *new_ref, const char *orig_ref,
+                          enum branch_track track)
+{
+       char key[1024];
+       struct tracking tracking;
+
+       if (strlen(new_ref) > 1024 - 7 - 7 - 1)
+               return error("Tracking not set up: name too long: %s",
+                               new_ref);
+
+       memset(&tracking, 0, sizeof(tracking));
+       tracking.spec.dst = (char *)orig_ref;
+       if (for_each_remote(find_tracked_branch, &tracking))
+               return 1;
+
+       if (!tracking.matches)
+               switch (track) {
+               case BRANCH_TRACK_ALWAYS:
+               case BRANCH_TRACK_EXPLICIT:
+                       break;
+               default:
+                       return 1;
+               }
+
+       if (tracking.matches > 1)
+               return error("Not tracking: ambiguous information for ref %s",
+                               orig_ref);
+
+       sprintf(key, "branch.%s.remote", new_ref);
+       git_config_set(key, tracking.remote ?  tracking.remote : ".");
+       sprintf(key, "branch.%s.merge", new_ref);
+       git_config_set(key, tracking.src ? tracking.src : orig_ref);
+       free(tracking.src);
+       printf("Branch %s set up to track %s branch %s.\n", new_ref,
+               tracking.remote ? "remote" : "local", orig_ref);
+
+       return 0;
+}
+
+void create_branch(const char *head,
+                  const char *name, const char *start_name,
+                  int force, int reflog, enum branch_track track)
+{
+       struct ref_lock *lock;
+       struct commit *commit;
+       unsigned char sha1[20];
+       char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];
+       int forcing = 0;
+
+       snprintf(ref, sizeof ref, "refs/heads/%s", name);
+       if (check_ref_format(ref))
+               die("'%s' is not a valid branch name.", name);
+
+       if (resolve_ref(ref, sha1, 1, NULL)) {
+               if (!force)
+                       die("A branch named '%s' already exists.", name);
+               else if (!is_bare_repository() && !strcmp(head, name))
+                       die("Cannot force update the current branch.");
+               forcing = 1;
+       }
+
+       real_ref = NULL;
+       if (get_sha1(start_name, sha1))
+               die("Not a valid object name: '%s'.", start_name);
+
+       switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {
+       case 0:
+               /* Not branching from any existing branch */
+               if (track == BRANCH_TRACK_EXPLICIT)
+                       die("Cannot setup tracking information; starting point is not a branch.");
+               break;
+       case 1:
+               /* Unique completion -- good */
+               break;
+       default:
+               die("Ambiguous object name: '%s'.", start_name);
+               break;
+       }
+
+       if ((commit = lookup_commit_reference(sha1)) == NULL)
+               die("Not a valid branch point: '%s'.", start_name);
+       hashcpy(sha1, commit->object.sha1);
+
+       lock = lock_any_ref_for_update(ref, NULL, 0);
+       if (!lock)
+               die("Failed to lock ref for update: %s.", strerror(errno));
+
+       if (reflog)
+               log_all_ref_updates = 1;
+
+       if (forcing)
+               snprintf(msg, sizeof msg, "branch: Reset from %s",
+                        start_name);
+       else
+               snprintf(msg, sizeof msg, "branch: Created from %s",
+                        start_name);
+
+       if (real_ref && track)
+               setup_tracking(name, real_ref, track);
+
+       if (write_ref_sha1(lock, sha1, msg) < 0)
+               die("Failed to write ref: %s.", strerror(errno));
+
+       free(real_ref);
+}
+
+void remove_branch_state(void)
+{
+       unlink(git_path("MERGE_HEAD"));
+       unlink(git_path("rr-cache/MERGE_RR"));
+       unlink(git_path("MERGE_MSG"));
+       unlink(git_path("SQUASH_MSG"));
+}
diff --git a/branch.h b/branch.h
new file mode 100644 (file)
index 0000000..9f0c2a2
--- /dev/null
+++ b/branch.h
@@ -0,0 +1,24 @@
+#ifndef BRANCH_H
+#define BRANCH_H
+
+/* Functions for acting on the information about branches. */
+
+/*
+ * Creates a new branch, where head is the branch currently checked
+ * out, name is the new branch name, start_name is the name of the
+ * existing branch that the new branch should start from, force
+ * enables overwriting an existing (non-head) branch, reflog creates a
+ * reflog for the branch, and track causes the new branch to be
+ * configured to merge the remote branch that start_name is a tracking
+ * branch for (if any).
+ */
+void create_branch(const char *head, const char *name, const char *start_name,
+                  int force, int reflog, enum branch_track track);
+
+/*
+ * Remove information about the state of working on the current
+ * branch. (E.g., MERGE_HEAD)
+ */
+void remove_branch_state(void);
+
+#endif
index 6a88ff018df8890af8ef2e17340d5969dc1e396b..a3f075df4bd91de8d3423420cfcffd9722ad9586 100644 (file)
@@ -161,6 +161,84 @@ struct patch {
        struct patch *next;
 };
 
+/*
+ * A line in a file, len-bytes long (includes the terminating LF,
+ * except for an incomplete line at the end if the file ends with
+ * one), and its contents hashes to 'hash'.
+ */
+struct line {
+       size_t len;
+       unsigned hash : 24;
+       unsigned flag : 8;
+#define LINE_COMMON     1
+};
+
+/*
+ * This represents a "file", which is an array of "lines".
+ */
+struct image {
+       char *buf;
+       size_t len;
+       size_t nr;
+       size_t alloc;
+       struct line *line_allocated;
+       struct line *line;
+};
+
+static uint32_t hash_line(const char *cp, size_t len)
+{
+       size_t i;
+       uint32_t h;
+       for (i = 0, h = 0; i < len; i++) {
+               if (!isspace(cp[i])) {
+                       h = h * 3 + (cp[i] & 0xff);
+               }
+       }
+       return h;
+}
+
+static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
+{
+       ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc);
+       img->line_allocated[img->nr].len = len;
+       img->line_allocated[img->nr].hash = hash_line(bol, len);
+       img->line_allocated[img->nr].flag = flag;
+       img->nr++;
+}
+
+static void prepare_image(struct image *image, char *buf, size_t len,
+                         int prepare_linetable)
+{
+       const char *cp, *ep;
+
+       memset(image, 0, sizeof(*image));
+       image->buf = buf;
+       image->len = len;
+
+       if (!prepare_linetable)
+               return;
+
+       ep = image->buf + image->len;
+       cp = image->buf;
+       while (cp < ep) {
+               const char *next;
+               for (next = cp; next < ep && *next != '\n'; next++)
+                       ;
+               if (next < ep)
+                       next++;
+               add_line_info(image, cp, next - cp, 0);
+               cp = next;
+       }
+       image->line = image->line_allocated;
+}
+
+static void clear_image(struct image *image)
+{
+       free(image->buf);
+       image->buf = NULL;
+       image->len = 0;
+}
+
 static void say_patch_name(FILE *output, const char *pre,
                           struct patch *patch, const char *post)
 {
@@ -1437,227 +1515,338 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
        }
 }
 
-static int find_offset(const char *buf, unsigned long size,
-                      const char *fragment, unsigned long fragsize,
-                      int line, int *lines)
+static void update_pre_post_images(struct image *preimage,
+                                  struct image *postimage,
+                                  char *buf,
+                                  size_t len)
 {
-       int i;
-       unsigned long start, backwards, forwards;
+       int i, ctx;
+       char *new, *old, *fixed;
+       struct image fixed_preimage;
 
-       if (fragsize > size)
-               return -1;
+       /*
+        * Update the preimage with whitespace fixes.  Note that we
+        * are not losing preimage->buf -- apply_one_fragment() will
+        * free "oldlines".
+        */
+       prepare_image(&fixed_preimage, buf, len, 1);
+       assert(fixed_preimage.nr == preimage->nr);
+       for (i = 0; i < preimage->nr; i++)
+               fixed_preimage.line[i].flag = preimage->line[i].flag;
+       free(preimage->line_allocated);
+       *preimage = fixed_preimage;
 
-       start = 0;
-       if (line > 1) {
-               unsigned long offset = 0;
-               i = line-1;
-               while (offset + fragsize <= size) {
-                       if (buf[offset++] == '\n') {
-                               start = offset;
-                               if (!--i)
-                                       break;
-                       }
+       /*
+        * Adjust the common context lines in postimage, in place.
+        * This is possible because whitespace fixing does not make
+        * the string grow.
+        */
+       new = old = postimage->buf;
+       fixed = preimage->buf;
+       for (i = ctx = 0; i < postimage->nr; i++) {
+               size_t len = postimage->line[i].len;
+               if (!(postimage->line[i].flag & LINE_COMMON)) {
+                       /* an added line -- no counterparts in preimage */
+                       memmove(new, old, len);
+                       old += len;
+                       new += len;
+                       continue;
                }
+
+               /* a common context -- skip it in the original postimage */
+               old += len;
+
+               /* and find the corresponding one in the fixed preimage */
+               while (ctx < preimage->nr &&
+                      !(preimage->line[ctx].flag & LINE_COMMON)) {
+                       fixed += preimage->line[ctx].len;
+                       ctx++;
+               }
+               if (preimage->nr <= ctx)
+                       die("oops");
+
+               /* and copy it in, while fixing the line length */
+               len = preimage->line[ctx].len;
+               memcpy(new, fixed, len);
+               new += len;
+               fixed += len;
+               postimage->line[i].len = len;
+               ctx++;
        }
 
-       /* Exact line number? */
-       if ((start + fragsize <= size) &&
-           !memcmp(buf + start, fragment, fragsize))
-               return start;
+       /* Fix the length of the whole thing */
+       postimage->len = new - postimage->buf;
+}
+
+static int match_fragment(struct image *img,
+                         struct image *preimage,
+                         struct image *postimage,
+                         unsigned long try,
+                         int try_lno,
+                         unsigned ws_rule,
+                         int match_beginning, int match_end)
+{
+       int i;
+       char *fixed_buf, *buf, *orig, *target;
+
+       if (preimage->nr + try_lno > img->nr)
+               return 0;
+
+       if (match_beginning && try_lno)
+               return 0;
+
+       if (match_end && preimage->nr + try_lno != img->nr)
+               return 0;
+
+       /* Quick hash check */
+       for (i = 0; i < preimage->nr; i++)
+               if (preimage->line[i].hash != img->line[try_lno + i].hash)
+                       return 0;
+
+       /*
+        * Do we have an exact match?  If we were told to match
+        * at the end, size must be exactly at try+fragsize,
+        * otherwise try+fragsize must be still within the preimage,
+        * and either case, the old piece should match the preimage
+        * exactly.
+        */
+       if ((match_end
+            ? (try + preimage->len == img->len)
+            : (try + preimage->len <= img->len)) &&
+           !memcmp(img->buf + try, preimage->buf, preimage->len))
+               return 1;
+
+       if (ws_error_action != correct_ws_error)
+               return 0;
+
+       /*
+        * The hunk does not apply byte-by-byte, but the hash says
+        * it might with whitespace fuzz.
+        */
+       fixed_buf = xmalloc(preimage->len + 1);
+       buf = fixed_buf;
+       orig = preimage->buf;
+       target = img->buf + try;
+       for (i = 0; i < preimage->nr; i++) {
+               size_t fixlen; /* length after fixing the preimage */
+               size_t oldlen = preimage->line[i].len;
+               size_t tgtlen = img->line[try_lno + i].len;
+               size_t tgtfixlen; /* length after fixing the target line */
+               char tgtfixbuf[1024], *tgtfix;
+               int match;
+
+               /* Try fixing the line in the preimage */
+               fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL);
+
+               /* Try fixing the line in the target */
+               if (sizeof(tgtfixbuf) < tgtlen)
+                       tgtfix = tgtfixbuf;
+               else
+                       tgtfix = xmalloc(tgtlen);
+               tgtfixlen = ws_fix_copy(tgtfix, target, tgtlen, ws_rule, NULL);
+
+               /*
+                * If they match, either the preimage was based on
+                * a version before our tree fixed whitespace breakage,
+                * or we are lacking a whitespace-fix patch the tree
+                * the preimage was based on already had (i.e. target
+                * has whitespace breakage, the preimage doesn't).
+                * In either case, we are fixing the whitespace breakages
+                * so we might as well take the fix together with their
+                * real change.
+                */
+               match = (tgtfixlen == fixlen && !memcmp(tgtfix, buf, fixlen));
+
+               if (tgtfix != tgtfixbuf)
+                       free(tgtfix);
+               if (!match)
+                       goto unmatch_exit;
+
+               orig += oldlen;
+               buf += fixlen;
+               target += tgtlen;
+       }
+
+       /*
+        * Yes, the preimage is based on an older version that still
+        * has whitespace breakages unfixed, and fixing them makes the
+        * hunk match.  Update the context lines in the postimage.
+        */
+       update_pre_post_images(preimage, postimage,
+                              fixed_buf, buf - fixed_buf);
+       return 1;
+
+ unmatch_exit:
+       free(fixed_buf);
+       return 0;
+}
+
+static int find_pos(struct image *img,
+                   struct image *preimage,
+                   struct image *postimage,
+                   int line,
+                   unsigned ws_rule,
+                   int match_beginning, int match_end)
+{
+       int i;
+       unsigned long backwards, forwards, try;
+       int backwards_lno, forwards_lno, try_lno;
+
+       if (preimage->nr > img->nr)
+               return -1;
+
+       /*
+        * If match_begining or match_end is specified, there is no
+        * point starting from a wrong line that will never match and
+        * wander around and wait for a match at the specified end.
+        */
+       if (match_beginning)
+               line = 0;
+       else if (match_end)
+               line = img->nr - preimage->nr;
+
+       if (line > img->nr)
+               line = img->nr;
+
+       try = 0;
+       for (i = 0; i < line; i++)
+               try += img->line[i].len;
 
        /*
         * There's probably some smart way to do this, but I'll leave
         * that to the smart and beautiful people. I'm simple and stupid.
         */
-       backwards = start;
-       forwards = start;
+       backwards = try;
+       backwards_lno = line;
+       forwards = try;
+       forwards_lno = line;
+       try_lno = line;
+
        for (i = 0; ; i++) {
-               unsigned long try;
-               int n;
+               if (match_fragment(img, preimage, postimage,
+                                  try, try_lno, ws_rule,
+                                  match_beginning, match_end))
+                       return try_lno;
+
+       again:
+               if (backwards_lno == 0 && forwards_lno == img->nr)
+                       break;
 
-               /* "backward" */
                if (i & 1) {
-                       if (!backwards) {
-                               if (forwards + fragsize > size)
-                                       break;
-                               continue;
+                       if (backwards_lno == 0) {
+                               i++;
+                               goto again;
                        }
-                       do {
-                               --backwards;
-                       } while (backwards && buf[backwards-1] != '\n');
+                       backwards_lno--;
+                       backwards -= img->line[backwards_lno].len;
                        try = backwards;
+                       try_lno = backwards_lno;
                } else {
-                       while (forwards + fragsize <= size) {
-                               if (buf[forwards++] == '\n')
-                                       break;
+                       if (forwards_lno == img->nr) {
+                               i++;
+                               goto again;
                        }
+                       forwards += img->line[forwards_lno].len;
+                       forwards_lno++;
                        try = forwards;
+                       try_lno = forwards_lno;
                }
 
-               if (try + fragsize > size)
-                       continue;
-               if (memcmp(buf + try, fragment, fragsize))
-                       continue;
-               n = (i >> 1)+1;
-               if (i & 1)
-                       n = -n;
-               *lines = n;
-               return try;
        }
-
-       /*
-        * We should start searching forward and backward.
-        */
        return -1;
 }
 
-static void remove_first_line(const char **rbuf, int *rsize)
+static void remove_first_line(struct image *img)
 {
-       const char *buf = *rbuf;
-       int size = *rsize;
-       unsigned long offset;
-       offset = 0;
-       while (offset <= size) {
-               if (buf[offset++] == '\n')
-                       break;
-       }
-       *rsize = size - offset;
-       *rbuf = buf + offset;
+       img->buf += img->line[0].len;
+       img->len -= img->line[0].len;
+       img->line++;
+       img->nr--;
 }
 
-static void remove_last_line(const char **rbuf, int *rsize)
+static void remove_last_line(struct image *img)
 {
-       const char *buf = *rbuf;
-       int size = *rsize;
-       unsigned long offset;
-       offset = size - 1;
-       while (offset > 0) {
-               if (buf[--offset] == '\n')
-                       break;
-       }
-       *rsize = offset + 1;
+       img->len -= img->line[--img->nr].len;
 }
 
-static int apply_line(char *output, const char *patch, int plen,
-                     unsigned ws_rule)
+static void update_image(struct image *img,
+                        int applied_pos,
+                        struct image *preimage,
+                        struct image *postimage)
 {
        /*
-        * plen is number of bytes to be copied from patch,
-        * starting at patch+1 (patch[0] is '+').  Typically
-        * patch[plen] is '\n', unless this is the incomplete
-        * last line.
+        * remove the copy of preimage at offset in img
+        * and replace it with postimage
         */
-       int i;
-       int add_nl_to_tail = 0;
-       int fixed = 0;
-       int last_tab_in_indent = 0;
-       int last_space_in_indent = 0;
-       int need_fix_leading_space = 0;
-       char *buf;
-
-       if ((ws_error_action != correct_ws_error) || !whitespace_error ||
-           *patch != '+') {
-               memcpy(output, patch + 1, plen);
-               return plen;
-       }
-
-       /*
-        * Strip trailing whitespace
-        */
-       if ((ws_rule & WS_TRAILING_SPACE) &&
-           (1 < plen && isspace(patch[plen-1]))) {
-               if (patch[plen] == '\n')
-                       add_nl_to_tail = 1;
-               plen--;
-               while (0 < plen && isspace(patch[plen]))
-                       plen--;
-               fixed = 1;
-       }
-
-       /*
-        * Check leading whitespaces (indent)
-        */
-       for (i = 1; i < plen; i++) {
-               char ch = patch[i];
-               if (ch == '\t') {
-                       last_tab_in_indent = i;
-                       if ((ws_rule & WS_SPACE_BEFORE_TAB) &&
-                           0 < last_space_in_indent)
-                           need_fix_leading_space = 1;
-               } else if (ch == ' ') {
-                       last_space_in_indent = i;
-                       if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
-                           8 <= i - last_tab_in_indent)
-                               need_fix_leading_space = 1;
-               }
-               else
-                       break;
-       }
-
-       buf = output;
-       if (need_fix_leading_space) {
-               int consecutive_spaces = 0;
-               int last = last_tab_in_indent + 1;
-
-               if (ws_rule & WS_INDENT_WITH_NON_TAB) {
-                       /* have "last" point at one past the indent */
-                       if (last_tab_in_indent < last_space_in_indent)
-                               last = last_space_in_indent + 1;
-                       else
-                               last = last_tab_in_indent + 1;
-               }
+       int i, nr;
+       size_t remove_count, insert_count, applied_at = 0;
+       char *result;
 
+       for (i = 0; i < applied_pos; i++)
+               applied_at += img->line[i].len;
+
+       remove_count = 0;
+       for (i = 0; i < preimage->nr; i++)
+               remove_count += img->line[applied_pos + i].len;
+       insert_count = postimage->len;
+
+       /* Adjust the contents */
+       result = xmalloc(img->len + insert_count - remove_count + 1);
+       memcpy(result, img->buf, applied_at);
+       memcpy(result + applied_at, postimage->buf, postimage->len);
+       memcpy(result + applied_at + postimage->len,
+              img->buf + (applied_at + remove_count),
+              img->len - (applied_at + remove_count));
+       free(img->buf);
+       img->buf = result;
+       img->len += insert_count - remove_count;
+       result[img->len] = '\0';
+
+       /* Adjust the line table */
+       nr = img->nr + postimage->nr - preimage->nr;
+       if (preimage->nr < postimage->nr) {
                /*
-                * between patch[1..last], strip the funny spaces,
-                * updating them to tab as needed.
+                * NOTE: this knows that we never call remove_first_line()
+                * on anything other than pre/post image.
                 */
-               for (i = 1; i < last; i++, plen--) {
-                       char ch = patch[i];
-                       if (ch != ' ') {
-                               consecutive_spaces = 0;
-                               *output++ = ch;
-                       } else {
-                               consecutive_spaces++;
-                               if (consecutive_spaces == 8) {
-                                       *output++ = '\t';
-                                       consecutive_spaces = 0;
-                               }
-                       }
-               }
-               while (0 < consecutive_spaces--)
-                       *output++ = ' ';
-               fixed = 1;
-               i = last;
+               img->line = xrealloc(img->line, nr * sizeof(*img->line));
+               img->line_allocated = img->line;
        }
-       else
-               i = 1;
-
-       memcpy(output, patch + i, plen);
-       if (add_nl_to_tail)
-               output[plen++] = '\n';
-       if (fixed)
-               applied_after_fixing_ws++;
-       return output + plen - buf;
+       if (preimage->nr != postimage->nr)
+               memmove(img->line + applied_pos + postimage->nr,
+                       img->line + applied_pos + preimage->nr,
+                       (img->nr - (applied_pos + preimage->nr)) *
+                       sizeof(*img->line));
+       memcpy(img->line + applied_pos,
+              postimage->line,
+              postimage->nr * sizeof(*img->line));
+       img->nr = nr;
 }
 
-static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
+static int apply_one_fragment(struct image *img, struct fragment *frag,
                              int inaccurate_eof, unsigned ws_rule)
 {
        int match_beginning, match_end;
        const char *patch = frag->patch;
-       int offset, size = frag->size;
-       char *old = xmalloc(size);
-       char *new = xmalloc(size);
-       const char *oldlines, *newlines;
-       int oldsize = 0, newsize = 0;
+       int size = frag->size;
+       char *old, *new, *oldlines, *newlines;
        int new_blank_lines_at_end = 0;
        unsigned long leading, trailing;
-       int pos, lines;
+       int pos, applied_pos;
+       struct image preimage;
+       struct image postimage;
+
+       memset(&preimage, 0, sizeof(preimage));
+       memset(&postimage, 0, sizeof(postimage));
+       oldlines = xmalloc(size);
+       newlines = xmalloc(size);
 
+       old = oldlines;
+       new = newlines;
        while (size > 0) {
                char first;
                int len = linelen(patch, size);
-               int plen;
+               int plen, added;
                int added_blank_line = 0;
 
                if (!len)
@@ -1670,7 +1859,7 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
                 * followed by "\ No newline", then we also remove the
                 * last one (which is the newline, of course).
                 */
-               plen = len-1;
+               plen = len - 1;
                if (len < size && patch[len] == '\\')
                        plen--;
                first = *patch;
@@ -1687,25 +1876,40 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
                        if (plen < 0)
                                /* ... followed by '\No newline'; nothing */
                                break;
-                       old[oldsize++] = '\n';
-                       new[newsize++] = '\n';
+                       *old++ = '\n';
+                       *new++ = '\n';
+                       add_line_info(&preimage, "\n", 1, LINE_COMMON);
+                       add_line_info(&postimage, "\n", 1, LINE_COMMON);
                        break;
                case ' ':
                case '-':
-                       memcpy(old + oldsize, patch + 1, plen);
-                       oldsize += plen;
+                       memcpy(old, patch + 1, plen);
+                       add_line_info(&preimage, old, plen,
+                                     (first == ' ' ? LINE_COMMON : 0));
+                       old += plen;
                        if (first == '-')
                                break;
                /* Fall-through for ' ' */
                case '+':
-                       if (first != '+' || !no_add) {
-                               int added = apply_line(new + newsize, patch,
-                                                      plen, ws_rule);
-                               newsize += added;
-                               if (first == '+' &&
-                                   added == 1 && new[newsize-1] == '\n')
-                                       added_blank_line = 1;
+                       /* --no-add does not add new lines */
+                       if (first == '+' && no_add)
+                               break;
+
+                       if (first != '+' ||
+                           !whitespace_error ||
+                           ws_error_action != correct_ws_error) {
+                               memcpy(new, patch + 1, plen);
+                               added = plen;
                        }
+                       else {
+                               added = ws_fix_copy(new, patch + 1, plen, ws_rule, &applied_after_fixing_ws);
+                       }
+                       add_line_info(&postimage, new, added,
+                                     (first == '+' ? 0 : LINE_COMMON));
+                       new += added;
+                       if (first == '+' &&
+                           added == 1 && new[-1] == '\n')
+                               added_blank_line = 1;
                        break;
                case '@': case '\\':
                        /* Ignore it, we already handled it */
@@ -1722,16 +1926,13 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
                patch += len;
                size -= len;
        }
-
        if (inaccurate_eof &&
-           oldsize > 0 && old[oldsize - 1] == '\n' &&
-           newsize > 0 && new[newsize - 1] == '\n') {
-               oldsize--;
-               newsize--;
+           old > oldlines && old[-1] == '\n' &&
+           new > newlines && new[-1] == '\n') {
+               old--;
+               new--;
        }
 
-       oldlines = old;
-       newlines = new;
        leading = frag->leading;
        trailing = frag->trailing;
 
@@ -1752,33 +1953,21 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
                match_end = !trailing;
        }
 
-       lines = 0;
-       pos = frag->newpos;
+       pos = frag->newpos ? (frag->newpos - 1) : 0;
+       preimage.buf = oldlines;
+       preimage.len = old - oldlines;
+       postimage.buf = newlines;
+       postimage.len = new - newlines;
+       preimage.line = preimage.line_allocated;
+       postimage.line = postimage.line_allocated;
+
        for (;;) {
-               offset = find_offset(buf->buf, buf->len,
-                                    oldlines, oldsize, pos, &lines);
-               if (match_end && offset + oldsize != buf->len)
-                       offset = -1;
-               if (match_beginning && offset)
-                       offset = -1;
-               if (offset >= 0) {
-                       if (ws_error_action == correct_ws_error &&
-                           (buf->len - oldsize - offset == 0)) /* end of file? */
-                               newsize -= new_blank_lines_at_end;
-
-                       /* Warn if it was necessary to reduce the number
-                        * of context lines.
-                        */
-                       if ((leading != frag->leading) ||
-                           (trailing != frag->trailing))
-                               fprintf(stderr, "Context reduced to (%ld/%ld)"
-                                       " to apply fragment at %d\n",
-                                       leading, trailing, pos + lines);
-
-                       strbuf_splice(buf, offset, oldsize, newlines, newsize);
-                       offset = 0;
+
+               applied_pos = find_pos(img, &preimage, &postimage, pos,
+                                      ws_rule, match_beginning, match_end);
+
+               if (applied_pos >= 0)
                        break;
-               }
 
                /* Am I at my context limits? */
                if ((leading <= p_context) && (trailing <= p_context))
@@ -1787,33 +1976,64 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
                        match_beginning = match_end = 0;
                        continue;
                }
+
                /*
                 * Reduce the number of context lines; reduce both
                 * leading and trailing if they are equal otherwise
                 * just reduce the larger context.
                 */
                if (leading >= trailing) {
-                       remove_first_line(&oldlines, &oldsize);
-                       remove_first_line(&newlines, &newsize);
+                       remove_first_line(&preimage);
+                       remove_first_line(&postimage);
                        pos--;
                        leading--;
                }
                if (trailing > leading) {
-                       remove_last_line(&oldlines, &oldsize);
-                       remove_last_line(&newlines, &newsize);
+                       remove_last_line(&preimage);
+                       remove_last_line(&postimage);
                        trailing--;
                }
        }
 
-       if (offset && apply_verbosely)
-               error("while searching for:\n%.*s", oldsize, oldlines);
+       if (applied_pos >= 0) {
+               if (ws_error_action == correct_ws_error &&
+                   new_blank_lines_at_end &&
+                   postimage.nr + applied_pos == img->nr) {
+                       /*
+                        * If the patch application adds blank lines
+                        * at the end, and if the patch applies at the
+                        * end of the image, remove those added blank
+                        * lines.
+                        */
+                       while (new_blank_lines_at_end--)
+                               remove_last_line(&postimage);
+               }
 
-       free(old);
-       free(new);
-       return offset;
+               /*
+                * Warn if it was necessary to reduce the number
+                * of context lines.
+                */
+               if ((leading != frag->leading) ||
+                   (trailing != frag->trailing))
+                       fprintf(stderr, "Context reduced to (%ld/%ld)"
+                               " to apply fragment at %d\n",
+                               leading, trailing, applied_pos+1);
+               update_image(img, applied_pos, &preimage, &postimage);
+       } else {
+               if (apply_verbosely)
+                       error("while searching for:\n%.*s",
+                             (int)(old - oldlines), oldlines);
+       }
+
+       free(oldlines);
+       free(newlines);
+       free(preimage.line_allocated);
+       free(postimage.line_allocated);
+
+       return (applied_pos < 0);
 }
 
-static int apply_binary_fragment(struct strbuf *buf, struct patch *patch)
+static int apply_binary_fragment(struct image *img, struct patch *patch)
 {
        struct fragment *fragment = patch->fragments;
        unsigned long len;
@@ -1830,22 +2050,26 @@ static int apply_binary_fragment(struct strbuf *buf, struct patch *patch)
        }
        switch (fragment->binary_patch_method) {
        case BINARY_DELTA_DEFLATED:
-               dst = patch_delta(buf->buf, buf->len, fragment->patch,
+               dst = patch_delta(img->buf, img->len, fragment->patch,
                                  fragment->size, &len);
                if (!dst)
                        return -1;
-               /* XXX patch_delta NUL-terminates */
-               strbuf_attach(buf, dst, len, len + 1);
+               clear_image(img);
+               img->buf = dst;
+               img->len = len;
                return 0;
        case BINARY_LITERAL_DEFLATED:
-               strbuf_reset(buf);
-               strbuf_add(buf, fragment->patch, fragment->size);
+               clear_image(img);
+               img->len = fragment->size;
+               img->buf = xmalloc(img->len+1);
+               memcpy(img->buf, fragment->patch, img->len);
+               img->buf[img->len] = '\0';
                return 0;
        }
        return -1;
 }
 
-static int apply_binary(struct strbuf *buf, struct patch *patch)
+static int apply_binary(struct image *img, struct patch *patch)
 {
        const char *name = patch->old_name ? patch->old_name : patch->new_name;
        unsigned char sha1[20];
@@ -1866,7 +2090,7 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
                 * See if the old one matches what the patch
                 * applies to.
                 */
-               hash_sha1_file(buf->buf, buf->len, blob_type, sha1);
+               hash_sha1_file(img->buf, img->len, blob_type, sha1);
                if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
                        return error("the patch applies to '%s' (%s), "
                                     "which does not match the "
@@ -1875,14 +2099,14 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
        }
        else {
                /* Otherwise, the old one must be empty. */
-               if (buf->len)
+               if (img->len)
                        return error("the patch applies to an empty "
                                     "'%s' but it is not empty", name);
        }
 
        get_sha1_hex(patch->new_sha1_prefix, sha1);
        if (is_null_sha1(sha1)) {
-               strbuf_release(buf);
+               clear_image(img);
                return 0; /* deletion patch */
        }
 
@@ -1897,20 +2121,21 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
                        return error("the necessary postimage %s for "
                                     "'%s' cannot be read",
                                     patch->new_sha1_prefix, name);
-               /* XXX read_sha1_file NUL-terminates */
-               strbuf_attach(buf, result, size, size + 1);
+               clear_image(img);
+               img->buf = result;
+               img->len = size;
        } else {
                /*
                 * We have verified buf matches the preimage;
                 * apply the patch data to it, which is stored
                 * in the patch->fragments->{patch,size}.
                 */
-               if (apply_binary_fragment(buf, patch))
+               if (apply_binary_fragment(img, patch))
                        return error("binary patch does not apply to '%s'",
                                     name);
 
                /* verify that the result matches */
-               hash_sha1_file(buf->buf, buf->len, blob_type, sha1);
+               hash_sha1_file(img->buf, img->len, blob_type, sha1);
                if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
                        return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)",
                                name, patch->new_sha1_prefix, sha1_to_hex(sha1));
@@ -1919,7 +2144,7 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
        return 0;
 }
 
-static int apply_fragments(struct strbuf *buf, struct patch *patch)
+static int apply_fragments(struct image *img, struct patch *patch)
 {
        struct fragment *frag = patch->fragments;
        const char *name = patch->old_name ? patch->old_name : patch->new_name;
@@ -1927,10 +2152,10 @@ static int apply_fragments(struct strbuf *buf, struct patch *patch)
        unsigned inaccurate_eof = patch->inaccurate_eof;
 
        if (patch->is_binary)
-               return apply_binary(buf, patch);
+               return apply_binary(img, patch);
 
        while (frag) {
-               if (apply_one_fragment(buf, frag, inaccurate_eof, ws_rule)) {
+               if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) {
                        error("patch failed: %s:%ld", name, frag->oldpos);
                        if (!apply_with_reject)
                                return -1;
@@ -1966,6 +2191,9 @@ static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
 static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
 {
        struct strbuf buf;
+       struct image image;
+       size_t len;
+       char *img;
 
        strbuf_init(&buf, 0);
        if (cached) {
@@ -1988,9 +2216,14 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
                }
        }
 
-       if (apply_fragments(&buf, patch) < 0)
+       img = strbuf_detach(&buf, &len);
+       prepare_image(&image, img, len, !patch->is_binary);
+
+       if (apply_fragments(&image, patch) < 0)
                return -1; /* note with --reject this succeeds. */
-       patch->result = strbuf_detach(&buf, &patch->resultsize);
+       patch->result = image.buf;
+       patch->resultsize = image.len;
+       free(image.line_allocated);
 
        if (0 < patch->is_delete && patch->resultsize)
                return error("removal patch leaves file contents");
index 79177007e617ef2b77c5e39dbdedbf9c668cbe25..5bc4526f645d40a4a32c50240975ad26f49b8e3b 100644 (file)
@@ -12,6 +12,7 @@
 #include "builtin.h"
 #include "remote.h"
 #include "parse-options.h"
+#include "branch.h"
 
 static const char * const builtin_branch_usage[] = {
        "git-branch [options] [-r | -a]",
@@ -29,8 +30,6 @@ static const char * const builtin_branch_usage[] = {
 static const char *head;
 static unsigned char head_sha1[20];
 
-static int branch_track = 1;
-
 static int branch_use_color = -1;
 static char branch_colors[][COLOR_MAXLEN] = {
        "\033[m",       /* reset */
@@ -75,10 +74,6 @@ static int git_branch_config(const char *var, const char *value)
                color_parse(value, var, branch_colors[slot]);
                return 0;
        }
-       if (!strcmp(var, "branch.autosetupmerge")) {
-               branch_track = git_config_bool(var, value);
-               return 0;
-       }
        return git_color_default_config(var, value);
 }
 
@@ -357,140 +352,6 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
        free_ref_list(&ref_list);
 }
 
-struct tracking {
-       struct refspec spec;
-       char *src;
-       const char *remote;
-       int matches;
-};
-
-static int find_tracked_branch(struct remote *remote, void *priv)
-{
-       struct tracking *tracking = priv;
-
-       if (!remote_find_tracking(remote, &tracking->spec)) {
-               if (++tracking->matches == 1) {
-                       tracking->src = tracking->spec.src;
-                       tracking->remote = remote->name;
-               } else {
-                       free(tracking->spec.src);
-                       if (tracking->src) {
-                               free(tracking->src);
-                               tracking->src = NULL;
-                       }
-               }
-               tracking->spec.src = NULL;
-       }
-
-       return 0;
-}
-
-
-/*
- * This is called when new_ref is branched off of orig_ref, and tries
- * to infer the settings for branch.<new_ref>.{remote,merge} from the
- * config.
- */
-static int setup_tracking(const char *new_ref, const char *orig_ref)
-{
-       char key[1024];
-       struct tracking tracking;
-
-       if (strlen(new_ref) > 1024 - 7 - 7 - 1)
-               return error("Tracking not set up: name too long: %s",
-                               new_ref);
-
-       memset(&tracking, 0, sizeof(tracking));
-       tracking.spec.dst = (char *)orig_ref;
-       if (for_each_remote(find_tracked_branch, &tracking) ||
-                       !tracking.matches)
-               return 1;
-
-       if (tracking.matches > 1)
-               return error("Not tracking: ambiguous information for ref %s",
-                               orig_ref);
-
-       if (tracking.matches == 1) {
-               sprintf(key, "branch.%s.remote", new_ref);
-               git_config_set(key, tracking.remote ?  tracking.remote : ".");
-               sprintf(key, "branch.%s.merge", new_ref);
-               git_config_set(key, tracking.src);
-               free(tracking.src);
-               printf("Branch %s set up to track remote branch %s.\n",
-                              new_ref, orig_ref);
-       }
-
-       return 0;
-}
-
-static void create_branch(const char *name, const char *start_name,
-                         int force, int reflog, int track)
-{
-       struct ref_lock *lock;
-       struct commit *commit;
-       unsigned char sha1[20];
-       char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];
-       int forcing = 0;
-
-       snprintf(ref, sizeof ref, "refs/heads/%s", name);
-       if (check_ref_format(ref))
-               die("'%s' is not a valid branch name.", name);
-
-       if (resolve_ref(ref, sha1, 1, NULL)) {
-               if (!force)
-                       die("A branch named '%s' already exists.", name);
-               else if (!is_bare_repository() && !strcmp(head, name))
-                       die("Cannot force update the current branch.");
-               forcing = 1;
-       }
-
-       real_ref = NULL;
-       if (get_sha1(start_name, sha1))
-               die("Not a valid object name: '%s'.", start_name);
-
-       switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {
-       case 0:
-               /* Not branching from any existing branch */
-               real_ref = NULL;
-               break;
-       case 1:
-               /* Unique completion -- good */
-               break;
-       default:
-               die("Ambiguous object name: '%s'.", start_name);
-               break;
-       }
-
-       if ((commit = lookup_commit_reference(sha1)) == NULL)
-               die("Not a valid branch point: '%s'.", start_name);
-       hashcpy(sha1, commit->object.sha1);
-
-       lock = lock_any_ref_for_update(ref, NULL, 0);
-       if (!lock)
-               die("Failed to lock ref for update: %s.", strerror(errno));
-
-       if (reflog)
-               log_all_ref_updates = 1;
-
-       if (forcing)
-               snprintf(msg, sizeof msg, "branch: Reset from %s",
-                        start_name);
-       else
-               snprintf(msg, sizeof msg, "branch: Created from %s",
-                        start_name);
-
-       /* When branching off a remote branch, set up so that git-pull
-          automatically merges from there.  So far, this is only done for
-          remotes registered via .git/config.  */
-       if (real_ref && track)
-               setup_tracking(name, real_ref);
-
-       if (write_ref_sha1(lock, sha1, msg) < 0)
-               die("Failed to write ref: %s.", strerror(errno));
-
-       free(real_ref);
-}
-
 static void rename_branch(const char *oldname, const char *newname, int force)
 {
        char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100];
@@ -551,14 +412,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 {
        int delete = 0, rename = 0, force_create = 0;
        int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
-       int reflog = 0, track;
+       int reflog = 0;
+       enum branch_track track;
        int kinds = REF_LOCAL_BRANCH;
        struct commit_list *with_commit = NULL;
 
        struct option options[] = {
                OPT_GROUP("Generic options"),
                OPT__VERBOSE(&verbose),
-               OPT_BOOLEAN( 0 , "track",  &track, "set up tracking mode (see git-pull(1))"),
+               OPT_SET_INT( 0 , "track",  &track, "set up tracking mode (see git-pull(1))",
+                       BRANCH_TRACK_EXPLICIT),
                OPT_BOOLEAN( 0 , "color",  &branch_use_color, "use colored output"),
                OPT_SET_INT('r', NULL,     &kinds, "act on remote-tracking branches",
                        REF_REMOTE_BRANCH),
@@ -589,7 +452,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (branch_use_color == -1)
                branch_use_color = git_use_color_default;
 
-       track = branch_track;
+       track = git_branch_track;
        argc = parse_options(argc, argv, options, builtin_branch_usage, 0);
        if (!!delete + !!rename + !!force_create > 1)
                usage_with_options(builtin_branch_usage, options);
@@ -615,7 +478,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        else if (rename && (argc == 2))
                rename_branch(argv[0], argv[1], rename > 1);
        else if (argc <= 2)
-               create_branch(argv[0], (argc == 2) ? argv[1] : head,
+               create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
                              force_create, reflog, track);
        else
                usage_with_options(builtin_branch_usage, options);
diff --git a/builtin-checkout.c b/builtin-checkout.c
new file mode 100644 (file)
index 0000000..4a4bb8b
--- /dev/null
@@ -0,0 +1,569 @@
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "refs.h"
+#include "commit.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "unpack-trees.h"
+#include "dir.h"
+#include "run-command.h"
+#include "merge-recursive.h"
+#include "branch.h"
+#include "diff.h"
+#include "revision.h"
+#include "remote.h"
+
+static const char * const checkout_usage[] = {
+       "git checkout [options] <branch>",
+       "git checkout [options] [<branch>] -- <file>...",
+       NULL,
+};
+
+static int post_checkout_hook(struct commit *old, struct commit *new,
+                             int changed)
+{
+       struct child_process proc;
+       const char *name = git_path("hooks/post-checkout");
+       const char *argv[5];
+
+       if (access(name, X_OK) < 0)
+               return 0;
+
+       memset(&proc, 0, sizeof(proc));
+       argv[0] = name;
+       argv[1] = xstrdup(sha1_to_hex(old->object.sha1));
+       argv[2] = xstrdup(sha1_to_hex(new->object.sha1));
+       argv[3] = changed ? "1" : "0";
+       argv[4] = NULL;
+       proc.argv = argv;
+       proc.no_stdin = 1;
+       proc.stdout_to_stderr = 1;
+       return run_command(&proc);
+}
+
+static int update_some(const unsigned char *sha1, const char *base, int baselen,
+                      const char *pathname, unsigned mode, int stage)
+{
+       int len;
+       struct cache_entry *ce;
+
+       if (S_ISGITLINK(mode))
+               return 0;
+
+       if (S_ISDIR(mode))
+               return READ_TREE_RECURSIVE;
+
+       len = baselen + strlen(pathname);
+       ce = xcalloc(1, cache_entry_size(len));
+       hashcpy(ce->sha1, sha1);
+       memcpy(ce->name, base, baselen);
+       memcpy(ce->name + baselen, pathname, len - baselen);
+       ce->ce_flags = create_ce_flags(len, 0);
+       ce->ce_mode = create_ce_mode(mode);
+       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+       return 0;
+}
+
+static int read_tree_some(struct tree *tree, const char **pathspec)
+{
+       int newfd;
+       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+       newfd = hold_locked_index(lock_file, 1);
+       read_cache();
+
+       read_tree_recursive(tree, "", 0, 0, pathspec, update_some);
+
+       if (write_cache(newfd, active_cache, active_nr) ||
+           commit_locked_index(lock_file))
+               die("unable to write new index file");
+
+       /* update the index with the given tree's info
+        * for all args, expanding wildcards, and exit
+        * with any non-zero return code.
+        */
+       return 0;
+}
+
+static int checkout_paths(const char **pathspec)
+{
+       int pos;
+       struct checkout state;
+       static char *ps_matched;
+       unsigned char rev[20];
+       int flag;
+       struct commit *head;
+
+       for (pos = 0; pathspec[pos]; pos++)
+               ;
+       ps_matched = xcalloc(1, pos);
+
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+               pathspec_match(pathspec, ps_matched, ce->name, 0);
+       }
+
+       if (report_path_error(ps_matched, pathspec, 0))
+               return 1;
+
+       memset(&state, 0, sizeof(state));
+       state.force = 1;
+       state.refresh_cache = 1;
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+               if (pathspec_match(pathspec, NULL, ce->name, 0)) {
+                       checkout_entry(ce, &state, NULL);
+               }
+       }
+
+       resolve_ref("HEAD", rev, 0, &flag);
+       head = lookup_commit_reference_gently(rev, 1);
+
+       return post_checkout_hook(head, head, 0);
+}
+
+static void show_local_changes(struct object *head)
+{
+       struct rev_info rev;
+       /* I think we want full paths, even if we're in a subdirectory. */
+       init_revisions(&rev, NULL);
+       rev.abbrev = 0;
+       rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
+       add_pending_object(&rev, head, NULL);
+       run_diff_index(&rev, 0);
+}
+
+static void describe_detached_head(char *msg, struct commit *commit)
+{
+       struct strbuf sb;
+       strbuf_init(&sb, 0);
+       parse_commit(commit);
+       pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, "", "", 0, 0);
+       fprintf(stderr, "%s %s... %s\n", msg,
+               find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
+       strbuf_release(&sb);
+}
+
+static int reset_to_new(struct tree *tree, int quiet)
+{
+       struct unpack_trees_options opts;
+       struct tree_desc tree_desc;
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = -1;
+       opts.update = 1;
+       opts.reset = 1;
+       opts.merge = 1;
+       opts.fn = oneway_merge;
+       opts.verbose_update = !quiet;
+       parse_tree(tree);
+       init_tree_desc(&tree_desc, tree->buffer, tree->size);
+       if (unpack_trees(1, &tree_desc, &opts))
+               return 128;
+       return 0;
+}
+
+static void reset_clean_to_new(struct tree *tree, int quiet)
+{
+       struct unpack_trees_options opts;
+       struct tree_desc tree_desc;
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = -1;
+       opts.skip_unmerged = 1;
+       opts.reset = 1;
+       opts.merge = 1;
+       opts.fn = oneway_merge;
+       opts.verbose_update = !quiet;
+       parse_tree(tree);
+       init_tree_desc(&tree_desc, tree->buffer, tree->size);
+       if (unpack_trees(1, &tree_desc, &opts))
+               exit(128);
+}
+
+struct checkout_opts {
+       int quiet;
+       int merge;
+       int force;
+
+       char *new_branch;
+       int new_branch_log;
+       enum branch_track track;
+};
+
+struct branch_info {
+       const char *name; /* The short name used */
+       const char *path; /* The full name of a real branch */
+       struct commit *commit; /* The named commit */
+};
+
+static void setup_branch_path(struct branch_info *branch)
+{
+       struct strbuf buf;
+       strbuf_init(&buf, 0);
+       strbuf_addstr(&buf, "refs/heads/");
+       strbuf_addstr(&buf, branch->name);
+       branch->path = strbuf_detach(&buf, NULL);
+}
+
+static int merge_working_tree(struct checkout_opts *opts,
+                             struct branch_info *old, struct branch_info *new)
+{
+       int ret;
+       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+       int newfd = hold_locked_index(lock_file, 1);
+       read_cache();
+
+       if (opts->force) {
+               ret = reset_to_new(new->commit->tree, opts->quiet);
+               if (ret)
+                       return ret;
+       } else {
+               struct tree_desc trees[2];
+               struct tree *tree;
+               struct unpack_trees_options topts;
+               memset(&topts, 0, sizeof(topts));
+               topts.head_idx = -1;
+
+               refresh_cache(REFRESH_QUIET);
+
+               if (unmerged_cache()) {
+                       error("you need to resolve your current index first");
+                       return 1;
+               }
+
+               /* 2-way merge to the new branch */
+               topts.update = 1;
+               topts.merge = 1;
+               topts.gently = opts->merge;
+               topts.verbose_update = !opts->quiet;
+               topts.fn = twoway_merge;
+               topts.dir = xcalloc(1, sizeof(*topts.dir));
+               topts.dir->show_ignored = 1;
+               topts.dir->exclude_per_dir = ".gitignore";
+               tree = parse_tree_indirect(old->commit->object.sha1);
+               init_tree_desc(&trees[0], tree->buffer, tree->size);
+               tree = parse_tree_indirect(new->commit->object.sha1);
+               init_tree_desc(&trees[1], tree->buffer, tree->size);
+
+               if (unpack_trees(2, trees, &topts)) {
+                       /*
+                        * Unpack couldn't do a trivial merge; either
+                        * give up or do a real merge, depending on
+                        * whether the merge flag was used.
+                        */
+                       struct tree *result;
+                       struct tree *work;
+                       if (!opts->merge)
+                               return 1;
+                       parse_commit(old->commit);
+
+                       /* Do more real merge */
+
+                       /*
+                        * We update the index fully, then write the
+                        * tree from the index, then merge the new
+                        * branch with the current tree, with the old
+                        * branch as the base. Then we reset the index
+                        * (but not the working tree) to the new
+                        * branch, leaving the working tree as the
+                        * merged version, but skipping unmerged
+                        * entries in the index.
+                        */
+
+                       add_files_to_cache(0, NULL, NULL);
+                       work = write_tree_from_memory();
+
+                       ret = reset_to_new(new->commit->tree, opts->quiet);
+                       if (ret)
+                               return ret;
+                       merge_trees(new->commit->tree, work, old->commit->tree,
+                                   new->name, "local", &result);
+                       reset_clean_to_new(new->commit->tree, opts->quiet);
+               }
+       }
+
+       if (write_cache(newfd, active_cache, active_nr) ||
+           commit_locked_index(lock_file))
+               die("unable to write new index file");
+
+       if (!opts->force)
+               show_local_changes(&new->commit->object);
+
+       return 0;
+}
+
+static void report_tracking(struct branch_info *new, struct checkout_opts *opts)
+{
+       /*
+        * We have switched to a new branch; is it building on
+        * top of another branch, and if so does that other branch
+        * have changes we do not have yet?
+        */
+       char *base;
+       unsigned char sha1[20];
+       struct commit *ours, *theirs;
+       char symmetric[84];
+       struct rev_info revs;
+       const char *rev_argv[10];
+       int rev_argc;
+       int num_ours, num_theirs;
+       const char *remote_msg;
+       struct branch *branch = branch_get(new->name);
+
+       /*
+        * Nothing to report unless we are marked to build on top of
+        * somebody else.
+        */
+       if (!branch || !branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
+               return;
+
+       /*
+        * If what we used to build on no longer exists, there is
+        * nothing to report.
+        */
+       base = branch->merge[0]->dst;
+       if (!resolve_ref(base, sha1, 1, NULL))
+               return;
+
+       theirs = lookup_commit(sha1);
+       ours = new->commit;
+       if (!hashcmp(sha1, ours->object.sha1))
+               return; /* we are the same */
+
+       /* Run "rev-list --left-right ours...theirs" internally... */
+       rev_argc = 0;
+       rev_argv[rev_argc++] = NULL;
+       rev_argv[rev_argc++] = "--left-right";
+       rev_argv[rev_argc++] = symmetric;
+       rev_argv[rev_argc++] = "--";
+       rev_argv[rev_argc] = NULL;
+
+       strcpy(symmetric, sha1_to_hex(ours->object.sha1));
+       strcpy(symmetric + 40, "...");
+       strcpy(symmetric + 43, sha1_to_hex(theirs->object.sha1));
+
+       init_revisions(&revs, NULL);
+       setup_revisions(rev_argc, rev_argv, &revs, NULL);
+       prepare_revision_walk(&revs);
+
+       /* ... and count the commits on each side. */
+       num_ours = 0;
+       num_theirs = 0;
+       while (1) {
+               struct commit *c = get_revision(&revs);
+               if (!c)
+                       break;
+               if (c->object.flags & SYMMETRIC_LEFT)
+                       num_ours++;
+               else
+                       num_theirs++;
+       }
+
+       if (!prefixcmp(base, "refs/remotes/")) {
+               remote_msg = " remote";
+               base += strlen("refs/remotes/");
+       } else {
+               remote_msg = "";
+       }
+
+       if (!num_theirs)
+               printf("Your branch is ahead of the tracked%s branch '%s' "
+                      "by %d commit%s.\n",
+                      remote_msg, base,
+                      num_ours, (num_ours == 1) ? "" : "s");
+       else if (!num_ours)
+               printf("Your branch is behind the tracked%s branch '%s' "
+                      "by %d commit%s,\n"
+                      "and can be fast-forwarded.\n",
+                      remote_msg, base,
+                      num_theirs, (num_theirs == 1) ? "" : "s");
+       else
+               printf("Your branch and the tracked%s branch '%s' "
+                      "have diverged,\nand respectively "
+                      "have %d and %d different commit(s) each.\n",
+                      remote_msg, base,
+                      num_ours, num_theirs);
+}
+
+static void update_refs_for_switch(struct checkout_opts *opts,
+                                  struct branch_info *old,
+                                  struct branch_info *new)
+{
+       struct strbuf msg;
+       const char *old_desc;
+       if (opts->new_branch) {
+               create_branch(old->name, opts->new_branch, new->name, 0,
+                             opts->new_branch_log, opts->track);
+               new->name = opts->new_branch;
+               setup_branch_path(new);
+       }
+
+       strbuf_init(&msg, 0);
+       old_desc = old->name;
+       if (!old_desc)
+               old_desc = sha1_to_hex(old->commit->object.sha1);
+       strbuf_addf(&msg, "checkout: moving from %s to %s",
+                   old_desc, new->name);
+
+       if (new->path) {
+               create_symref("HEAD", new->path, msg.buf);
+               if (!opts->quiet) {
+                       if (old->path && !strcmp(new->path, old->path))
+                               fprintf(stderr, "Already on \"%s\"\n",
+                                       new->name);
+                       else
+                               fprintf(stderr, "Switched to%s branch \"%s\"\n",
+                                       opts->new_branch ? " a new" : "",
+                                       new->name);
+               }
+       } else if (strcmp(new->name, "HEAD")) {
+               update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
+                          REF_NODEREF, DIE_ON_ERR);
+               if (!opts->quiet) {
+                       if (old->path)
+                               fprintf(stderr, "Note: moving to \"%s\" which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n  git checkout -b <new_branch_name>\n", new->name);
+                       describe_detached_head("HEAD is now at", new->commit);
+               }
+       }
+       remove_branch_state();
+       strbuf_release(&msg);
+       if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
+               report_tracking(new, opts);
+}
+
+static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
+{
+       int ret = 0;
+       struct branch_info old;
+       unsigned char rev[20];
+       int flag;
+       memset(&old, 0, sizeof(old));
+       old.path = resolve_ref("HEAD", rev, 0, &flag);
+       old.commit = lookup_commit_reference_gently(rev, 1);
+       if (!(flag & REF_ISSYMREF))
+               old.path = NULL;
+
+       if (old.path && !prefixcmp(old.path, "refs/heads/"))
+               old.name = old.path + strlen("refs/heads/");
+
+       if (!new->name) {
+               new->name = "HEAD";
+               new->commit = old.commit;
+               if (!new->commit)
+                       die("You are on a branch yet to be born");
+               parse_commit(new->commit);
+       }
+
+       /*
+        * If the new thing isn't a branch and isn't HEAD and we're
+        * not starting a new branch, and we want messages, and we
+        * weren't on a branch, and we're moving to a new commit,
+        * describe the old commit.
+        */
+       if (!new->path && strcmp(new->name, "HEAD") && !opts->new_branch &&
+           !opts->quiet && !old.path && new->commit != old.commit)
+               describe_detached_head("Previous HEAD position was", old.commit);
+
+       if (!old.commit) {
+               if (!opts->quiet) {
+                       fprintf(stderr, "warning: You appear to be on a branch yet to be born.\n");
+                       fprintf(stderr, "warning: Forcing checkout of %s.\n", new->name);
+               }
+               opts->force = 1;
+       }
+
+       ret = merge_working_tree(opts, &old, new);
+       if (ret)
+               return ret;
+
+       update_refs_for_switch(opts, &old, new);
+
+       return post_checkout_hook(old.commit, new->commit, 1);
+}
+
+static int git_checkout_config(const char *var, const char *value)
+{
+       return git_default_config(var, value);
+}
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+       struct checkout_opts opts;
+       unsigned char rev[20];
+       const char *arg;
+       struct branch_info new;
+       struct tree *source_tree = 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( 0 , "track",  &opts.track, "track",
+                       BRANCH_TRACK_EXPLICIT),
+               OPT_BOOLEAN('f', NULL, &opts.force, "force"),
+               OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
+               OPT_END(),
+       };
+
+       memset(&opts, 0, sizeof(opts));
+       memset(&new, 0, sizeof(new));
+
+       git_config(git_checkout_config);
+
+       opts.track = git_branch_track;
+
+       argc = parse_options(argc, argv, options, checkout_usage, 0);
+       if (argc) {
+               arg = argv[0];
+               if (get_sha1(arg, rev))
+                       ;
+               else if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
+                       new.name = arg;
+                       setup_branch_path(&new);
+                       if (resolve_ref(new.path, rev, 1, NULL))
+                               new.commit = lookup_commit_reference(rev);
+                       else
+                               new.path = NULL;
+                       parse_commit(new.commit);
+                       source_tree = new.commit->tree;
+                       argv++;
+                       argc--;
+               } else if ((source_tree = parse_tree_indirect(rev))) {
+                       argv++;
+                       argc--;
+               }
+       }
+
+       if (argc && !strcmp(argv[0], "--")) {
+               argv++;
+               argc--;
+       }
+
+       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");
+
+       if (argc) {
+               const char **pathspec = get_pathspec(prefix, argv);
+               /* Checkout paths */
+               if (opts.new_branch || opts.force || opts.merge) {
+                       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]);
+                       } else {
+                               die("git checkout: updating paths is incompatible with switching branches/forcing");
+                       }
+               }
+
+               if (source_tree)
+                       read_tree_some(source_tree, pathspec);
+               else
+                       read_cache();
+               return checkout_paths(pathspec);
+       }
+
+       if (new.name && !new.commit) {
+               die("Cannot switch branch to a non-commit.");
+       }
+
+       return switch_branches(&opts, &new);
+}
index 065e1f7b7fbf426210d08062800ed81448a30d4a..f49c22e64255225e492614bb628c1d1776521424 100644 (file)
@@ -205,7 +205,8 @@ static void create_base_index(void)
                die("failed to unpack HEAD tree object");
        parse_tree(tree);
        init_tree_desc(&t, tree->buffer, tree->size);
-       unpack_trees(1, &t, &opts);
+       if (unpack_trees(1, &t, &opts))
+               exit(128); /* We've already reported the error, finish dying */
 }
 
 static char *prepare_index(int argc, const char **argv, const char *prefix)
index 3428483134156f4e1761aa47ed2e8098a294808c..05e309f5ad15f9a6f85df34322bb59152b4e37be 100644 (file)
@@ -46,19 +46,34 @@ static void add_to_known_names(const char *path,
 
 static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
-       struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+       int might_be_tag = !prefixcmp(path, "refs/tags/");
+       struct commit *commit;
        struct object *object;
-       int prio;
+       unsigned char peeled[20];
+       int is_tag, prio;
 
-       if (!commit)
+       if (!all && !might_be_tag)
                return 0;
-       object = parse_object(sha1);
+
+       if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) {
+               commit = lookup_commit_reference_gently(peeled, 1);
+               if (!commit)
+                       return 0;
+               is_tag = !!hashcmp(sha1, commit->object.sha1);
+       } else {
+               commit = lookup_commit_reference_gently(sha1, 1);
+               object = parse_object(sha1);
+               if (!commit || !object)
+                       return 0;
+               is_tag = object->type == OBJ_TAG;
+       }
+
        /* If --all, then any refs are used.
         * If --tags, then any tags are used.
         * Otherwise only annotated tags are used.
         */
-       if (!prefixcmp(path, "refs/tags/")) {
-               if (object->type == OBJ_TAG) {
+       if (might_be_tag) {
+               if (is_tag) {
                        prio = 2;
                        if (pattern && fnmatch(pattern, path + 10, 0))
                                prio = 0;
@@ -159,6 +174,8 @@ static void describe(const char *arg, int last_one)
                return;
        }
 
+       if (!max_candidates)
+               die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1));
        if (debug)
                fprintf(stderr, "searching to describe %s\n", arg);
 
@@ -255,6 +272,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                OPT_BOOLEAN(0, "all",        &all, "use any ref in .git/refs"),
                OPT_BOOLEAN(0, "tags",       &tags, "use any tag in .git/refs/tags"),
                OPT__ABBREV(&abbrev),
+               OPT_SET_INT(0, "exact-match", &max_candidates,
+                           "only output exact matches", 0),
                OPT_INTEGER(0, "candidates", &max_candidates,
                            "consider <n> most recent tags (default: 10)"),
                OPT_STRING(0, "match",       &pattern, "pattern",
@@ -263,8 +282,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
        };
 
        argc = parse_options(argc, argv, options, describe_usage, 0);
-       if (max_candidates < 1)
-               max_candidates = 1;
+       if (max_candidates < 0)
+               max_candidates = 0;
        else if (max_candidates > MAX_TAGS)
                max_candidates = MAX_TAGS;
 
index 8f53f52dcbe74c1fdb6b7a54a8b250537bc821ee..444ff2fd92da38ab4002af6a1882839c1c3d930a 100644 (file)
@@ -44,12 +44,17 @@ static void stuff_change(struct diff_options *opt,
                tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u;
                tmp_c = old_name; old_name = new_name; new_name = tmp_c;
        }
+
+       if (opt->prefix &&
+           (strncmp(old_name, opt->prefix, opt->prefix_length) ||
+            strncmp(new_name, opt->prefix, opt->prefix_length)))
+               return;
+
        one = alloc_filespec(old_name);
        two = alloc_filespec(new_name);
        fill_filespec(one, old_sha1, old_mode);
        fill_filespec(two, new_sha1, new_mode);
 
-       /* NEEDSWORK: shouldn't this part of diffopt??? */
        diff_queue(&diff_queued_diff, one, two);
 }
 
@@ -246,6 +251,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                if (diff_setup_done(&rev.diffopt) < 0)
                        die("diff_setup_done failed");
        }
+       if (rev.diffopt.prefix && nongit) {
+               rev.diffopt.prefix = NULL;
+               rev.diffopt.prefix_length = 0;
+       }
        DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
        DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
 
index 49b54de0541b4537ac8da51018fa7f5a7276b71e..e1c56303e5cb7882bc79980c4f8e6c6791ad323b 100755 (executable)
@@ -123,7 +123,7 @@ static void show_filemodify(struct diff_queue_struct *q,
                        printf("D %s\n", spec->path);
                else {
                        struct object *object = lookup_object(spec->sha1);
-                       printf("M 0%06o :%d %s\n", spec->mode,
+                       printf("M %06o :%d %s\n", spec->mode,
                               get_object_mark(object), spec->path);
                }
        }
index f40135248a5a1e2012c4cb01667f037407aae800..5ea48ca7dbc22785f4e4f3c35f3dc48ee18f0d8a 100644 (file)
@@ -538,8 +538,10 @@ static int get_pack(int xd[2], char **pack_lockfile)
        cmd.git_cmd = 1;
        if (start_command(&cmd))
                die("fetch-pack: unable to fork off %s", argv[0]);
-       if (do_keep && pack_lockfile)
+       if (do_keep && pack_lockfile) {
                *pack_lockfile = index_pack_lockfile(cmd.out);
+               close(cmd.out);
+       }
 
        if (finish_command(&cmd))
                die("%s failed", argv[0]);
index f36a43c26459bd386618e551e2e93743cd8030aa..07d9c572125523e2eb8f82e4cab907ee7dc94348 100644 (file)
@@ -165,7 +165,7 @@ static int verify_format(const char *format)
        for (cp = format; *cp && (sp = find_next(cp)); ) {
                const char *ep = strchr(sp, ')');
                if (!ep)
-                       return error("malformatted format string %s", sp);
+                       return error("malformed format string %s", sp);
                /* sp points at "%(" and ep points at the closing ")" */
                parse_atom(sp + 2, ep);
                cp = ep + 1;
index 5d7cdda93314b1d40f5f512897e8a35af0480a8f..79eaf8d6edf18675897f6eed571289fce450526a 100644 (file)
@@ -29,27 +29,6 @@ static void safe_create_dir(const char *dir, int share)
                die("Could not make %s writable by group\n", dir);
 }
 
-static int copy_file(const char *dst, const char *src, int mode)
-{
-       int fdi, fdo, status;
-
-       mode = (mode & 0111) ? 0777 : 0666;
-       if ((fdi = open(src, O_RDONLY)) < 0)
-               return fdi;
-       if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) {
-               close(fdi);
-               return fdo;
-       }
-       status = copy_fd(fdi, fdo);
-       if (close(fdo) != 0)
-               return error("%s: write error: %s", dst, strerror(errno));
-
-       if (!status && adjust_shared_perm(dst))
-               return -1;
-
-       return status;
-}
-
 static void copy_templates_1(char *path, int baselen,
                             char *template, int template_baselen,
                             DIR *dir)
index c67d63cb1cf588877fa10a7546757f2cd249c5c6..3209ea5c6d61d18dfe885f07b20d11119799b803 100644 (file)
@@ -15,6 +15,8 @@
 #include "reflog-walk.h"
 #include "patch-ids.h"
 #include "refs.h"
+#include "run-command.h"
+#include "shortlog.h"
 
 static int default_show_root = 1;
 static const char *fmt_patch_subject_prefix = "PATCH";
@@ -428,24 +430,47 @@ static int istitlechar(char c)
                (c >= '0' && c <= '9') || c == '.' || c == '_';
 }
 
-static char *extra_headers = NULL;
-static int extra_headers_size = 0;
 static const char *fmt_patch_suffix = ".patch";
 static int numbered = 0;
 static int auto_number = 0;
 
+static char **extra_hdr;
+static int extra_hdr_nr;
+static int extra_hdr_alloc;
+
+static char **extra_to;
+static int extra_to_nr;
+static int extra_to_alloc;
+
+static char **extra_cc;
+static int extra_cc_nr;
+static int extra_cc_alloc;
+
+static void add_header(const char *value)
+{
+       int len = strlen(value);
+       while (value[len - 1] == '\n')
+               len--;
+       if (!strncasecmp(value, "to: ", 4)) {
+               ALLOC_GROW(extra_to, extra_to_nr + 1, extra_to_alloc);
+               extra_to[extra_to_nr++] = xstrndup(value + 4, len - 4);
+               return;
+       }
+       if (!strncasecmp(value, "cc: ", 4)) {
+               ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
+               extra_cc[extra_cc_nr++] = xstrndup(value + 4, len - 4);
+               return;
+       }
+       ALLOC_GROW(extra_hdr, extra_hdr_nr + 1, extra_hdr_alloc);
+       extra_hdr[extra_hdr_nr++] = xstrndup(value, len);
+}
+
 static int git_format_config(const char *var, const char *value)
 {
        if (!strcmp(var, "format.headers")) {
-               int len;
-
                if (!value)
                        die("format.headers without value");
-               len = strlen(value);
-               extra_headers_size += len + 1;
-               extra_headers = xrealloc(extra_headers, extra_headers_size);
-               extra_headers[extra_headers_size - len - 1] = 0;
-               strcat(extra_headers, value);
+               add_header(value);
                return 0;
        }
        if (!strcmp(var, "format.suffix")) {
@@ -470,74 +495,81 @@ static int git_format_config(const char *var, const char *value)
 }
 
 
+static const char *get_oneline_for_filename(struct commit *commit,
+                                           int keep_subject)
+{
+       static char filename[PATH_MAX];
+       char *sol;
+       int len = 0;
+       int suffix_len = strlen(fmt_patch_suffix) + 1;
+
+       sol = strstr(commit->buffer, "\n\n");
+       if (!sol)
+               filename[0] = '\0';
+       else {
+               int j, space = 0;
+
+               sol += 2;
+               /* strip [PATCH] or [PATCH blabla] */
+               if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
+                       char *eos = strchr(sol + 6, ']');
+                       if (eos) {
+                               while (isspace(*eos))
+                                       eos++;
+                               sol = eos;
+                       }
+               }
+
+               for (j = 0;
+                    j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 &&
+                            len < sizeof(filename) - suffix_len &&
+                            sol[j] && sol[j] != '\n';
+                    j++) {
+                       if (istitlechar(sol[j])) {
+                               if (space) {
+                                       filename[len++] = '-';
+                                       space = 0;
+                               }
+                               filename[len++] = sol[j];
+                               if (sol[j] == '.')
+                                       while (sol[j + 1] == '.')
+                                               j++;
+                       } else
+                               space = 1;
+               }
+               while (filename[len - 1] == '.'
+                      || filename[len - 1] == '-')
+                       len--;
+               filename[len] = '\0';
+       }
+       return filename;
+}
+
 static FILE *realstdout = NULL;
 static const char *output_directory = NULL;
 
-static int reopen_stdout(struct commit *commit, int nr, int keep_subject,
-                        int numbered_files)
+static int reopen_stdout(const char *oneline, int nr, int total)
 {
        char filename[PATH_MAX];
-       char *sol;
        int len = 0;
        int suffix_len = strlen(fmt_patch_suffix) + 1;
 
        if (output_directory) {
-               if (strlen(output_directory) >=
+               len = snprintf(filename, sizeof(filename), "%s",
+                               output_directory);
+               if (len >=
                    sizeof(filename) - FORMAT_PATCH_NAME_MAX - suffix_len)
                        return error("name of output directory is too long");
-               strlcpy(filename, output_directory, sizeof(filename) - suffix_len);
-               len = strlen(filename);
                if (filename[len - 1] != '/')
                        filename[len++] = '/';
        }
 
-       if (numbered_files) {
-               sprintf(filename + len, "%d", nr);
-               len = strlen(filename);
-
-       } else {
-               sprintf(filename + len, "%04d", nr);
-               len = strlen(filename);
-
-               sol = strstr(commit->buffer, "\n\n");
-               if (sol) {
-                       int j, space = 1;
-
-                       sol += 2;
-                       /* strip [PATCH] or [PATCH blabla] */
-                       if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
-                               char *eos = strchr(sol + 6, ']');
-                               if (eos) {
-                                       while (isspace(*eos))
-                                               eos++;
-                                       sol = eos;
-                               }
-                       }
-
-                       for (j = 0;
-                            j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 &&
-                                    len < sizeof(filename) - suffix_len &&
-                                    sol[j] && sol[j] != '\n';
-                            j++) {
-                               if (istitlechar(sol[j])) {
-                                       if (space) {
-                                               filename[len++] = '-';
-                                               space = 0;
-                                       }
-                                       filename[len++] = sol[j];
-                                       if (sol[j] == '.')
-                                               while (sol[j + 1] == '.')
-                                                       j++;
-                               } else
-                                       space = 1;
-                       }
-                       while (filename[len - 1] == '.'
-                              || filename[len - 1] == '-')
-                               len--;
-                       filename[len] = 0;
-               }
-               if (len + suffix_len >= sizeof(filename))
-                       return error("Patch pathname too long");
+       if (!oneline)
+               len += sprintf(filename + len, "%d", nr);
+       else {
+               len += sprintf(filename + len, "%04d-", nr);
+               len += snprintf(filename + len, sizeof(filename) - len - 1
+                               - suffix_len, "%s", oneline);
                strcpy(filename + len, fmt_patch_suffix);
        }
 
@@ -594,16 +626,89 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
        o2->flags = flags2;
 }
 
-static void gen_message_id(char *dest, unsigned int length, char *base)
+static void gen_message_id(struct rev_info *info, char *base)
 {
        const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
        const char *email_start = strrchr(committer, '<');
        const char *email_end = strrchr(committer, '>');
-       if(!email_start || !email_end || email_start > email_end - 1)
+       struct strbuf buf;
+       if (!email_start || !email_end || email_start > email_end - 1)
                die("Could not extract email from committer identity.");
-       snprintf(dest, length, "%s.%lu.git.%.*s", base,
-                (unsigned long) time(NULL),
-                (int)(email_end - email_start - 1), email_start + 1);
+       strbuf_init(&buf, 0);
+       strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
+                   (unsigned long) time(NULL),
+                   (int)(email_end - email_start - 1), email_start + 1);
+       info->message_id = strbuf_detach(&buf, NULL);
+}
+
+static void make_cover_letter(struct rev_info *rev, int use_stdout,
+                             int numbered, int numbered_files,
+                             struct commit *origin,
+                             int nr, struct commit **list, struct commit *head)
+{
+       const char *committer;
+       const char *origin_sha1, *head_sha1;
+       const char *argv[7];
+       const char *subject_start = NULL;
+       const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
+       const char *msg;
+       const char *extra_headers = rev->extra_headers;
+       struct shortlog log;
+       struct strbuf sb;
+       int i;
+       const char *encoding = "utf-8";
+
+       if (rev->commit_format != CMIT_FMT_EMAIL)
+               die("Cover letter needs email format");
+
+       if (!use_stdout && reopen_stdout(numbered_files ?
+                               NULL : "cover-letter", 0, rev->total))
+               return;
+
+       head_sha1 = sha1_to_hex(head->object.sha1);
+
+       log_write_email_headers(rev, head_sha1, &subject_start, &extra_headers);
+
+       committer = git_committer_info(0);
+
+       msg = body;
+       strbuf_init(&sb, 0);
+       pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
+                    encoding);
+       pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers,
+                     encoding, 0);
+       pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0);
+       printf("%s\n", sb.buf);
+
+       strbuf_release(&sb);
+
+       shortlog_init(&log);
+       for (i = 0; i < nr; i++)
+               shortlog_add_commit(&log, list[i]);
+
+       shortlog_output(&log);
+
+       /*
+        * We can only do diffstat with a unique reference point
+        */
+       if (!origin)
+               return;
+
+       origin_sha1 = sha1_to_hex(origin->object.sha1);
+
+       argv[0] = "diff";
+       argv[1] = "--stat";
+       argv[2] = "--summary";
+       argv[3] = head_sha1;
+       argv[4] = "--not";
+       argv[5] = origin_sha1;
+       argv[6] = "--";
+       argv[7] = NULL;
+       fflush(stdout);
+       run_command_v_opt(argv, RUN_GIT_CMD);
+
+       fflush(stdout);
+       printf("\n");
 }
 
 static const char *clean_message_id(const char *msg_id)
@@ -641,11 +746,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        int subject_prefix = 0;
        int ignore_if_in_upstream = 0;
        int thread = 0;
+       int cover_letter = 0;
+       int boundary_count = 0;
+       struct commit *origin = NULL, *head = NULL;
        const char *in_reply_to = NULL;
        struct patch_ids ids;
        char *add_signoff = NULL;
-       char message_id[1024];
-       char ref_message_id[1024];
+       struct strbuf buf;
 
        git_config(git_format_config);
        init_revisions(&rev, prefix);
@@ -658,7 +765,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
 
        rev.subject_prefix = fmt_patch_subject_prefix;
-       rev.extra_headers = extra_headers;
 
        /*
         * Parse the arguments before setup_revisions(), or something
@@ -686,6 +792,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                                die("Need a number for --start-number");
                        start_number = strtol(argv[i], NULL, 10);
                }
+               else if (!prefixcmp(argv[i], "--cc=")) {
+                       ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
+                       extra_cc[extra_cc_nr++] = xstrdup(argv[i] + 5);
+               }
                else if (!strcmp(argv[i], "-k") ||
                                !strcmp(argv[i], "--keep-subject")) {
                        keep_subject = 1;
@@ -742,11 +852,44 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                        rev.subject_prefix = argv[i] + 17;
                } else if (!prefixcmp(argv[i], "--suffix="))
                        fmt_patch_suffix = argv[i] + 9;
+               else if (!strcmp(argv[i], "--cover-letter"))
+                       cover_letter = 1;
                else
                        argv[j++] = argv[i];
        }
        argc = j;
 
+       strbuf_init(&buf, 0);
+
+       for (i = 0; i < extra_hdr_nr; i++) {
+               strbuf_addstr(&buf, extra_hdr[i]);
+               strbuf_addch(&buf, '\n');
+       }
+
+       if (extra_to_nr)
+               strbuf_addstr(&buf, "To: ");
+       for (i = 0; i < extra_to_nr; i++) {
+               if (i)
+                       strbuf_addstr(&buf, "    ");
+               strbuf_addstr(&buf, extra_to[i]);
+               if (i + 1 < extra_to_nr)
+                       strbuf_addch(&buf, ',');
+               strbuf_addch(&buf, '\n');
+       }
+
+       if (extra_cc_nr)
+               strbuf_addstr(&buf, "Cc: ");
+       for (i = 0; i < extra_cc_nr; i++) {
+               if (i)
+                       strbuf_addstr(&buf, "    ");
+               strbuf_addstr(&buf, extra_cc[i]);
+               if (i + 1 < extra_cc_nr)
+                       strbuf_addch(&buf, ',');
+               strbuf_addch(&buf, '\n');
+       }
+
+       rev.extra_headers = strbuf_detach(&buf, 0);
+
        if (start_number < 0)
                start_number = 1;
        if (numbered && keep_subject)
@@ -793,6 +936,18 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                 * get_revision() to do the usual traversal.
                 */
        }
+       if (cover_letter) {
+               /* remember the range */
+               int i;
+               for (i = 0; i < rev.pending.nr; i++) {
+                       struct object *o = rev.pending.objects[i].item;
+                       if (!(o->flags & UNINTERESTING))
+                               head = (struct commit *)o;
+               }
+               /* We can't generate a cover letter without any patches */
+               if (!head)
+                       return 0;
+       }
 
        if (ignore_if_in_upstream)
                get_patch_ids(&rev, &ids, prefix);
@@ -802,7 +957,15 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 
        if (prepare_revision_walk(&rev))
                die("revision walk setup failed");
+       rev.boundary = 1;
        while ((commit = get_revision(&rev)) != NULL) {
+               if (commit->object.flags & BOUNDARY) {
+                       fprintf(stderr, "Boundary %s\n", sha1_to_hex(commit->object.sha1));
+                       boundary_count++;
+                       origin = (boundary_count == 1) ? commit : NULL;
+                       continue;
+               }
+
                /* ignore merges */
                if (commit->parents && commit->parents->next)
                        continue;
@@ -820,29 +983,42 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                numbered = 1;
        if (numbered)
                rev.total = total + start_number - 1;
-       rev.add_signoff = add_signoff;
        if (in_reply_to)
                rev.ref_message_id = clean_message_id(in_reply_to);
+       if (cover_letter) {
+               if (thread)
+                       gen_message_id(&rev, "cover");
+               make_cover_letter(&rev, use_stdout, numbered, numbered_files,
+                                 origin, nr, list, head);
+               total++;
+               start_number--;
+       }
+       rev.add_signoff = add_signoff;
        while (0 <= --nr) {
                int shown;
                commit = list[nr];
                rev.nr = total - nr + (start_number - 1);
                /* Make the second and subsequent mails replies to the first */
                if (thread) {
-                       if (nr == (total - 2)) {
-                               strncpy(ref_message_id, message_id,
-                                       sizeof(ref_message_id));
-                               ref_message_id[sizeof(ref_message_id)-1]='\0';
-                               rev.ref_message_id = ref_message_id;
+                       /* Have we already had a message ID? */
+                       if (rev.message_id) {
+                               /*
+                                * If we've got the ID to be a reply
+                                * to, discard the current ID;
+                                * otherwise, make everything a reply
+                                * to that.
+                                */
+                               if (rev.ref_message_id)
+                                       free(rev.message_id);
+                               else
+                                       rev.ref_message_id = rev.message_id;
                        }
-                       gen_message_id(message_id, sizeof(message_id),
-                                      sha1_to_hex(commit->object.sha1));
-                       rev.message_id = message_id;
+                       gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
                }
-               if (!use_stdout)
-                       if (reopen_stdout(commit, rev.nr, keep_subject,
-                                         numbered_files))
-                               die("Failed to create output files");
+               if (!use_stdout && reopen_stdout(numbered_files ? NULL :
+                               get_oneline_for_filename(commit, keep_subject),
+                               rev.nr, rev.total))
+                       die("Failed to create output files");
                shown = log_tree_commit(&rev, commit);
                free(commit->buffer);
                commit->buffer = NULL;
index 58deb62ac08507901c40e89aec0cea7fcdc78f1e..adce6d4635a4153428368073677cd74a9bafc045 100644 (file)
@@ -46,7 +46,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, &result);
+                       &xpp, XDL_MERGE_ZEALOUS_ALNUM, &result);
 
        for (i = 0; i < 3; i++)
                free(mmfs[i].ptr);
diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c
new file mode 100644 (file)
index 0000000..6fe4102
--- /dev/null
@@ -0,0 +1,1754 @@
+/*
+ * 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 "run-command.h"
+#include "tag.h"
+#include "unpack-trees.h"
+#include "path-list.h"
+#include "xdiff-interface.h"
+#include "interpolate.h"
+#include "attr.h"
+#include "merge-recursive.h"
+
+static int subtree_merge;
+
+static struct tree *shift_tree_object(struct tree *one, struct tree *two)
+{
+       unsigned char shifted[20];
+
+       /*
+        * NEEDSWORK: this limits the recursion depth to hardcoded
+        * value '2' to avoid excessive overhead.
+        */
+       shift_tree(one->object.sha1, two->object.sha1, shifted, 2);
+       if (!hashcmp(two->object.sha1, shifted))
+               return two;
+       return lookup_tree(shifted);
+}
+
+/*
+ * A virtual commit has
+ * - (const char *)commit->util set to the name, and
+ * - *(int *)commit->object.sha1 set to the virtual id.
+ */
+
+static unsigned commit_list_count(const struct commit_list *l)
+{
+       unsigned c = 0;
+       for (; l; l = l->next )
+               c++;
+       return c;
+}
+
+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 path_list current_file_set = {NULL, 0, 0, 1};
+static struct path_list current_directory_set = {NULL, 0, 0, 1};
+
+static int call_depth = 0;
+static int verbosity = 2;
+static int 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;
+
+       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)
+{
+       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))
+               path_list_insert(newpath, &current_directory_set);
+       else
+               path_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) != 0)
+               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 path_list *entries)
+{
+       struct path_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 = path_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 path_list *get_unmerged(void)
+{
+       struct path_list *unmerged = xcalloc(1, sizeof(struct path_list));
+       int i;
+
+       unmerged->strdup_paths = 1;
+
+       for (i = 0; i < active_nr; i++) {
+               struct path_list_item *item;
+               struct stage_data *e;
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+
+               item = path_list_lookup(ce->name, unmerged);
+               if (!item) {
+                       item = path_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 path_list *get_renames(struct tree *tree,
+                                       struct tree *o_tree,
+                                       struct tree *a_tree,
+                                       struct tree *b_tree,
+                                       struct path_list *entries)
+{
+       int i;
+       struct path_list *renames;
+       struct diff_options opts;
+
+       renames = xcalloc(1, sizeof(struct path_list));
+       diff_setup(&opts);
+       DIFF_OPT_SET(&opts, RECURSIVE);
+       opts.detect_rename = DIFF_DETECT_RENAME;
+       opts.rename_limit = rename_limit;
+       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 path_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 = path_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 = path_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 = path_list_insert(pair->one->path, renames);
+               item->util = re;
+       }
+       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       diff_queued_diff.nr = 0;
+       diff_flush(&opts);
+       return renames;
+}
+
+static int update_stages(const char *path, struct diff_filespec *o,
+                        struct diff_filespec *a, struct diff_filespec *b,
+                        int clear)
+{
+       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
+       if (clear)
+               if (remove_file_from_cache(path))
+                       return -1;
+       if (o)
+               if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))
+                       return -1;
+       if (a)
+               if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options))
+                       return -1;
+       if (b)
+               if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options))
+                       return -1;
+       return 0;
+}
+
+static int remove_path(const char *name)
+{
+       int ret;
+       char *slash, *dirs;
+
+       ret = unlink(name);
+       if (ret)
+               return ret;
+       dirs = xstrdup(name);
+       while ((slash = strrchr(name, '/'))) {
+               *slash = '\0';
+               if (rmdir(name) != 0)
+                       break;
+       }
+       free(dirs);
+       return ret;
+}
+
+static int remove_file(int clean, const char *path, int no_wd)
+{
+       int update_cache = index_only || clean;
+       int update_working_directory = !index_only && !no_wd;
+
+       if (update_cache) {
+               if (remove_file_from_cache(path))
+                       return -1;
+       }
+       if (update_working_directory) {
+               unlink(path);
+               if (errno != ENOENT || errno != EISDIR)
+                       return -1;
+               remove_path(path);
+       }
+       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 (path_list_has_path(&current_file_set, newpath) ||
+              path_list_has_path(&current_directory_set, newpath) ||
+              lstat(newpath, &st) == 0)
+               sprintf(p, "_%d", suffix++);
+
+       path_list_insert(newpath, &current_file_set);
+       return newpath;
+}
+
+static int mkdir_p(const char *path, unsigned long mode)
+{
+       /* path points to cache entries, so xstrdup before messing with it */
+       char *buf = xstrdup(path);
+       int result = safe_create_leading_directories(buf);
+       free(buf);
+       return result;
+}
+
+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 = mkdir_p(path, 0777);
+       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 (make_room_for_path(path) < 0) {
+                       update_wd = 0;
+                       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);
+                       mkdir_p(path, 0777);
+                       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);
+       }
+ 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;
+}
+
+/*
+ * Customizable low-level merge drivers support.
+ */
+
+struct ll_merge_driver;
+typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
+                          const char *path,
+                          mmfile_t *orig,
+                          mmfile_t *src1, const char *name1,
+                          mmfile_t *src2, const char *name2,
+                          mmbuffer_t *result);
+
+struct ll_merge_driver {
+       const char *name;
+       const char *description;
+       ll_merge_fn fn;
+       const char *recursive;
+       struct ll_merge_driver *next;
+       char *cmdline;
+};
+
+/*
+ * Built-in low-levels
+ */
+static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
+                          const char *path_unused,
+                          mmfile_t *orig,
+                          mmfile_t *src1, const char *name1,
+                          mmfile_t *src2, const char *name2,
+                          mmbuffer_t *result)
+{
+       /*
+        * The tentative merge result is "ours" for the final round,
+        * or common ancestor for an internal merge.  Still return
+        * "conflicted merge" status.
+        */
+       mmfile_t *stolen = index_only ? orig : src1;
+
+       result->ptr = stolen->ptr;
+       result->size = stolen->size;
+       stolen->ptr = NULL;
+       return 1;
+}
+
+static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
+                       const char *path_unused,
+                       mmfile_t *orig,
+                       mmfile_t *src1, const char *name1,
+                       mmfile_t *src2, const char *name2,
+                       mmbuffer_t *result)
+{
+       xpparam_t xpp;
+
+       if (buffer_is_binary(orig->ptr, orig->size) ||
+           buffer_is_binary(src1->ptr, src1->size) ||
+           buffer_is_binary(src2->ptr, src2->size)) {
+               warning("Cannot merge binary files: %s vs. %s\n",
+                       name1, name2);
+               return ll_binary_merge(drv_unused, path_unused,
+                                      orig, src1, name1,
+                                      src2, name2,
+                                      result);
+       }
+
+       memset(&xpp, 0, sizeof(xpp));
+       return xdl_merge(orig,
+                        src1, name1,
+                        src2, name2,
+                        &xpp, XDL_MERGE_ZEALOUS,
+                        result);
+}
+
+static int ll_union_merge(const struct ll_merge_driver *drv_unused,
+                         const char *path_unused,
+                         mmfile_t *orig,
+                         mmfile_t *src1, const char *name1,
+                         mmfile_t *src2, const char *name2,
+                         mmbuffer_t *result)
+{
+       char *src, *dst;
+       long size;
+       const int marker_size = 7;
+
+       int status = ll_xdl_merge(drv_unused, path_unused,
+                                 orig, src1, NULL, src2, NULL, result);
+       if (status <= 0)
+               return status;
+       size = result->size;
+       src = dst = result->ptr;
+       while (size) {
+               char ch;
+               if ((marker_size < size) &&
+                   (*src == '<' || *src == '=' || *src == '>')) {
+                       int i;
+                       ch = *src;
+                       for (i = 0; i < marker_size; i++)
+                               if (src[i] != ch)
+                                       goto not_a_marker;
+                       if (src[marker_size] != '\n')
+                               goto not_a_marker;
+                       src += marker_size + 1;
+                       size -= marker_size + 1;
+                       continue;
+               }
+       not_a_marker:
+               do {
+                       ch = *src++;
+                       *dst++ = ch;
+                       size--;
+               } while (ch != '\n' && size);
+       }
+       result->size = dst - result->ptr;
+       return 0;
+}
+
+#define LL_BINARY_MERGE 0
+#define LL_TEXT_MERGE 1
+#define LL_UNION_MERGE 2
+static struct ll_merge_driver ll_merge_drv[] = {
+       { "binary", "built-in binary merge", ll_binary_merge },
+       { "text", "built-in 3-way text merge", ll_xdl_merge },
+       { "union", "built-in union merge", ll_union_merge },
+};
+
+static void create_temp(mmfile_t *src, char *path)
+{
+       int fd;
+
+       strcpy(path, ".merge_file_XXXXXX");
+       fd = xmkstemp(path);
+       if (write_in_full(fd, src->ptr, src->size) != src->size)
+               die("unable to write temp-file");
+       close(fd);
+}
+
+/*
+ * User defined low-level merge driver support.
+ */
+static int ll_ext_merge(const struct ll_merge_driver *fn,
+                       const char *path,
+                       mmfile_t *orig,
+                       mmfile_t *src1, const char *name1,
+                       mmfile_t *src2, const char *name2,
+                       mmbuffer_t *result)
+{
+       char temp[3][50];
+       char cmdbuf[2048];
+       struct interp table[] = {
+               { "%O" },
+               { "%A" },
+               { "%B" },
+       };
+       struct child_process child;
+       const char *args[20];
+       int status, fd, i;
+       struct stat st;
+
+       if (fn->cmdline == NULL)
+               die("custom merge driver %s lacks command line.", fn->name);
+
+       result->ptr = NULL;
+       result->size = 0;
+       create_temp(orig, temp[0]);
+       create_temp(src1, temp[1]);
+       create_temp(src2, temp[2]);
+
+       interp_set_entry(table, 0, temp[0]);
+       interp_set_entry(table, 1, temp[1]);
+       interp_set_entry(table, 2, temp[2]);
+
+       output(1, "merging %s using %s", path,
+              fn->description ? fn->description : fn->name);
+
+       interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3);
+
+       memset(&child, 0, sizeof(child));
+       child.argv = args;
+       args[0] = "sh";
+       args[1] = "-c";
+       args[2] = cmdbuf;
+       args[3] = NULL;
+
+       status = run_command(&child);
+       if (status < -ERR_RUN_COMMAND_FORK)
+               ; /* failure in run-command */
+       else
+               status = -status;
+       fd = open(temp[1], O_RDONLY);
+       if (fd < 0)
+               goto bad;
+       if (fstat(fd, &st))
+               goto close_bad;
+       result->size = st.st_size;
+       result->ptr = xmalloc(result->size + 1);
+       if (read_in_full(fd, result->ptr, result->size) != result->size) {
+               free(result->ptr);
+               result->ptr = NULL;
+               result->size = 0;
+       }
+ close_bad:
+       close(fd);
+ bad:
+       for (i = 0; i < 3; i++)
+               unlink(temp[i]);
+       return status;
+}
+
+/*
+ * merge.default and merge.driver configuration items
+ */
+static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
+static const char *default_ll_merge;
+
+static int read_merge_config(const char *var, const char *value)
+{
+       struct ll_merge_driver *fn;
+       const char *ep, *name;
+       int namelen;
+
+       if (!strcmp(var, "merge.default")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               default_ll_merge = strdup(value);
+               return 0;
+       }
+
+       /*
+        * We are not interested in anything but "merge.<name>.variable";
+        * especially, we do not want to look at variables such as
+        * "merge.summary", "merge.tool", and "merge.verbosity".
+        */
+       if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5)
+               return 0;
+
+       /*
+        * Find existing one as we might be processing merge.<name>.var2
+        * after seeing merge.<name>.var1.
+        */
+       name = var + 6;
+       namelen = ep - name;
+       for (fn = ll_user_merge; fn; fn = fn->next)
+               if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
+                       break;
+       if (!fn) {
+               fn = xcalloc(1, sizeof(struct ll_merge_driver));
+               fn->name = xmemdupz(name, namelen);
+               fn->fn = ll_ext_merge;
+               *ll_user_merge_tail = fn;
+               ll_user_merge_tail = &(fn->next);
+       }
+
+       ep++;
+
+       if (!strcmp("name", ep)) {
+               if (!value)
+                       return config_error_nonbool(var);
+               fn->description = strdup(value);
+               return 0;
+       }
+
+       if (!strcmp("driver", ep)) {
+               if (!value)
+                       return config_error_nonbool(var);
+               /*
+                * merge.<name>.driver specifies the command line:
+                *
+                *      command-line
+                *
+                * The command-line will be interpolated with the following
+                * tokens and is given to the shell:
+                *
+                *    %O - temporary file name for the merge base.
+                *    %A - temporary file name for our version.
+                *    %B - temporary file name for the other branches' version.
+                *
+                * The external merge driver should write the results in the
+                * file named by %A, and signal that it has done with zero exit
+                * status.
+                */
+               fn->cmdline = strdup(value);
+               return 0;
+       }
+
+       if (!strcmp("recursive", ep)) {
+               if (!value)
+                       return config_error_nonbool(var);
+               fn->recursive = strdup(value);
+               return 0;
+       }
+
+       return 0;
+}
+
+static void initialize_ll_merge(void)
+{
+       if (ll_user_merge_tail)
+               return;
+       ll_user_merge_tail = &ll_user_merge;
+       git_config(read_merge_config);
+}
+
+static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)
+{
+       struct ll_merge_driver *fn;
+       const char *name;
+       int i;
+
+       initialize_ll_merge();
+
+       if (ATTR_TRUE(merge_attr))
+               return &ll_merge_drv[LL_TEXT_MERGE];
+       else if (ATTR_FALSE(merge_attr))
+               return &ll_merge_drv[LL_BINARY_MERGE];
+       else if (ATTR_UNSET(merge_attr)) {
+               if (!default_ll_merge)
+                       return &ll_merge_drv[LL_TEXT_MERGE];
+               else
+                       name = default_ll_merge;
+       }
+       else
+               name = merge_attr;
+
+       for (fn = ll_user_merge; fn; fn = fn->next)
+               if (!strcmp(fn->name, name))
+                       return fn;
+
+       for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
+               if (!strcmp(ll_merge_drv[i].name, name))
+                       return &ll_merge_drv[i];
+
+       /* default to the 3-way */
+       return &ll_merge_drv[LL_TEXT_MERGE];
+}
+
+static const char *git_path_check_merge(const char *path)
+{
+       static struct git_attr_check attr_merge_check;
+
+       if (!attr_merge_check.attr)
+               attr_merge_check.attr = git_attr("merge", 5);
+
+       if (git_checkattr(path, 1, &attr_merge_check))
+               return NULL;
+       return attr_merge_check.value;
+}
+
+static int ll_merge(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;
+       const char *ll_driver_name;
+       const struct ll_merge_driver *driver;
+
+       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);
+
+       ll_driver_name = git_path_check_merge(a->path);
+       driver = find_ll_merge_driver(ll_driver_name);
+
+       if (index_only && driver->recursive)
+               driver = find_ll_merge_driver(driver->recursive);
+       merge_status = driver->fn(driver, a->path,
+                                 &orig, &src1, name1, &src2, name2,
+                                 result_buf);
+
+       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;
+
+               result.mode = a->mode == o->mode ? b->mode: a->mode;
+
+               if (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 = ll_merge(&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 (path_list_has_path(&current_directory_set, ren1_dst)) {
+               dst_name1 = del[delp++] = unique_path(ren1_dst, branch1);
+               output(1, "%s is a directory in %s added as %s instead",
+                      ren1_dst, branch2, dst_name1);
+               remove_file(0, ren1_dst, 0);
+       }
+       if (path_list_has_path(&current_directory_set, ren2_dst)) {
+               dst_name2 = del[delp++] = unique_path(ren2_dst, branch2);
+               output(1, "%s is a directory in %s added as %s instead",
+                      ren2_dst, branch1, dst_name2);
+               remove_file(0, ren2_dst, 0);
+       }
+       if (index_only) {
+               remove_file_from_cache(dst_name1);
+               remove_file_from_cache(dst_name2);
+               /*
+                * Uncomment to leave the conflicting names in the resulting tree
+                *
+                * update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1);
+                * update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2);
+                */
+       } else {
+               update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
+               update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
+       }
+       while (delp--)
+               free(del[delp]);
+}
+
+static void conflict_rename_dir(struct rename *ren1,
+                               const char *branch1)
+{
+       char *new_path = unique_path(ren1->pair->two->path, branch1);
+       output(1, "Renamed %s to %s instead", ren1->pair->one->path, new_path);
+       remove_file(0, ren1->pair->two->path, 0);
+       update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
+       free(new_path);
+}
+
+static void conflict_rename_rename_2(struct rename *ren1,
+                                    const char *branch1,
+                                    struct rename *ren2,
+                                    const char *branch2)
+{
+       char *new_path1 = unique_path(ren1->pair->two->path, branch1);
+       char *new_path2 = unique_path(ren2->pair->two->path, branch2);
+       output(1, "Renamed %s to %s and %s to %s instead",
+              ren1->pair->one->path, new_path1,
+              ren2->pair->one->path, new_path2);
+       remove_file(0, ren1->pair->two->path, 0);
+       update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
+       update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
+       free(new_path2);
+       free(new_path1);
+}
+
+static int process_renames(struct path_list *a_renames,
+                          struct path_list *b_renames,
+                          const char *a_branch,
+                          const char *b_branch)
+{
+       int clean_merge = 1, i, j;
+       struct path_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;
+               path_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;
+               path_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 path_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].path,
+                                       b_renames->items[j].path);
+                       if (compare <= 0)
+                               ren1 = a_renames->items[i++].util;
+                       if (compare >= 0)
+                               ren2 = b_renames->items[j++].util;
+               }
+
+               /* TODO: refactor, so that 1/2 are not needed */
+               if (ren1) {
+                       renames1 = a_renames;
+                       renames2 = b_renames;
+                       renames2Dst = &b_by_dst;
+                       branch1 = a_branch;
+                       branch2 = b_branch;
+               } else {
+                       struct rename *tmp;
+                       renames1 = b_renames;
+                       renames2 = a_renames;
+                       renames2Dst = &a_by_dst;
+                       branch1 = b_branch;
+                       branch2 = a_branch;
+                       tmp = ren2;
+                       ren2 = ren1;
+                       ren1 = tmp;
+               }
+               src = ren1->pair->one->path;
+
+               ren1->dst_entry->processed = 1;
+               ren1->src_entry->processed = 1;
+
+               if (ren1->processed)
+                       continue;
+               ren1->processed = 1;
+
+               ren1_src = ren1->pair->one->path;
+               ren1_dst = ren1->pair->two->path;
+
+               if (ren2) {
+                       const char *ren2_src = ren2->pair->one->path;
+                       const char *ren2_dst = ren2->pair->two->path;
+                       /* Renamed in 1 and renamed in 2 */
+                       if (strcmp(ren1_src, ren2_src) != 0)
+                               die("ren1.src != ren2.src");
+                       ren2->dst_entry->processed = 1;
+                       ren2->processed = 1;
+                       if (strcmp(ren1_dst, ren2_dst) != 0) {
+                               clean_merge = 0;
+                               output(1, "CONFLICT (rename/rename): "
+                                      "Rename \"%s\"->\"%s\" in branch \"%s\" "
+                                      "rename \"%s\"->\"%s\" in \"%s\"%s",
+                                      src, ren1_dst, branch1,
+                                      src, ren2_dst, branch2,
+                                      index_only ? " (left unresolved)": "");
+                               if (index_only) {
+                                       remove_file_from_cache(src);
+                                       update_file(0, ren1->pair->one->sha1,
+                                                   ren1->pair->one->mode, src);
+                               }
+                               conflict_rename_rename(ren1, branch1, ren2, branch2);
+                       } else {
+                               struct merge_file_info mfi;
+                               remove_file(1, ren1_src, 1);
+                               mfi = merge_file(ren1->pair->one,
+                                                ren1->pair->two,
+                                                ren2->pair->two,
+                                                branch1,
+                                                branch2);
+                               if (mfi.merge || !mfi.clean)
+                                       output(1, "Renamed %s->%s", src, ren1_dst);
+
+                               if (mfi.merge)
+                                       output(2, "Auto-merged %s", ren1_dst);
+
+                               if (!mfi.clean) {
+                                       output(1, "CONFLICT (content): merge conflict in %s",
+                                              ren1_dst);
+                                       clean_merge = 0;
+
+                                       if (!index_only)
+                                               update_stages(ren1_dst,
+                                                             ren1->pair->one,
+                                                             ren1->pair->two,
+                                                             ren2->pair->two,
+                                                             1 /* clear */);
+                               }
+                               update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
+                       }
+               } else {
+                       /* Renamed in 1, maybe changed in 2 */
+                       struct path_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 (path_list_has_path(&current_directory_set, ren1_dst)) {
+                               clean_merge = 0;
+                               output(1, "CONFLICT (rename/directory): Renamed %s->%s in %s "
+                                      " directory %s added in %s",
+                                      ren1_src, ren1_dst, branch1,
+                                      ren1_dst, branch2);
+                               conflict_rename_dir(ren1, branch1);
+                       } else if (sha_eq(src_other.sha1, null_sha1)) {
+                               clean_merge = 0;
+                               output(1, "CONFLICT (rename/delete): Renamed %s->%s in %s "
+                                      "and deleted in %s",
+                                      ren1_src, ren1_dst, branch1,
+                                      branch2);
+                               update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
+                       } else if (!sha_eq(dst_other.sha1, null_sha1)) {
+                               const char *new_path;
+                               clean_merge = 0;
+                               try_merge = 1;
+                               output(1, "CONFLICT (rename/add): Renamed %s->%s in %s. "
+                                      "%s added in %s",
+                                      ren1_src, ren1_dst, branch1,
+                                      ren1_dst, branch2);
+                               new_path = unique_path(ren1_dst, branch2);
+                               output(1, "Added as %s instead", new_path);
+                               update_file(0, dst_other.sha1, dst_other.mode, new_path);
+                       } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) {
+                               ren2 = item->util;
+                               clean_merge = 0;
+                               ren2->processed = 1;
+                               output(1, "CONFLICT (rename/rename): Renamed %s->%s in %s. "
+                                      "Renamed %s->%s in %s",
+                                      ren1_src, ren1_dst, branch1,
+                                      ren2->pair->one->path, ren2->pair->two->path, branch2);
+                               conflict_rename_rename_2(ren1, branch1, ren2, branch2);
+                       } else
+                               try_merge = 1;
+
+                       if (try_merge) {
+                               struct diff_filespec *o, *a, *b;
+                               struct merge_file_info mfi;
+                               src_other.path = (char *)ren1_src;
+
+                               o = ren1->pair->one;
+                               if (a_renames == renames1) {
+                                       a = ren1->pair->two;
+                                       b = &src_other;
+                               } else {
+                                       b = ren1->pair->two;
+                                       a = &src_other;
+                               }
+                               mfi = merge_file(o, a, b,
+                                               a_branch, b_branch);
+
+                               if (mfi.clean &&
+                                   sha_eq(mfi.sha, ren1->pair->two->sha1) &&
+                                   mfi.mode == ren1->pair->two->mode)
+                                       /*
+                                        * This messaged is part of
+                                        * t6022 test. If you change
+                                        * it update the test too.
+                                        */
+                                       output(3, "Skipped %s (merged same as existing)", ren1_dst);
+                               else {
+                                       if (mfi.merge || !mfi.clean)
+                                               output(1, "Renamed %s => %s", ren1_src, ren1_dst);
+                                       if (mfi.merge)
+                                               output(2, "Auto-merged %s", ren1_dst);
+                                       if (!mfi.clean) {
+                                               output(1, "CONFLICT (rename/modify): Merge conflict in %s",
+                                                      ren1_dst);
+                                               clean_merge = 0;
+
+                                               if (!index_only)
+                                                       update_stages(ren1_dst,
+                                                                     o, a, b, 1);
+                                       }
+                                       update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
+                               }
+                       }
+               }
+       }
+       path_list_clear(&a_by_dst, 0);
+       path_list_clear(&b_by_dst, 0);
+
+       return clean_merge;
+}
+
+static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)
+{
+       return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
+}
+
+/* Per entry merge function */
+static int process_entry(const char *path, struct stage_data *entry,
+                        const char *branch1,
+                        const char *branch2)
+{
+       /*
+       printf("processing entry, clean cache: %s\n", index_only ? "yes": "no");
+       print_index_entry("\tpath: ", entry);
+       */
+       int clean_merge = 1;
+       unsigned o_mode = entry->stages[1].mode;
+       unsigned a_mode = entry->stages[2].mode;
+       unsigned b_mode = entry->stages[3].mode;
+       unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
+       unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
+       unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
+
+       if (o_sha && (!a_sha || !b_sha)) {
+               /* Case A: Deleted in one */
+               if ((!a_sha && !b_sha) ||
+                   (sha_eq(a_sha, o_sha) && !b_sha) ||
+                   (!a_sha && sha_eq(b_sha, o_sha))) {
+                       /* Deleted in both or deleted in one and
+                        * unchanged in the other */
+                       if (a_sha)
+                               output(2, "Removed %s", path);
+                       /* do not touch working file if it did not exist */
+                       remove_file(1, path, !a_sha);
+               } else {
+                       /* Deleted in one and changed in the other */
+                       clean_merge = 0;
+                       if (!a_sha) {
+                               output(1, "CONFLICT (delete/modify): %s deleted in %s "
+                                      "and modified in %s. Version %s of %s left in tree.",
+                                      path, branch1,
+                                      branch2, branch2, path);
+                               update_file(0, b_sha, b_mode, path);
+                       } else {
+                               output(1, "CONFLICT (delete/modify): %s deleted in %s "
+                                      "and modified in %s. Version %s of %s left in tree.",
+                                      path, branch2,
+                                      branch1, branch1, path);
+                               update_file(0, a_sha, a_mode, path);
+                       }
+               }
+
+       } else if ((!o_sha && a_sha && !b_sha) ||
+                  (!o_sha && !a_sha && b_sha)) {
+               /* Case B: Added in one. */
+               const char *add_branch;
+               const char *other_branch;
+               unsigned mode;
+               const unsigned char *sha;
+               const char *conf;
+
+               if (a_sha) {
+                       add_branch = branch1;
+                       other_branch = branch2;
+                       mode = a_mode;
+                       sha = a_sha;
+                       conf = "file/directory";
+               } else {
+                       add_branch = branch2;
+                       other_branch = branch1;
+                       mode = b_mode;
+                       sha = b_sha;
+                       conf = "directory/file";
+               }
+               if (path_list_has_path(&current_directory_set, path)) {
+                       const char *new_path = unique_path(path, add_branch);
+                       clean_merge = 0;
+                       output(1, "CONFLICT (%s): There is a directory with name %s in %s. "
+                              "Added %s as %s",
+                              conf, path, other_branch, path, new_path);
+                       remove_file(0, path, 0);
+                       update_file(0, sha, mode, new_path);
+               } else {
+                       output(2, "Added %s", path);
+                       update_file(1, sha, mode, path);
+               }
+       } else if (a_sha && b_sha) {
+               /* Case C: Added in both (check for same permissions) and */
+               /* case D: Modified in both, but differently. */
+               const char *reason = "content";
+               struct merge_file_info mfi;
+               struct diff_filespec o, a, b;
+
+               if (!o_sha) {
+                       reason = "add/add";
+                       o_sha = (unsigned char *)null_sha1;
+               }
+               output(2, "Auto-merged %s", path);
+               o.path = a.path = b.path = (char *)path;
+               hashcpy(o.sha1, o_sha);
+               o.mode = o_mode;
+               hashcpy(a.sha1, a_sha);
+               a.mode = a_mode;
+               hashcpy(b.sha1, b_sha);
+               b.mode = b_mode;
+
+               mfi = merge_file(&o, &a, &b,
+                                branch1, branch2);
+
+               clean_merge = mfi.clean;
+               if (mfi.clean)
+                       update_file(1, mfi.sha, mfi.mode, path);
+               else if (S_ISGITLINK(mfi.mode))
+                       output(1, "CONFLICT (submodule): Merge conflict in %s "
+                              "- needs %s", path, sha1_to_hex(b.sha1));
+               else {
+                       output(1, "CONFLICT (%s): Merge conflict in %s",
+                                       reason, path);
+
+                       if (index_only)
+                               update_file(0, mfi.sha, mfi.mode, path);
+                       else
+                               update_file_flags(mfi.sha, mfi.mode, path,
+                                             0 /* update_cache */, 1 /* update_working_directory */);
+               }
+       } else if (!o_sha && !a_sha && !b_sha) {
+               /*
+                * this entry was deleted altogether. a_mode == 0 means
+                * we had that path and want to actively remove it.
+                */
+               remove_file(1, path, !a_mode);
+       } else
+               die("Fatal merge failure, shouldn't happen.");
+
+       return clean_merge;
+}
+
+int merge_trees(struct tree *head,
+               struct tree *merge,
+               struct tree *common,
+               const char *branch1,
+               const char *branch2,
+               struct tree **result)
+{
+       int code, clean;
+
+       if (subtree_merge) {
+               merge = shift_tree_object(head, merge);
+               common = shift_tree_object(head, common);
+       }
+
+       if (sha_eq(common->object.sha1, merge->object.sha1)) {
+               output(0, "Already uptodate!");
+               *result = head;
+               return 1;
+       }
+
+       code = git_merge_trees(index_only, common, head, merge);
+
+       if (code != 0)
+               die("merging of trees %s and %s failed",
+                   sha1_to_hex(head->object.sha1),
+                   sha1_to_hex(merge->object.sha1));
+
+       if (unmerged_cache()) {
+               struct path_list *entries, *re_head, *re_merge;
+               int i;
+               path_list_clear(&current_file_set, 1);
+               path_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].path;
+                       struct stage_data *e = entries->items[i].util;
+                       if (!e->processed
+                               && !process_entry(path, e, branch1, branch2))
+                               clean = 0;
+               }
+
+               path_list_clear(re_merge, 0);
+               path_list_clear(re_head, 0);
+               path_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];
+       char *name;
+
+       if (strlen(branch) != 40)
+               return branch;
+       sprintf(githead_env, "GITHEAD_%s", branch);
+       name = getenv(githead_env);
+       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)
+{
+       if (!strcasecmp(var, "merge.verbosity")) {
+               verbosity = git_config_int(var, value);
+               return 0;
+       }
+       if (!strcasecmp(var, "diff.renamelimit")) {
+               rename_limit = git_config_int(var, value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
+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;
+
+       if (argv[0]) {
+               int namelen = strlen(argv[0]);
+               if (8 < namelen &&
+                   !strcmp(argv[0] + namelen - 8, "-subtree"))
+                       subtree_merge = 1;
+       }
+
+       git_config(merge_config);
+       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 (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];
+
+       h1 = get_ref(branch1);
+       h2 = get_ref(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);
+
+       if (active_cache_changed &&
+           (write_cache(index_fd, active_cache, active_nr) ||
+            commit_locked_index(lock)))
+                       die ("unable to write %s", get_index_file());
+
+       return clean ? 0: 1;
+}
index 7dff6536dea3b725a95fcd17b001c7297b251151..1bba6e6a64220ef2121ce8817ccae123739f4e8e 100644 (file)
@@ -16,6 +16,7 @@
 #include "progress.h"
 
 #ifdef THREADED_DELTA_SEARCH
+#include "thread-utils.h"
 #include <pthread.h>
 #endif
 
@@ -1851,11 +1852,11 @@ static int git_pack_config(const char *k, const char *v)
        }
        if (!strcmp(k, "pack.threads")) {
                delta_search_threads = git_config_int(k, v);
-               if (delta_search_threads < 1)
+               if (delta_search_threads < 0)
                        die("invalid number of threads specified (%d)",
                            delta_search_threads);
 #ifndef THREADED_DELTA_SEARCH
-               if (delta_search_threads > 1)
+               if (delta_search_threads != 1)
                        warning("no threads support, ignoring %s", k);
 #endif
                return 0;
@@ -2121,10 +2122,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                if (!prefixcmp(arg, "--threads=")) {
                        char *end;
                        delta_search_threads = strtoul(arg+10, &end, 0);
-                       if (!arg[10] || *end || delta_search_threads < 1)
+                       if (!arg[10] || *end || delta_search_threads < 0)
                                usage(pack_usage);
 #ifndef THREADED_DELTA_SEARCH
-                       if (delta_search_threads > 1)
+                       if (delta_search_threads != 1)
                                warning("no threads support, "
                                        "ignoring %s", arg);
 #endif
@@ -2234,6 +2235,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        if (!pack_to_stdout && thin)
                die("--thin cannot be used to build an indexable pack.");
 
+#ifdef THREADED_DELTA_SEARCH
+       if (!delta_search_threads)      /* --threads=0 means autodetect */
+               delta_search_threads = online_cpus();
+#endif
+
        prepare_packed_git();
 
        if (progress)
index 9f727c00f689ae23373502c7fb21b2234a1c1748..b68c6813b8c71e0e00790eb422e57f8e219095fb 100644 (file)
@@ -44,15 +44,6 @@ static void set_refspecs(const char **refs, int nr)
                        strcat(tag, refs[i]);
                        ref = tag;
                }
-               if (!strcmp("HEAD", ref)) {
-                       unsigned char sha1_dummy[20];
-                       ref = resolve_ref(ref, sha1_dummy, 1, NULL);
-                       if (!ref)
-                               die("HEAD cannot be resolved.");
-                       if (prefixcmp(ref, "refs/heads/"))
-                               die("HEAD cannot be resolved to branch.");
-                       ref = xstrdup(ref + 11);
-               }
                add_refspec(ref);
        }
 }
index 726fb0b588dcb550b5554b111a416c7ac3b25521..0138f5a9172034b2ce34222ff077a975f8998005 100644 (file)
@@ -41,6 +41,7 @@ static int read_cache_unmerged(void)
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
                if (ce_stage(ce)) {
+                       remove_index_entry(ce);
                        if (last && !strcmp(ce->name, last->name))
                                continue;
                        cache_tree_invalidate_path(active_cache_tree, ce->name);
@@ -268,7 +269,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                parse_tree(tree);
                init_tree_desc(t+i, tree->buffer, tree->size);
        }
-       unpack_trees(nr_trees, t, &opts);
+       if (unpack_trees(nr_trees, t, &opts))
+               return 128;
 
        /*
         * When reading only one tree (either the most basic form,
index 4836ec951be727512bcd550dd3604c43c39aa557..ab53c8cb7c08298c3ab0f69bf600a70385dafa64 100644 (file)
@@ -276,10 +276,11 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
        for_each_reflog_ent(ref, expire_reflog_ent, &cb);
  finish:
        if (cb.newlog) {
-               if (fclose(cb.newlog))
+               if (fclose(cb.newlog)) {
                        status |= error("%s: %s", strerror(errno),
                                        newlog_path);
-               if (rename(newlog_path, log_file)) {
+                       unlink(newlog_path);
+               } else if (rename(newlog_path, log_file)) {
                        status |= error("cannot rename %s to %s",
                                        newlog_path, log_file);
                        unlink(newlog_path);
index b0c17bde879d42c16fe5c7f0756763cd638f8bac..c607aade637423f0129167163fa4055b08546248 100644 (file)
@@ -267,23 +267,6 @@ static int diff_two(const char *file1, const char *label1,
        return 0;
 }
 
-static int copy_file(const char *src, const char *dest)
-{
-       FILE *in, *out;
-       char buffer[32768];
-       int count;
-
-       if (!(in = fopen(src, "r")))
-               return error("Could not open %s", src);
-       if (!(out = fopen(dest, "w")))
-               return error("Could not open %s", dest);
-       while ((count = fread(buffer, 1, sizeof(buffer), in)))
-               fwrite(buffer, 1, count, out);
-       fclose(in);
-       fclose(out);
-       return 0;
-}
-
 static int do_plain_rerere(struct path_list *rr, int fd)
 {
        struct path_list conflict = { NULL, 0, 0, 1 };
@@ -343,7 +326,7 @@ static int do_plain_rerere(struct path_list *rr, int fd)
                        continue;
 
                fprintf(stderr, "Recorded resolution for '%s'.\n", path);
-               copy_file(path, rr_path(name, "postimage"));
+               copy_file(rr_path(name, "postimage"), path, 0666);
 tail_optimization:
                if (i < rr->nr - 1)
                        memmove(rr->items + i,
index 7ee811f0b8de34025a14a53d42f4fceb477a598e..af0037ec6e3456ccd688da6375c76db4a59f099a 100644 (file)
@@ -16,6 +16,7 @@
 #include "diff.h"
 #include "diffcore.h"
 #include "tree.h"
+#include "branch.h"
 
 static const char builtin_reset_usage[] =
 "git-reset [--mixed | --soft | --hard] [-q] [<commit-ish>] [ [--] <paths>...]";
@@ -44,18 +45,6 @@ static inline int is_merge(void)
        return !access(git_path("MERGE_HEAD"), F_OK);
 }
 
-static int unmerged_files(void)
-{
-       int i;
-       read_cache();
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (ce_stage(ce))
-                       return 1;
-       }
-       return 0;
-}
-
 static int reset_index_file(const unsigned char *sha1, int is_hard_reset)
 {
        int i = 0;
@@ -250,7 +239,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
         * at all, but requires them in a good order.  Other resets reset
         * the index file to the tree object we are switching to. */
        if (reset_type == SOFT) {
-               if (is_merge() || unmerged_files())
+               if (is_merge() || read_cache() < 0 || unmerged_cache())
                        die("Cannot do a soft reset in the middle of a merge.");
        }
        else if (reset_index_file(sha1, (reset_type == HARD)))
@@ -282,10 +271,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                break;
        }
 
-       unlink(git_path("MERGE_HEAD"));
-       unlink(git_path("rr-cache/MERGE_RR"));
-       unlink(git_path("MERGE_MSG"));
-       unlink(git_path("SQUASH_MSG"));
+       remove_branch_state();
 
        free(reflog_action);
 
index b9af1a5a554e21338531b88cd34bcb767d5f3848..90dbb9d7c1a28fd463ba827cb8da2eab42e489df 100644 (file)
@@ -315,7 +315,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
                s = strchr(sb.buf, ' ');
                if (!s || *sb.buf == ' ') {
                        o->type = OPTION_GROUP;
-                       o->help = xstrdup(skipspaces(s));
+                       o->help = xstrdup(skipspaces(sb.buf));
                        continue;
                }
 
index 8afb1d0bca0635dc22f658455477d9fada231290..b0cfae83fc4dcdd8f58e29b87fd8aa4b6af0f6c0 100644 (file)
@@ -71,6 +71,7 @@ static int pack_objects(int fd, struct ref *refs)
                refs = refs->next;
        }
 
+       close(po.in);
        if (finish_command(&po))
                return error("pack-objects died with strange error");
        return 0;
@@ -403,12 +404,15 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
        if (!remote_tail)
                remote_tail = &remote_refs;
        if (match_refs(local_refs, remote_refs, &remote_tail,
-                                              nr_refspec, refspec, flags))
+                      nr_refspec, refspec, flags)) {
+               close(out);
                return -1;
+       }
 
        if (!remote_refs) {
                fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
                        "Perhaps you should specify a branch such as 'master'.\n");
+               close(out);
                return 0;
        }
 
@@ -495,12 +499,11 @@ 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) {
-                       close(out);
+               if (pack_objects(out, remote_refs) < 0)
                        return -1;
-               }
        }
-       close(out);
+       else
+               close(out);
 
        if (expect_status_report)
                ret = receive_status(in, remote_refs);
@@ -648,7 +651,7 @@ int send_pack(struct send_pack_args *my_args,
        conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0);
        ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads);
        close(fd[0]);
-       close(fd[1]);
+       /* do_send_pack always closes fd[1] */
        ret |= finish_connect(conn);
        return !!ret;
 }
index 0055a57aeb24392de0dcf337bb93f30fceccd88f..af31abaaf862bb85a72e8052cf28a874d34c714a 100644 (file)
@@ -6,13 +6,11 @@
 #include "revision.h"
 #include "utf8.h"
 #include "mailmap.h"
+#include "shortlog.h"
 
 static const char shortlog_usage[] =
 "git-shortlog [-n] [-s] [-e] [<commit-id>... ]";
 
-static char *common_repo_prefix;
-static int email;
-
 static int compare_by_number(const void *a1, const void *a2)
 {
        const struct path_list_item *i1 = a1, *i2 = a2;
@@ -26,13 +24,11 @@ static int compare_by_number(const void *a1, const void *a2)
                return -1;
 }
 
-static struct path_list mailmap = {NULL, 0, 0, 0};
-
-static void insert_one_record(struct path_list *list,
+static void insert_one_record(struct shortlog *log,
                              const char *author,
                              const char *oneline)
 {
-       const char *dot3 = common_repo_prefix;
+       const char *dot3 = log->common_repo_prefix;
        char *buffer, *p;
        struct path_list_item *item;
        struct path_list *onelines;
@@ -47,7 +43,7 @@ static void insert_one_record(struct path_list *list,
        eoemail = strchr(boemail, '>');
        if (!eoemail)
                return;
-       if (!map_email(&mailmap, boemail+1, namebuf, sizeof(namebuf))) {
+       if (!map_email(&log->mailmap, boemail+1, namebuf, sizeof(namebuf))) {
                while (author < boemail && isspace(*author))
                        author++;
                for (len = 0;
@@ -61,14 +57,14 @@ static void insert_one_record(struct path_list *list,
        else
                len = strlen(namebuf);
 
-       if (email) {
+       if (log->email) {
                size_t room = sizeof(namebuf) - len - 1;
                int maillen = eoemail - boemail + 1;
                snprintf(namebuf + len, room, " %.*s", maillen, boemail);
        }
 
        buffer = xstrdup(namebuf);
-       item = path_list_insert(buffer, list);
+       item = path_list_insert(buffer, &log->list);
        if (item->util == NULL)
                item->util = xcalloc(1, sizeof(struct path_list));
        else
@@ -114,7 +110,7 @@ static void insert_one_record(struct path_list *list,
        onelines->items[onelines->nr++].path = buffer;
 }
 
-static void read_from_stdin(struct path_list *list)
+static void read_from_stdin(struct shortlog *log)
 {
        char author[1024], oneline[1024];
 
@@ -128,39 +124,43 @@ static void read_from_stdin(struct path_list *list)
                while (fgets(oneline, sizeof(oneline), stdin) &&
                       oneline[0] == '\n')
                        ; /* discard blanks */
-               insert_one_record(list, author + 8, oneline);
+               insert_one_record(log, author + 8, oneline);
        }
 }
 
-static void get_from_rev(struct rev_info *rev, struct path_list *list)
+void shortlog_add_commit(struct shortlog *log, struct commit *commit)
 {
-       struct commit *commit;
-
-       if (prepare_revision_walk(rev))
-               die("revision walk setup failed");
-       while ((commit = get_revision(rev)) != NULL) {
-               const char *author = NULL, *buffer;
+       const char *author = NULL, *buffer;
 
-               buffer = commit->buffer;
-               while (*buffer && *buffer != '\n') {
-                       const char *eol = strchr(buffer, '\n');
+       buffer = commit->buffer;
+       while (*buffer && *buffer != '\n') {
+               const char *eol = strchr(buffer, '\n');
 
-                       if (eol == NULL)
-                               eol = buffer + strlen(buffer);
-                       else
-                               eol++;
+               if (eol == NULL)
+                       eol = buffer + strlen(buffer);
+               else
+                       eol++;
 
-                       if (!prefixcmp(buffer, "author "))
-                               author = buffer + 7;
-                       buffer = eol;
-               }
-               if (!author)
-                       die("Missing author: %s",
-                           sha1_to_hex(commit->object.sha1));
-               if (*buffer)
-                       buffer++;
-               insert_one_record(list, author, !*buffer ? "<none>" : buffer);
+               if (!prefixcmp(buffer, "author "))
+                       author = buffer + 7;
+               buffer = eol;
        }
+       if (!author)
+               die("Missing author: %s",
+                   sha1_to_hex(commit->object.sha1));
+       if (*buffer)
+               buffer++;
+       insert_one_record(log, author, !*buffer ? "<none>" : buffer);
+}
+
+static void get_from_rev(struct rev_info *rev, struct shortlog *log)
+{
+       struct commit *commit;
+
+       if (prepare_revision_walk(rev))
+               die("revision walk setup failed");
+       while ((commit = get_revision(rev)) != NULL)
+               shortlog_add_commit(log, commit);
 }
 
 static int parse_uint(char const **arg, int comma)
@@ -212,29 +212,38 @@ static void parse_wrap_args(const char *arg, int *in1, int *in2, int *wrap)
                die(wrap_arg_usage);
 }
 
+void shortlog_init(struct shortlog *log)
+{
+       memset(log, 0, sizeof(*log));
+
+       read_mailmap(&log->mailmap, ".mailmap", &log->common_repo_prefix);
+
+       log->list.strdup_paths = 1;
+       log->wrap = DEFAULT_WRAPLEN;
+       log->in1 = DEFAULT_INDENT1;
+       log->in2 = DEFAULT_INDENT2;
+}
+
 int cmd_shortlog(int argc, const char **argv, const char *prefix)
 {
+       struct shortlog log;
        struct rev_info rev;
-       struct path_list list = { NULL, 0, 0, 1 };
-       int i, j, sort_by_number = 0, summary = 0;
-       int wrap_lines = 0;
-       int wrap = DEFAULT_WRAPLEN;
-       int in1 = DEFAULT_INDENT1;
-       int in2 = DEFAULT_INDENT2;
+
+       shortlog_init(&log);
 
        /* since -n is a shadowed rev argument, parse our args first */
        while (argc > 1) {
                if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered"))
-                       sort_by_number = 1;
+                       log.sort_by_number = 1;
                else if (!strcmp(argv[1], "-s") ||
                                !strcmp(argv[1], "--summary"))
-                       summary = 1;
+                       log.summary = 1;
                else if (!strcmp(argv[1], "-e") ||
                         !strcmp(argv[1], "--email"))
-                       email = 1;
+                       log.email = 1;
                else if (!prefixcmp(argv[1], "-w")) {
-                       wrap_lines = 1;
-                       parse_wrap_args(argv[1], &in1, &in2, &wrap);
+                       log.wrap_lines = 1;
+                       parse_wrap_args(argv[1], &log.in1, &log.in2, &log.wrap);
                }
                else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
                        usage(shortlog_usage);
@@ -248,34 +257,38 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
        if (argc > 1)
                die ("unrecognized argument: %s", argv[1]);
 
-       read_mailmap(&mailmap, ".mailmap", &common_repo_prefix);
-
        /* assume HEAD if from a tty */
        if (!rev.pending.nr && isatty(0))
                add_head_to_pending(&rev);
        if (rev.pending.nr == 0) {
-               read_from_stdin(&list);
+               read_from_stdin(&log);
        }
        else
-               get_from_rev(&rev, &list);
+               get_from_rev(&rev, &log);
 
-       if (sort_by_number)
-               qsort(list.items, list.nr, sizeof(struct path_list_item),
-                       compare_by_number);
+       shortlog_output(&log);
+       return 0;
+}
 
-       for (i = 0; i < list.nr; i++) {
-               struct path_list *onelines = list.items[i].util;
+void shortlog_output(struct shortlog *log)
+{
+       int i, j;
+       if (log->sort_by_number)
+               qsort(log->list.items, log->list.nr, sizeof(struct path_list_item),
+                       compare_by_number);
+       for (i = 0; i < log->list.nr; i++) {
+               struct path_list *onelines = log->list.items[i].util;
 
-               if (summary) {
-                       printf("%6d\t%s\n", onelines->nr, list.items[i].path);
+               if (log->summary) {
+                       printf("%6d\t%s\n", onelines->nr, log->list.items[i].path);
                } else {
-                       printf("%s (%d):\n", list.items[i].path, onelines->nr);
+                       printf("%s (%d):\n", log->list.items[i].path, onelines->nr);
                        for (j = onelines->nr - 1; j >= 0; j--) {
                                const char *msg = onelines->items[j].path;
 
-                               if (wrap_lines) {
-                                       int col = print_wrapped_text(msg, in1, in2, wrap);
-                                       if (col != wrap)
+                               if (log->wrap_lines) {
+                                       int col = print_wrapped_text(msg, log->in1, log->in2, log->wrap);
+                                       if (col != log->wrap)
                                                putchar('\n');
                                }
                                else
@@ -287,13 +300,11 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
                onelines->strdup_paths = 1;
                path_list_clear(onelines, 1);
                free(onelines);
-               list.items[i].util = NULL;
+               log->list.items[i].util = NULL;
        }
 
-       list.strdup_paths = 1;
-       path_list_clear(&list, 1);
-       mailmap.strdup_paths = 1;
-       path_list_clear(&mailmap, 1);
-
-       return 0;
+       log->list.strdup_paths = 1;
+       path_list_clear(&log->list, 1);
+       log->mailmap.strdup_paths = 1;
+       path_list_clear(&log->mailmap, 1);
 }
index 716b4fff323f7df638f8bed0940a1119e43c2590..28c36fdcd1658968ff7c3e2f1d6ba6f364f99592 100644 (file)
@@ -226,12 +226,13 @@ static int do_sign(struct strbuf *buffer)
 
        if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
                close(gpg.in);
+               close(gpg.out);
                finish_command(&gpg);
                return error("gpg did not accept the tag data");
        }
        close(gpg.in);
-       gpg.close_in = 0;
        len = strbuf_read(buffer, gpg.out, 1024);
+       close(gpg.out);
 
        if (finish_command(&gpg) || !len || len < 0)
                return error("gpg failed to sign the tag");
index cc4c55d7ee35ceeaf8c4a857d5dad857a7eb2664..f3ef11fa2d582682b428d6e165da65f05e2d7511 100644 (file)
@@ -45,14 +45,12 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
        memset(&gpg, 0, sizeof(gpg));
        gpg.argv = args_gpg;
        gpg.in = -1;
-       gpg.out = 1;
        args_gpg[2] = path;
        if (start_command(&gpg))
                return error("could not run gpg.");
 
        write_in_full(gpg.in, buf, len);
        close(gpg.in);
-       gpg.close_in = 0;
        ret = finish_command(&gpg);
 
        unlink(path);
index 3d1628c597b21cc22b750809c27d4499e725d259..674c8a141faf808883c9de283d10da01b3f9c2d5 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -18,6 +18,7 @@ extern int cmd_blame(int argc, const char **argv, const char *prefix);
 extern int cmd_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_bundle(int argc, const char **argv, const char *prefix);
 extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
+extern int cmd_checkout(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
 extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
@@ -56,6 +57,7 @@ extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_recursive(int argc, const char **argv, const char *prefix);
 extern int cmd_mv(int argc, const char **argv, const char *prefix);
 extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
 extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
index bd12ec8537781c5c82e77637312ccabb708d5040..0ba5df17e15d679b03fe38af40260c118c9588fa 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -333,10 +333,12 @@ int create_bundle(struct bundle_header *header, const char *path,
                write_or_die(rls.in, sha1_to_hex(object->sha1), 40);
                write_or_die(rls.in, "\n", 1);
        }
+       close(rls.in);
        if (finish_command(&rls))
                return error ("pack-objects died");
-
-       return bundle_to_stdout ? close(bundle_fd) : commit_lock_file(&lock);
+       if (!bundle_to_stdout)
+               commit_lock_file(&lock);
+       return 0;
 }
 
 int unbundle(struct bundle_header *header, int bundle_fd)
diff --git a/cache.h b/cache.h
index e1000bccb2470ad66038aabbf57b0af96e506f11..f16d341f5233b154c6fc1518ae0bd619c8f05c38 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -110,7 +110,6 @@ struct ondisk_cache_entry {
 };
 
 struct cache_entry {
-       struct cache_entry *next;
        unsigned int ce_ctime;
        unsigned int ce_mtime;
        unsigned int ce_dev;
@@ -121,6 +120,7 @@ struct cache_entry {
        unsigned int ce_size;
        unsigned int ce_flags;
        unsigned char sha1[20];
+       struct cache_entry *next;
        char name[FLEX_ARRAY]; /* more */
 };
 
@@ -133,7 +133,39 @@ struct cache_entry {
 #define CE_UPDATE    (0x10000)
 #define CE_REMOVE    (0x20000)
 #define CE_UPTODATE  (0x40000)
-#define CE_UNHASHED  (0x80000)
+
+#define CE_HASHED    (0x100000)
+#define CE_UNHASHED  (0x200000)
+
+/*
+ * Copy the sha1 and stat state of a cache entry from one to
+ * another. But we never change the name, or the hash state!
+ */
+#define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
+static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)
+{
+       unsigned int state = dst->ce_flags & CE_STATE_MASK;
+
+       /* Don't copy hash chain and name */
+       memcpy(dst, src, offsetof(struct cache_entry, next));
+
+       /* Restore the hash state */
+       dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state;
+}
+
+/*
+ * We don't actually *remove* it, we can just mark it invalid so that
+ * we won't find it in lookups.
+ *
+ * Not only would we have to search the lists (simple enough), but
+ * we'd also have to rehash other hash buckets in case this makes the
+ * hash bucket empty (common). So it's much better to just mark
+ * it.
+ */
+static inline void remove_index_entry(struct cache_entry *ce)
+{
+       ce->ce_flags |= CE_UNHASHED;
+}
 
 static inline unsigned create_ce_flags(size_t len, unsigned stage)
 {
@@ -220,6 +252,7 @@ extern struct index_state the_index;
 #define read_cache_from(path) read_index_from(&the_index, (path))
 #define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
 #define discard_cache() discard_index(&the_index)
+#define unmerged_cache() unmerged_index(&the_index)
 #define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen))
 #define add_cache_entry(ce, option) add_index_entry(&the_index, (ce), (option))
 #define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
@@ -314,6 +347,7 @@ extern int read_index(struct index_state *);
 extern int read_index_from(struct index_state *, const char *path);
 extern int write_index(struct index_state *, int newfd);
 extern int discard_index(struct index_state *);
+extern int unmerged_index(struct index_state *);
 extern int verify_path(const char *path);
 extern int index_name_exists(struct index_state *istate, const char *name, int namelen);
 extern int index_name_pos(struct index_state *, const char *name, int namelen);
@@ -391,6 +425,15 @@ enum safe_crlf {
 
 extern enum safe_crlf safe_crlf;
 
+enum branch_track {
+       BRANCH_TRACK_NEVER = 0,
+       BRANCH_TRACK_REMOTE,
+       BRANCH_TRACK_ALWAYS,
+       BRANCH_TRACK_EXPLICIT,
+};
+
+extern enum branch_track git_branch_track;
+
 #define GIT_REPO_VERSION 0
 extern int repository_format_version;
 extern int check_repository_format(void);
@@ -666,6 +709,7 @@ extern const char *git_log_output_encoding;
 /* IO helper functions */
 extern void maybe_flush_or_die(FILE *, const char *);
 extern int copy_fd(int ifd, int ofd);
+extern int copy_file(const char *dst, const char *src, int mode);
 extern int read_in_full(int fd, void *buf, size_t count);
 extern int write_in_full(int fd, const void *buf, size_t count);
 extern void write_or_die(int fd, const void *buf, size_t count);
@@ -719,6 +763,7 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i
 #define WS_TRAILING_SPACE      01
 #define WS_SPACE_BEFORE_TAB    02
 #define WS_INDENT_WITH_NON_TAB 04
+#define WS_CR_AT_EOL           010
 #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
 extern unsigned whitespace_rule_cfg;
 extern unsigned whitespace_rule(const char *);
@@ -727,10 +772,13 @@ extern unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
     FILE *stream, const char *set,
     const char *reset, const char *ws);
 extern char *whitespace_error_string(unsigned ws);
+extern int ws_fix_copy(char *, const char *, int, unsigned, int *);
 
 /* ls-files */
 int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen);
 int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
 void overlay_tree_on_cache(const char *tree_name, const char *prefix);
 
+char *alias_lookup(const char *alias);
+
 #endif /* CACHE_H */
index 10e2b5d4cfdc7ac129ead711421ccc51d2667f02..80d65b96dafe33b128e4444d81046360eeb105af 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -71,6 +71,21 @@ extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*,
                                 int abbrev, const char *subject,
                                 const char *after_subject, enum date_mode,
                                int non_ascii_present);
+void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
+                  const char *line, enum date_mode dmode,
+                  const char *encoding);
+void pp_title_line(enum cmit_fmt fmt,
+                  const char **msg_p,
+                  struct strbuf *sb,
+                  const char *subject,
+                  const char *after_subject,
+                  const char *encoding,
+                  int plain_non_ascii);
+void pp_remainder(enum cmit_fmt fmt,
+                 const char **msg_p,
+                 struct strbuf *sb,
+                 int indent);
+
 
 /** Removes the first commit from a list sorted by date, and adds all
  * of its parents.
index cba2bcfb6722bb26ed80e8b7e5a50616cb4702e1..062449459e1a4cfc2a605c065ed281669e0e7452 100644 (file)
--- a/config.c
+++ b/config.c
@@ -471,6 +471,14 @@ int git_default_config(const char *var, const char *value)
                whitespace_rule_cfg = parse_whitespace_rule(value);
                return 0;
        }
+       if (!strcmp(var, "branch.autosetupmerge")) {
+               if (value && !strcasecmp(value, "always")) {
+                       git_branch_track = BRANCH_TRACK_ALWAYS;
+                       return 0;
+               }
+               git_branch_track = git_config_bool(var, value);
+               return 0;
+       }
 
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
index 4ea727b14303e397117067993dbda446ed154ea1..8722a687954ec47a538eff45d41434deedf717fb 100755 (executable)
@@ -91,7 +91,10 @@ __git_ps1 ()
                        fi
                        if ! b="$(git symbolic-ref HEAD 2>/dev/null)"
                        then
-                               b="$(cut -c1-7 $g/HEAD)..."
+                               if ! b="$(git describe --exact-match HEAD 2>/dev/null)"
+                               then
+                                       b="$(cut -c1-7 $g/HEAD)..."
+                               fi
                        fi
                fi
 
diff --git a/contrib/examples/git-checkout.sh b/contrib/examples/git-checkout.sh
new file mode 100755 (executable)
index 0000000..1a7689a
--- /dev/null
@@ -0,0 +1,302 @@
+#!/bin/sh
+
+OPTIONS_KEEPDASHDASH=t
+OPTIONS_SPEC="\
+git-checkout [options] [<branch>] [<paths>...]
+--
+b=          create a new branch started at <branch>
+l           create the new branch's reflog
+track       arrange that the new branch tracks the remote branch
+f           proceed even if the index or working tree is not HEAD
+m           merge local modifications into the new branch
+q,quiet     be quiet
+"
+SUBDIRECTORY_OK=Sometimes
+. git-sh-setup
+require_work_tree
+
+old_name=HEAD
+old=$(git rev-parse --verify $old_name 2>/dev/null)
+oldbranch=$(git symbolic-ref $old_name 2>/dev/null)
+new=
+new_name=
+force=
+branch=
+track=
+newbranch=
+newbranch_log=
+merge=
+quiet=
+v=-v
+LF='
+'
+
+while test $# != 0; do
+       case "$1" in
+       -b)
+               shift
+               newbranch="$1"
+               [ -z "$newbranch" ] &&
+                       die "git checkout: -b needs a branch name"
+               git show-ref --verify --quiet -- "refs/heads/$newbranch" &&
+                       die "git checkout: branch $newbranch already exists"
+               git check-ref-format "heads/$newbranch" ||
+                       die "git checkout: we do not like '$newbranch' as a branch name."
+               ;;
+       -l)
+               newbranch_log=-l
+               ;;
+       --track|--no-track)
+               track="$1"
+               ;;
+       -f)
+               force=1
+               ;;
+       -m)
+               merge=1
+               ;;
+       -q|--quiet)
+               quiet=1
+               v=
+               ;;
+       --)
+               shift
+               break
+               ;;
+       *)
+               usage
+               ;;
+       esac
+       shift
+done
+
+arg="$1"
+rev=$(git rev-parse --verify "$arg" 2>/dev/null)
+if rev=$(git rev-parse --verify "$rev^0" 2>/dev/null)
+then
+       [ -z "$rev" ] && die "unknown flag $arg"
+       new_name="$arg"
+       if git show-ref --verify --quiet -- "refs/heads/$arg"
+       then
+               rev=$(git rev-parse --verify "refs/heads/$arg^0")
+               branch="$arg"
+       fi
+       new="$rev"
+       shift
+elif rev=$(git rev-parse --verify "$rev^{tree}" 2>/dev/null)
+then
+       # checking out selected paths from a tree-ish.
+       new="$rev"
+       new_name="$rev^{tree}"
+       shift
+fi
+[ "$1" = "--" ] && shift
+
+case "$newbranch,$track" in
+,--*)
+       die "git checkout: --track and --no-track require -b"
+esac
+
+case "$force$merge" in
+11)
+       die "git checkout: -f and -m are incompatible"
+esac
+
+# The behaviour of the command with and without explicit path
+# parameters is quite different.
+#
+# Without paths, we are checking out everything in the work tree,
+# possibly switching branches.  This is the traditional behaviour.
+#
+# With paths, we are _never_ switching branch, but checking out
+# the named paths from either index (when no rev is given),
+# or the named tree-ish (when rev is given).
+
+if test "$#" -ge 1
+then
+       hint=
+       if test "$#" -eq 1
+       then
+               hint="
+Did you intend to checkout '$@' which can not be resolved as commit?"
+       fi
+       if test '' != "$newbranch$force$merge"
+       then
+               die "git checkout: updating paths is incompatible with switching branches/forcing$hint"
+       fi
+       if test '' != "$new"
+       then
+               # from a specific tree-ish; note that this is for
+               # rescuing paths and is never meant to remove what
+               # is not in the named tree-ish.
+               git ls-tree --full-name -r "$new" "$@" |
+               git update-index --index-info || exit $?
+       fi
+
+       # Make sure the request is about existing paths.
+       git ls-files --full-name --error-unmatch -- "$@" >/dev/null || exit
+       git ls-files --full-name -- "$@" |
+               (cd_to_toplevel && git checkout-index -f -u --stdin)
+
+       # Run a post-checkout hook -- the HEAD does not change so the
+       # current HEAD is passed in for both args
+       if test -x "$GIT_DIR"/hooks/post-checkout; then
+           "$GIT_DIR"/hooks/post-checkout $old $old 0
+       fi
+
+       exit $?
+else
+       # Make sure we did not fall back on $arg^{tree} codepath
+       # since we are not checking out from an arbitrary tree-ish,
+       # but switching branches.
+       if test '' != "$new"
+       then
+               git rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
+               die "Cannot switch branch to a non-commit."
+       fi
+fi
+
+# We are switching branches and checking out trees, so
+# we *NEED* to be at the toplevel.
+cd_to_toplevel
+
+[ -z "$new" ] && new=$old && new_name="$old_name"
+
+# If we don't have an existing branch that we're switching to,
+# and we don't have a new branch name for the target we
+# are switching to, then we are detaching our HEAD from any
+# branch.  However, if "git checkout HEAD" detaches the HEAD
+# from the current branch, even though that may be logically
+# correct, it feels somewhat funny.  More importantly, we do not
+# want "git checkout" nor "git checkout -f" to detach HEAD.
+
+detached=
+detach_warn=
+
+describe_detached_head () {
+       test -n "$quiet" || {
+               printf >&2 "$1 "
+               GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2" --
+       }
+}
+
+if test -z "$branch$newbranch" && test "$new_name" != "$old_name"
+then
+       detached="$new"
+       if test -n "$oldbranch" && test -z "$quiet"
+       then
+               detach_warn="Note: moving to \"$new_name\" which isn't a local branch
+If you want to create a new branch from this checkout, you may do so
+(now or later) by using -b with the checkout command again. Example:
+  git checkout -b <new_branch_name>"
+       fi
+elif test -z "$oldbranch" && test "$new" != "$old"
+then
+       describe_detached_head 'Previous HEAD position was' "$old"
+fi
+
+if [ "X$old" = X ]
+then
+       if test -z "$quiet"
+       then
+               echo >&2 "warning: You appear to be on a branch yet to be born."
+               echo >&2 "warning: Forcing checkout of $new_name."
+       fi
+       force=1
+fi
+
+if [ "$force" ]
+then
+    git read-tree $v --reset -u $new
+else
+    git update-index --refresh >/dev/null
+    git read-tree $v -m -u --exclude-per-directory=.gitignore $old $new || (
+       case "$merge,$v" in
+       ,*)
+               exit 1 ;;
+       1,)
+               ;; # quiet
+       *)
+               echo >&2 "Falling back to 3-way merge..." ;;
+       esac
+
+       # Match the index to the working tree, and do a three-way.
+       git diff-files --name-only | git update-index --remove --stdin &&
+       work=`git write-tree` &&
+       git read-tree $v --reset -u $new || exit
+
+       eval GITHEAD_$new='${new_name:-${branch:-$new}}' &&
+       eval GITHEAD_$work=local &&
+       export GITHEAD_$new GITHEAD_$work &&
+       git merge-recursive $old -- $new $work
+
+       # Do not register the cleanly merged paths in the index yet.
+       # this is not a real merge before committing, but just carrying
+       # the working tree changes along.
+       unmerged=`git ls-files -u`
+       git read-tree $v --reset $new
+       case "$unmerged" in
+       '')     ;;
+       *)
+               (
+                       z40=0000000000000000000000000000000000000000
+                       echo "$unmerged" |
+                       sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /"
+                       echo "$unmerged"
+               ) | git update-index --index-info
+               ;;
+       esac
+       exit 0
+    )
+    saved_err=$?
+    if test "$saved_err" = 0 && test -z "$quiet"
+    then
+       git diff-index --name-status "$new"
+    fi
+    (exit $saved_err)
+fi
+
+#
+# Switch the HEAD pointer to the new branch if we
+# checked out a branch head, and remove any potential
+# old MERGE_HEAD's (subsequent commits will clearly not
+# be based on them, since we re-set the index)
+#
+if [ "$?" -eq 0 ]; then
+       if [ "$newbranch" ]; then
+               git branch $track $newbranch_log "$newbranch" "$new_name" || exit
+               branch="$newbranch"
+       fi
+       if test -n "$branch"
+       then
+               old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
+               GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from ${old_branch_name:-$old} to $branch" HEAD "refs/heads/$branch"
+               if test -n "$quiet"
+               then
+                       true    # nothing
+               elif test "refs/heads/$branch" = "$oldbranch"
+               then
+                       echo >&2 "Already on branch \"$branch\""
+               else
+                       echo >&2 "Switched to${newbranch:+ a new} branch \"$branch\""
+               fi
+       elif test -n "$detached"
+       then
+               old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
+               git update-ref --no-deref -m "checkout: moving from ${old_branch_name:-$old} to $arg" HEAD "$detached" ||
+                       die "Cannot detach HEAD"
+               if test -n "$detach_warn"
+               then
+                       echo >&2 "$detach_warn"
+               fi
+               describe_detached_head 'HEAD is now at' HEAD
+       fi
+       rm -f "$GIT_DIR/MERGE_HEAD"
+else
+       exit 1
+fi
+
+# Run a post-checkout hook
+if test -x "$GIT_DIR"/hooks/post-checkout; then
+       "$GIT_DIR"/hooks/post-checkout $old $new 1
+fi
diff --git a/copy.c b/copy.c
index c225d1b0ff0a67e637f7200ab5c2a917b550af4f..afc4fbf41405d42d2751ea35ec7a9a32f8df6274 100644 (file)
--- a/copy.c
+++ b/copy.c
@@ -34,3 +34,24 @@ int copy_fd(int ifd, int ofd)
        close(ifd);
        return 0;
 }
+
+int copy_file(const char *dst, const char *src, int mode)
+{
+       int fdi, fdo, status;
+
+       mode = (mode & 0111) ? 0777 : 0666;
+       if ((fdi = open(src, O_RDONLY)) < 0)
+               return fdi;
+       if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) {
+               close(fdi);
+               return fdo;
+       }
+       status = copy_fd(fdi, fdo);
+       if (close(fdo) != 0)
+               return error("%s: write error: %s", dst, strerror(errno));
+
+       if (!status && adjust_shared_perm(dst))
+               return -1;
+
+       return status;
+}
diff --git a/date.c b/date.c
index 8f7050027053a4e2390097e341327b117404c26a..a74ed86422763e7d7e5dccf73530e52551a6929a 100644 (file)
--- a/date.c
+++ b/date.c
@@ -213,9 +213,9 @@ static const struct {
        { "EAST", +10, 0, },    /* Eastern Australian Standard */
        { "EADT", +10, 1, },    /* Eastern Australian Daylight */
        { "GST",  +10, 0, },    /* Guam Standard, USSR Zone 9 */
-       { "NZT",  +11, 0, },    /* New Zealand */
-       { "NZST", +11, 0, },    /* New Zealand Standard */
-       { "NZDT", +11, 1, },    /* New Zealand Daylight */
+       { "NZT",  +12, 0, },    /* New Zealand */
+       { "NZST", +12, 0, },    /* New Zealand Standard */
+       { "NZDT", +12, 1, },    /* New Zealand Daylight */
        { "IDLE", +12, 0, },    /* International Date Line East */
 };
 
index 03eaa7cef35dfaec0a8f61b2b96892e5604ed731..94b150e830c5d9bd828a5e050166eed27e0b0e62 100644 (file)
@@ -737,7 +737,8 @@ int run_diff_index(struct rev_info *revs, int cached)
        opts.unpack_data = revs;
 
        init_tree_desc(&t, tree->buffer, tree->size);
-       unpack_trees(1, &t, &opts);
+       if (unpack_trees(1, &t, &opts))
+               exit(128);
 
        diffcore_std(&revs->diffopt);
        diff_flush(&revs->diffopt);
@@ -789,6 +790,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
        opts.unpack_data = &revs;
 
        init_tree_desc(&t, tree->buffer, tree->size);
-       unpack_trees(1, &t, &opts);
+       if (unpack_trees(1, &t, &opts))
+               exit(128);
        return 0;
 }
diff --git a/diff.c b/diff.c
index f08e663b865ca545b871b153160104d4a3eb329e..ad16164232f22207c2aa52c3b8c6ed4dcc3067b1 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -271,8 +271,8 @@ static void print_line_count(int count)
        }
 }
 
-static void copy_file(int prefix, const char *data, int size,
-               const char *set, const char *reset)
+static void copy_file_with_prefix(int prefix, const char *data, int size,
+                                 const char *set, const char *reset)
 {
        int ch, nl_just_seen = 1;
        while (0 < size--) {
@@ -330,9 +330,9 @@ static void emit_rewrite_diff(const char *name_a,
        print_line_count(lc_b);
        printf(" @@%s\n", reset);
        if (lc_a)
-               copy_file('-', one->data, one->size, old, reset);
+               copy_file_with_prefix('-', one->data, one->size, old, reset);
        if (lc_b)
-               copy_file('+', two->data, two->size, new, reset);
+               copy_file_with_prefix('+', two->data, two->size, new, reset);
 }
 
 static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
@@ -979,6 +979,90 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options)
        }
 }
 
+struct diffstat_dir {
+       struct diffstat_file **files;
+       int nr, percent, cumulative;
+};
+
+static long gather_dirstat(struct diffstat_dir *dir, unsigned long changed, const char *base, int baselen)
+{
+       unsigned long this_dir = 0;
+       unsigned int sources = 0;
+
+       while (dir->nr) {
+               struct diffstat_file *f = *dir->files;
+               int namelen = strlen(f->name);
+               unsigned long this;
+               char *slash;
+
+               if (namelen < baselen)
+                       break;
+               if (memcmp(f->name, base, baselen))
+                       break;
+               slash = strchr(f->name + baselen, '/');
+               if (slash) {
+                       int newbaselen = slash + 1 - f->name;
+                       this = gather_dirstat(dir, changed, f->name, newbaselen);
+                       sources++;
+               } else {
+                       if (f->is_unmerged || f->is_binary)
+                               this = 0;
+                       else
+                               this = f->added + f->deleted;
+                       dir->files++;
+                       dir->nr--;
+                       sources += 2;
+               }
+               this_dir += this;
+       }
+
+       /*
+        * We don't report dirstat's for
+        *  - the top level
+        *  - or cases where everything came from a single directory
+        *    under this directory (sources == 1).
+        */
+       if (baselen && sources != 1) {
+               int permille = this_dir * 1000 / changed;
+               if (permille) {
+                       int percent = permille / 10;
+                       if (percent >= dir->percent) {
+                               printf("%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base);
+                               if (!dir->cumulative)
+                                       return 0;
+                       }
+               }
+       }
+       return this_dir;
+}
+
+static void show_dirstat(struct diffstat_t *data, struct diff_options *options)
+{
+       int i;
+       unsigned long changed;
+       struct diffstat_dir dir;
+
+       /* Calculate total changes */
+       changed = 0;
+       for (i = 0; i < data->nr; i++) {
+               if (data->files[i]->is_binary || data->files[i]->is_unmerged)
+                       continue;
+               changed += data->files[i]->added;
+               changed += data->files[i]->deleted;
+       }
+
+       /* This can happen even with many files, if everything was renames */
+       if (!changed)
+               return;
+
+       /* Show all directories with more than x% of the changes */
+       dir.files = data->files;
+       dir.nr = data->nr;
+       dir.percent = options->dirstat_percent;
+       dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE;
+       gather_dirstat(&dir, changed, "", 0);
+}
+
 static void free_diffstat_info(struct diffstat_t *diffstat)
 {
        int i;
@@ -1396,6 +1480,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
 }
 
 static void builtin_checkdiff(const char *name_a, const char *name_b,
+                             const char *attr_path,
                             struct diff_filespec *one,
                             struct diff_filespec *two, struct diff_options *o)
 {
@@ -1410,7 +1495,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
        data.filename = name_b ? name_b : name_a;
        data.lineno = 0;
        data.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
-       data.ws_rule = whitespace_rule(data.filename);
+       data.ws_rule = whitespace_rule(attr_path);
 
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
@@ -1835,6 +1920,9 @@ static const char *external_diff_attr(const char *name)
 {
        struct git_attr_check attr_diff_check;
 
+       if (!name)
+               return NULL;
+
        setup_diff_attr_check(&attr_diff_check);
        if (!git_checkattr(name, 1, &attr_diff_check)) {
                const char *value = attr_diff_check.value;
@@ -1854,6 +1942,7 @@ static const char *external_diff_attr(const char *name)
 static void run_diff_cmd(const char *pgm,
                         const char *name,
                         const char *other,
+                        const char *attr_path,
                         struct diff_filespec *one,
                         struct diff_filespec *two,
                         const char *xfrm_msg,
@@ -1863,7 +1952,7 @@ static void run_diff_cmd(const char *pgm,
        if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
                pgm = NULL;
        else {
-               const char *cmd = external_diff_attr(name);
+               const char *cmd = external_diff_attr(attr_path);
                if (cmd)
                        pgm = cmd;
        }
@@ -1904,6 +1993,15 @@ static int similarity_index(struct diff_filepair *p)
        return p->score * 100 / MAX_SCORE;
 }
 
+static void strip_prefix(int prefix_length, const char **namep, const char **otherp)
+{
+       /* Strip the prefix but do not molest /dev/null and absolute paths */
+       if (*namep && **namep != '/')
+               *namep += prefix_length;
+       if (*otherp && **otherp != '/')
+               *otherp += prefix_length;
+}
+
 static void run_diff(struct diff_filepair *p, struct diff_options *o)
 {
        const char *pgm = external_diff();
@@ -1913,16 +2011,21 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
        struct diff_filespec *two = p->two;
        const char *name;
        const char *other;
+       const char *attr_path;
        int complete_rewrite = 0;
 
+       name  = p->one->path;
+       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+       attr_path = name;
+       if (o->prefix_length)
+               strip_prefix(o->prefix_length, &name, &other);
 
        if (DIFF_PAIR_UNMERGED(p)) {
-               run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, o, 0);
+               run_diff_cmd(pgm, name, NULL, attr_path,
+                            NULL, NULL, NULL, o, 0);
                return;
        }
 
-       name  = p->one->path;
-       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
        diff_fill_sha1_info(one);
        diff_fill_sha1_info(two);
 
@@ -1985,15 +2088,17 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
                 * needs to be split into deletion and creation.
                 */
                struct diff_filespec *null = alloc_filespec(two->path);
-               run_diff_cmd(NULL, name, other, one, null, xfrm_msg, o, 0);
+               run_diff_cmd(NULL, name, other, attr_path,
+                            one, null, xfrm_msg, o, 0);
                free(null);
                null = alloc_filespec(one->path);
-               run_diff_cmd(NULL, name, other, null, two, xfrm_msg, o, 0);
+               run_diff_cmd(NULL, name, other, attr_path,
+                            null, two, xfrm_msg, o, 0);
                free(null);
        }
        else
-               run_diff_cmd(pgm, name, other, one, two, xfrm_msg, o,
-                            complete_rewrite);
+               run_diff_cmd(pgm, name, other, attr_path,
+                            one, two, xfrm_msg, o, complete_rewrite);
 
        strbuf_release(&msg);
 }
@@ -2014,6 +2119,9 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
        name = p->one->path;
        other = (strcmp(name, p->two->path) ? p->two->path : NULL);
 
+       if (o->prefix_length)
+               strip_prefix(o->prefix_length, &name, &other);
+
        diff_fill_sha1_info(p->one);
        diff_fill_sha1_info(p->two);
 
@@ -2026,6 +2134,7 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
 {
        const char *name;
        const char *other;
+       const char *attr_path;
 
        if (DIFF_PAIR_UNMERGED(p)) {
                /* unmerged */
@@ -2034,11 +2143,15 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
 
        name = p->one->path;
        other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+       attr_path = other ? other : name;
+
+       if (o->prefix_length)
+               strip_prefix(o->prefix_length, &name, &other);
 
        diff_fill_sha1_info(p->one);
        diff_fill_sha1_info(p->two);
 
-       builtin_checkdiff(name, other, p->one, p->two, o);
+       builtin_checkdiff(name, other, attr_path, p->one, p->two, o);
 }
 
 void diff_setup(struct diff_options *options)
@@ -2047,6 +2160,7 @@ void diff_setup(struct diff_options *options)
        options->line_termination = '\n';
        options->break_opt = -1;
        options->rename_limit = -1;
+       options->dirstat_percent = 3;
        options->context = 3;
        options->msg_sep = "";
 
@@ -2080,6 +2194,13 @@ int diff_setup_done(struct diff_options *options)
        if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
                options->detect_rename = DIFF_DETECT_COPY;
 
+       if (!DIFF_OPT_TST(options, RELATIVE_NAME))
+               options->prefix = NULL;
+       if (options->prefix)
+               options->prefix_length = strlen(options->prefix);
+       else
+               options->prefix_length = 0;
+
        if (options->output_format & (DIFF_FORMAT_NAME |
                                      DIFF_FORMAT_NAME_STATUS |
                                      DIFF_FORMAT_CHECKDIFF |
@@ -2088,6 +2209,7 @@ int diff_setup_done(struct diff_options *options)
                                            DIFF_FORMAT_NUMSTAT |
                                            DIFF_FORMAT_DIFFSTAT |
                                            DIFF_FORMAT_SHORTSTAT |
+                                           DIFF_FORMAT_DIRSTAT |
                                            DIFF_FORMAT_SUMMARY |
                                            DIFF_FORMAT_PATCH);
 
@@ -2099,6 +2221,7 @@ int diff_setup_done(struct diff_options *options)
                                      DIFF_FORMAT_NUMSTAT |
                                      DIFF_FORMAT_DIFFSTAT |
                                      DIFF_FORMAT_SHORTSTAT |
+                                     DIFF_FORMAT_DIRSTAT |
                                      DIFF_FORMAT_SUMMARY |
                                      DIFF_FORMAT_CHECKDIFF))
                DIFF_OPT_SET(options, RECURSIVE);
@@ -2209,6 +2332,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                options->output_format |= DIFF_FORMAT_NUMSTAT;
        else if (!strcmp(arg, "--shortstat"))
                options->output_format |= DIFF_FORMAT_SHORTSTAT;
+       else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent))
+               options->output_format |= DIFF_FORMAT_DIRSTAT;
+       else if (!strcmp(arg, "--cumulative"))
+               options->output_format |= DIFF_FORMAT_CUMULATIVE;
        else if (!strcmp(arg, "--check"))
                options->output_format |= DIFF_FORMAT_CHECKDIFF;
        else if (!strcmp(arg, "--summary"))
@@ -2268,6 +2395,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        }
        else if (!strcmp(arg, "--no-renames"))
                options->detect_rename = 0;
+       else if (!strcmp(arg, "--relative"))
+               DIFF_OPT_SET(options, RELATIVE_NAME);
+       else if (!prefixcmp(arg, "--relative=")) {
+               DIFF_OPT_SET(options, RELATIVE_NAME);
+               options->prefix = arg + 11;
+       }
 
        /* xdiff options */
        else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
@@ -2479,12 +2612,20 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt)
                printf("%c%c", p->status, inter_name_termination);
        }
 
-       if (p->status == DIFF_STATUS_COPIED || p->status == DIFF_STATUS_RENAMED) {
-               write_name_quoted(p->one->path, stdout, inter_name_termination);
-               write_name_quoted(p->two->path, stdout, line_termination);
+       if (p->status == DIFF_STATUS_COPIED ||
+           p->status == DIFF_STATUS_RENAMED) {
+               const char *name_a, *name_b;
+               name_a = p->one->path;
+               name_b = p->two->path;
+               strip_prefix(opt->prefix_length, &name_a, &name_b);
+               write_name_quoted(name_a, stdout, inter_name_termination);
+               write_name_quoted(name_b, stdout, line_termination);
        } else {
-               const char *path = p->one->mode ? p->one->path : p->two->path;
-               write_name_quoted(path, stdout, line_termination);
+               const char *name_a, *name_b;
+               name_a = p->one->mode ? p->one->path : p->two->path;
+               name_b = NULL;
+               strip_prefix(opt->prefix_length, &name_a, &name_b);
+               write_name_quoted(name_a, stdout, line_termination);
        }
 }
 
@@ -2681,8 +2822,13 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt)
                diff_flush_checkdiff(p, opt);
        else if (fmt & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS))
                diff_flush_raw(p, opt);
-       else if (fmt & DIFF_FORMAT_NAME)
-               write_name_quoted(p->two->path, stdout, opt->line_termination);
+       else if (fmt & DIFF_FORMAT_NAME) {
+               const char *name_a, *name_b;
+               name_a = p->two->path;
+               name_b = NULL;
+               strip_prefix(opt->prefix_length, &name_a, &name_b);
+               write_name_quoted(name_a, stdout, opt->line_termination);
+       }
 }
 
 static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs)
@@ -2927,7 +3073,7 @@ void diff_flush(struct diff_options *options)
                separator++;
        }
 
-       if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) {
+       if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIRSTAT)) {
                struct diffstat_t diffstat;
 
                memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -2937,6 +3083,8 @@ void diff_flush(struct diff_options *options)
                        if (check_pair_status(p))
                                diff_flush_stat(p, options, &diffstat);
                }
+               if (output_format & DIFF_FORMAT_DIRSTAT)
+                       show_dirstat(&diffstat, options);
                if (output_format & DIFF_FORMAT_NUMSTAT)
                        show_numstat(&diffstat, options);
                if (output_format & DIFF_FORMAT_DIFFSTAT)
@@ -3168,6 +3316,11 @@ void diff_addremove(struct diff_options *options,
 
        if (!path) path = "";
        sprintf(concatpath, "%s%s", base, path);
+
+       if (options->prefix &&
+           strncmp(concatpath, options->prefix, options->prefix_length))
+               return;
+
        one = alloc_filespec(concatpath);
        two = alloc_filespec(concatpath);
 
@@ -3197,6 +3350,11 @@ void diff_change(struct diff_options *options,
        }
        if (!path) path = "";
        sprintf(concatpath, "%s%s", base, path);
+
+       if (options->prefix &&
+           strncmp(concatpath, options->prefix, options->prefix_length))
+               return;
+
        one = alloc_filespec(concatpath);
        two = alloc_filespec(concatpath);
        fill_filespec(one, old_sha1, old_mode);
@@ -3211,6 +3369,11 @@ void diff_unmerge(struct diff_options *options,
                  unsigned mode, const unsigned char *sha1)
 {
        struct diff_filespec *one, *two;
+
+       if (options->prefix &&
+           strncmp(path, options->prefix, options->prefix_length))
+               return;
+
        one = alloc_filespec(path);
        two = alloc_filespec(path);
        fill_filespec(one, sha1, mode);
diff --git a/diff.h b/diff.h
index 8e73f07d7e35cd414c0f5cf347516e39921a7dd1..9a652e7f38e3c1aeb7c7eaf840dea7bcea285311 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -30,6 +30,8 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_FORMAT_SUMMARY    0x0008
 #define DIFF_FORMAT_PATCH      0x0010
 #define DIFF_FORMAT_SHORTSTAT  0x0020
+#define DIFF_FORMAT_DIRSTAT    0x0040
+#define DIFF_FORMAT_CUMULATIVE 0x0080
 
 /* These override all above */
 #define DIFF_FORMAT_NAME       0x0100
@@ -60,6 +62,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_OPT_EXIT_WITH_STATUS    (1 << 14)
 #define DIFF_OPT_REVERSE_DIFF        (1 << 15)
 #define DIFF_OPT_CHECK_FAILED        (1 << 16)
+#define DIFF_OPT_RELATIVE_NAME       (1 << 17)
 #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)
@@ -80,8 +83,11 @@ struct diff_options {
        int pickaxe_opts;
        int rename_score;
        int rename_limit;
+       int dirstat_percent;
        int setup;
        int abbrev;
+       const char *prefix;
+       int prefix_length;
        const char *msg_sep;
        const char *stat_sep;
        long xdl_opts;
index 3527f1663f3f948085ab56a81609ed9764b4e6e9..6739a3f41745fe1c01dc767e09af8746d6f2815f 100644 (file)
@@ -37,6 +37,7 @@ const char *excludes_file;
 int auto_crlf = 0;     /* 1: both ways, -1: only when adding git objects */
 enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
 unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
+enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
 
 /* This is set by setup_git_dir_gently() and/or git_default_config() */
 char *git_work_tree_cfg;
index 74715edf0b197376250e5d111407a36a6c237678..2c32d0b9ebcdb9bf43124721e109cfac72149990 100755 (executable)
@@ -67,16 +67,18 @@ bisect_start() {
        die "Bad HEAD - I need a HEAD"
        case "$head" in
        refs/heads/bisect)
-               if [ -s "$GIT_DIR/head-name" ]; then
-                   branch=`cat "$GIT_DIR/head-name"`
+               if [ -s "$GIT_DIR/BISECT_START" ]; then
+                   branch=`cat "$GIT_DIR/BISECT_START"`
                else
                    branch=master
                fi
                git checkout $branch || exit
                ;;
        refs/heads/*|$_x40)
+               # This error message should only be triggered by cogito usage,
+               # and cogito users should understand it relates to cg-seek.
                [ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree"
-               echo "${head#refs/heads/}" >"$GIT_DIR/head-name"
+               echo "${head#refs/heads/}" >"$GIT_DIR/BISECT_START"
                ;;
        *)
                die "Bad HEAD - strange symbolic ref"
@@ -353,8 +355,8 @@ bisect_reset() {
                return
        }
        case "$#" in
-       0) if [ -s "$GIT_DIR/head-name" ]; then
-              branch=`cat "$GIT_DIR/head-name"`
+       0) if [ -s "$GIT_DIR/BISECT_START" ]; then
+              branch=`cat "$GIT_DIR/BISECT_START"`
           else
               branch=master
           fi ;;
@@ -365,7 +367,9 @@ bisect_reset() {
            usage ;;
        esac
        if git checkout "$branch"; then
+               # Cleanup head-name if it got left by an old version of git-bisect
                rm -f "$GIT_DIR/head-name"
+               rm -f "$GIT_DIR/BISECT_START"
                bisect_clean_state
        fi
 }
diff --git a/git-checkout.sh b/git-checkout.sh
deleted file mode 100755 (executable)
index bd74d70..0000000
+++ /dev/null
@@ -1,299 +0,0 @@
-#!/bin/sh
-
-OPTIONS_KEEPDASHDASH=t
-OPTIONS_SPEC="\
-git-checkout [options] [<branch>] [<paths>...]
---
-b=          create a new branch started at <branch>
-l           create the new branch's reflog
-track       arrange that the new branch tracks the remote branch
-f           proceed even if the index or working tree is not HEAD
-m           merge local modifications into the new branch
-q,quiet     be quiet
-"
-SUBDIRECTORY_OK=Sometimes
-. git-sh-setup
-require_work_tree
-
-old_name=HEAD
-old=$(git rev-parse --verify $old_name 2>/dev/null)
-oldbranch=$(git symbolic-ref $old_name 2>/dev/null)
-new=
-new_name=
-force=
-branch=
-track=
-newbranch=
-newbranch_log=
-merge=
-quiet=
-v=-v
-LF='
-'
-
-while test $# != 0; do
-       case "$1" in
-       -b)
-               shift
-               newbranch="$1"
-               [ -z "$newbranch" ] &&
-                       die "git checkout: -b needs a branch name"
-               git show-ref --verify --quiet -- "refs/heads/$newbranch" &&
-                       die "git checkout: branch $newbranch already exists"
-               git check-ref-format "heads/$newbranch" ||
-                       die "git checkout: we do not like '$newbranch' as a branch name."
-               ;;
-       -l)
-               newbranch_log=-l
-               ;;
-       --track|--no-track)
-               track="$1"
-               ;;
-       -f)
-               force=1
-               ;;
-       -m)
-               merge=1
-               ;;
-       -q|--quiet)
-               quiet=1
-               v=
-               ;;
-       --)
-               shift
-               break
-               ;;
-       *)
-               usage
-               ;;
-       esac
-       shift
-done
-
-arg="$1"
-rev=$(git rev-parse --verify "$arg" 2>/dev/null)
-if rev=$(git rev-parse --verify "$rev^0" 2>/dev/null)
-then
-       [ -z "$rev" ] && die "unknown flag $arg"
-       new_name="$arg"
-       if git show-ref --verify --quiet -- "refs/heads/$arg"
-       then
-               rev=$(git rev-parse --verify "refs/heads/$arg^0")
-               branch="$arg"
-       fi
-       new="$rev"
-       shift
-elif rev=$(git rev-parse --verify "$rev^{tree}" 2>/dev/null)
-then
-       # checking out selected paths from a tree-ish.
-       new="$rev"
-       new_name="$rev^{tree}"
-       shift
-fi
-[ "$1" = "--" ] && shift
-
-case "$newbranch,$track" in
-,--*)
-       die "git checkout: --track and --no-track require -b"
-esac
-
-case "$force$merge" in
-11)
-       die "git checkout: -f and -m are incompatible"
-esac
-
-# The behaviour of the command with and without explicit path
-# parameters is quite different.
-#
-# Without paths, we are checking out everything in the work tree,
-# possibly switching branches.  This is the traditional behaviour.
-#
-# With paths, we are _never_ switching branch, but checking out
-# the named paths from either index (when no rev is given),
-# or the named tree-ish (when rev is given).
-
-if test "$#" -ge 1
-then
-       hint=
-       if test "$#" -eq 1
-       then
-               hint="
-Did you intend to checkout '$@' which can not be resolved as commit?"
-       fi
-       if test '' != "$newbranch$force$merge"
-       then
-               die "git checkout: updating paths is incompatible with switching branches/forcing$hint"
-       fi
-       if test '' != "$new"
-       then
-               # from a specific tree-ish; note that this is for
-               # rescuing paths and is never meant to remove what
-               # is not in the named tree-ish.
-               git ls-tree --full-name -r "$new" "$@" |
-               git update-index --index-info || exit $?
-       fi
-
-       # Make sure the request is about existing paths.
-       git ls-files --full-name --error-unmatch -- "$@" >/dev/null || exit
-       git ls-files --full-name -- "$@" |
-               (cd_to_toplevel && git checkout-index -f -u --stdin)
-
-       # Run a post-checkout hook -- the HEAD does not change so the
-       # current HEAD is passed in for both args
-       if test -x "$GIT_DIR"/hooks/post-checkout; then
-           "$GIT_DIR"/hooks/post-checkout $old $old 0
-       fi
-
-       exit $?
-else
-       # Make sure we did not fall back on $arg^{tree} codepath
-       # since we are not checking out from an arbitrary tree-ish,
-       # but switching branches.
-       if test '' != "$new"
-       then
-               git rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
-               die "Cannot switch branch to a non-commit."
-       fi
-fi
-
-# We are switching branches and checking out trees, so
-# we *NEED* to be at the toplevel.
-cd_to_toplevel
-
-[ -z "$new" ] && new=$old && new_name="$old_name"
-
-# If we don't have an existing branch that we're switching to,
-# and we don't have a new branch name for the target we
-# are switching to, then we are detaching our HEAD from any
-# branch.  However, if "git checkout HEAD" detaches the HEAD
-# from the current branch, even though that may be logically
-# correct, it feels somewhat funny.  More importantly, we do not
-# want "git checkout" nor "git checkout -f" to detach HEAD.
-
-detached=
-detach_warn=
-
-describe_detached_head () {
-       test -n "$quiet" || {
-               printf >&2 "$1 "
-               GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2" --
-       }
-}
-
-if test -z "$branch$newbranch" && test "$new_name" != "$old_name"
-then
-       detached="$new"
-       if test -n "$oldbranch" && test -z "$quiet"
-       then
-               detach_warn="Note: moving to \"$new_name\" which isn't a local branch
-If you want to create a new branch from this checkout, you may do so
-(now or later) by using -b with the checkout command again. Example:
-  git checkout -b <new_branch_name>"
-       fi
-elif test -z "$oldbranch" && test "$new" != "$old"
-then
-       describe_detached_head 'Previous HEAD position was' "$old"
-fi
-
-if [ "X$old" = X ]
-then
-       if test -z "$quiet"
-       then
-               echo >&2 "warning: You appear to be on a branch yet to be born."
-               echo >&2 "warning: Forcing checkout of $new_name."
-       fi
-       force=1
-fi
-
-if [ "$force" ]
-then
-    git read-tree $v --reset -u $new
-else
-    git update-index --refresh >/dev/null
-    merge_error=$(git read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || (
-       case "$merge" in
-       '')
-               echo >&2 "$merge_error"
-               exit 1 ;;
-       esac
-
-       # Match the index to the working tree, and do a three-way.
-       git diff-files --name-only | git update-index --remove --stdin &&
-       work=`git write-tree` &&
-       git read-tree $v --reset -u $new || exit
-
-       eval GITHEAD_$new='${new_name:-${branch:-$new}}' &&
-       eval GITHEAD_$work=local &&
-       export GITHEAD_$new GITHEAD_$work &&
-       git merge-recursive $old -- $new $work
-
-       # Do not register the cleanly merged paths in the index yet.
-       # this is not a real merge before committing, but just carrying
-       # the working tree changes along.
-       unmerged=`git ls-files -u`
-       git read-tree $v --reset $new
-       case "$unmerged" in
-       '')     ;;
-       *)
-               (
-                       z40=0000000000000000000000000000000000000000
-                       echo "$unmerged" |
-                       sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /"
-                       echo "$unmerged"
-               ) | git update-index --index-info
-               ;;
-       esac
-       exit 0
-    )
-    saved_err=$?
-    if test "$saved_err" = 0 && test -z "$quiet"
-    then
-       git diff-index --name-status "$new"
-    fi
-    (exit $saved_err)
-fi
-
-#
-# Switch the HEAD pointer to the new branch if we
-# checked out a branch head, and remove any potential
-# old MERGE_HEAD's (subsequent commits will clearly not
-# be based on them, since we re-set the index)
-#
-if [ "$?" -eq 0 ]; then
-       if [ "$newbranch" ]; then
-               git branch $track $newbranch_log "$newbranch" "$new_name" || exit
-               branch="$newbranch"
-       fi
-       if test -n "$branch"
-       then
-               old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
-               GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from ${old_branch_name:-$old} to $branch" HEAD "refs/heads/$branch"
-               if test -n "$quiet"
-               then
-                       true    # nothing
-               elif test "refs/heads/$branch" = "$oldbranch"
-               then
-                       echo >&2 "Already on branch \"$branch\""
-               else
-                       echo >&2 "Switched to${newbranch:+ a new} branch \"$branch\""
-               fi
-       elif test -n "$detached"
-       then
-               old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
-               git update-ref --no-deref -m "checkout: moving from ${old_branch_name:-$old} to $arg" HEAD "$detached" ||
-                       die "Cannot detach HEAD"
-               if test -n "$detach_warn"
-               then
-                       echo >&2 "$detach_warn"
-               fi
-               describe_detached_head 'HEAD is now at' HEAD
-       fi
-       rm -f "$GIT_DIR/MERGE_HEAD"
-else
-       exit 1
-fi
-
-# Run a post-checkout hook
-if test -x "$GIT_DIR"/hooks/post-checkout; then
-       "$GIT_DIR"/hooks/post-checkout $old $new 1
-fi
index 081d7550a7dbc91d8360822a4da3ca51c66a33a2..01e0a46ba56faa8a17ed8710346b18dd215899c7 100644 (file)
@@ -92,8 +92,12 @@ ifndef V
        REMOVE_F1 = && echo '   ' REMOVE `basename "$$dst"` && $(RM_RF) "$$dst"
 endif
 
-TCL_PATH   ?= tclsh
 TCLTK_PATH ?= wish
+ifeq (./,$(dir $(TCLTK_PATH)))
+       TCL_PATH ?= $(subst wish,tclsh,$(TCLTK_PATH))
+else
+       TCL_PATH ?= $(dir $(TCLTK_PATH))$(notdir $(subst wish,tclsh,$(TCLTK_PATH)))
+endif
 
 ifeq ($(uname_S),Darwin)
        TKFRAMEWORK = /Library/Frameworks/Tk.framework/Resources/Wish.app
@@ -127,7 +131,17 @@ GITGUI_MACOSXAPP :=
 
 ifeq ($(uname_O),Cygwin)
        GITGUI_SCRIPT := `cygpath --windows --absolute "$(GITGUI_SCRIPT)"`
-       gg_libdir_sed_in := $(shell cygpath --windows --absolute "$(gg_libdir)")
+
+       # Is this a Cygwin Tcl/Tk binary?  If so it knows how to do
+       # POSIX path translation just like cygpath does and we must
+       # keep libdir in POSIX format so Cygwin packages of git-gui
+       # work no matter where the user installs them.
+       #
+       ifeq ($(shell echo 'puts [file normalize /]' | '$(TCL_PATH_SQ)'),$(shell cygpath --mixed --absolute /))
+               gg_libdir_sed_in := $(gg_libdir)
+       else
+               gg_libdir_sed_in := $(shell cygpath --windows --absolute "$(gg_libdir)")
+       endif
 else
        ifeq ($(exedir),$(gg_libdir))
                GITGUI_RELATIVE := 1
index 5d65272e26dd08fc3abb8bf8cdbf57065da53bcc..238a2393ff7811675003e0e846f3d9409fb04e0c 100755 (executable)
@@ -663,7 +663,7 @@ if {![regsub {^git version } $_git_version {} _git_version]} {
 }
 
 set _real_git_version $_git_version
-regsub -- {-dirty$} $_git_version {} _git_version
+regsub -- {[\-\.]dirty$} $_git_version {} _git_version
 regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
 regsub {\.rc[0-9]+$} $_git_version {} _git_version
 regsub {\.GIT$} $_git_version {} _git_version
index 86faf24cc8788701983f4ff49dc407ecce6d3148..0adcf9d958b76b5a52386effe6afbca95f0abaf9 100644 (file)
@@ -11,6 +11,7 @@ field w_quit      ; # Quit button
 field o_cons      ; # Console object (if active)
 field w_types     ; # List of type buttons in clone
 field w_recentlist ; # Listbox containing recent repositories
+field w_localpath  ; # Entry widget bound to local_path
 
 field done              0 ; # Finished picking the repository?
 field local_path       {} ; # Where this repository is locally
@@ -385,6 +386,7 @@ method _do_new {} {
        button $w_body.where.b \
                -text [mc "Browse"] \
                -command [cb _new_local_path]
+       set w_localpath $w_body.where.t
 
        pack $w_body.where.b -side right
        pack $w_body.where.l -side left
@@ -416,6 +418,7 @@ method _new_local_path {} {
                return
        }
        set local_path $p
+       $w_localpath icursor end
 }
 
 method _do_new2 {} {
@@ -481,6 +484,7 @@ method _do_clone {} {
                -text [mc "Browse"] \
                -command [cb _new_local_path]
        grid $args.where_l $args.where_t $args.where_b -sticky ew
+       set w_localpath $args.where_t
 
        label $args.type_l -text [mc "Clone Type:"]
        frame $args.type_f
index 0fdd7531dac07e68315cebc2efa87e99947821b6..08a24622c7ff199399805ef34f0396f6cb81613c 100644 (file)
@@ -1,6 +1,14 @@
 # git-gui branch (create/delete) support
 # Copyright (C) 2006, 2007 Shawn Pearce
 
+proc _error_parent {} {
+       set p [grab current .]
+       if {$p eq {}} {
+               return .
+       }
+       return $p
+}
+
 proc error_popup {msg} {
        set title [appname]
        if {[reponame] ne {}} {
@@ -11,8 +19,8 @@ proc error_popup {msg} {
                -type ok \
                -title [append "$title: " [mc "error"]] \
                -message $msg]
-       if {[winfo ismapped .]} {
-               lappend cmd -parent .
+       if {[winfo ismapped [_error_parent]]} {
+               lappend cmd -parent [_error_parent]
        }
        eval $cmd
 }
@@ -27,13 +35,13 @@ proc warn_popup {msg} {
                -type ok \
                -title [append "$title: " [mc "warning"]] \
                -message $msg]
-       if {[winfo ismapped .]} {
-               lappend cmd -parent .
+       if {[winfo ismapped [_error_parent]]} {
+               lappend cmd -parent [_error_parent]
        }
        eval $cmd
 }
 
-proc info_popup {msg {parent .}} {
+proc info_popup {msg} {
        set title [appname]
        if {[reponame] ne {}} {
                append title " ([reponame])"
@@ -56,8 +64,8 @@ proc ask_popup {msg} {
                -type yesno \
                -title $title \
                -message $msg]
-       if {[winfo ismapped .]} {
-               lappend cmd -parent .
+       if {[winfo ismapped [_error_parent]]} {
+               lappend cmd -parent [_error_parent]
        }
        eval $cmd
 }
index 46da0f4ca2bfc2b30e4da8eb551565a06c876fb1..3ce32b5f211bd20f1d154c860eb5bf7f9005ca3c 100755 (executable)
@@ -174,6 +174,7 @@ fi
 
 merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
 test true = "$rebase" &&
-       exec git-rebase --onto $merge_head ${oldremoteref:-$merge_head}
+       exec git-rebase $strategy_args --onto $merge_head \
+       ${oldremoteref:-$merge_head}
 exec git-merge $no_summary $no_commit $squash $no_ff $strategy_args \
        "$merge_name" HEAD $merge_head
diff --git a/git.c b/git.c
index 0cb86884d738a8314f164c82e4cc619a9998f3db..9cca81a60e6d4f93cf3132b76fd8f147a6a5d98f 100644 (file)
--- a/git.c
+++ b/git.c
@@ -87,19 +87,6 @@ static int handle_options(const char*** argv, int* argc, int* envchanged)
        return handled;
 }
 
-static const char *alias_command;
-static char *alias_string;
-
-static int git_alias_config(const char *var, const char *value)
-{
-       if (!prefixcmp(var, "alias.") && !strcmp(var + 6, alias_command)) {
-               if (!value)
-                       return config_error_nonbool(var);
-               alias_string = xstrdup(value);
-       }
-       return 0;
-}
-
 static int split_cmdline(char *cmdline, const char ***argv)
 {
        int src, dst, count = 0, size = 16;
@@ -159,11 +146,13 @@ static int handle_alias(int *argcp, const char ***argv)
        const char *subdir;
        int count, option_count;
        const char** new_argv;
+       const char *alias_command;
+       char *alias_string;
 
        subdir = setup_git_directory_gently(&nongit);
 
        alias_command = (*argv)[0];
-       git_config(git_alias_config);
+       alias_string = alias_lookup(alias_command);
        if (alias_string) {
                if (alias_string[0] == '!') {
                        if (*argcp > 1) {
@@ -289,6 +278,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "branch", cmd_branch, RUN_SETUP },
                { "bundle", cmd_bundle },
                { "cat-file", cmd_cat_file, RUN_SETUP },
+               { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
                { "checkout-index", cmd_checkout_index,
                        RUN_SETUP | NEED_WORK_TREE},
                { "check-ref-format", cmd_check_ref_format },
@@ -332,6 +322,8 @@ static void handle_internal_command(int argc, const char **argv)
                { "merge-base", cmd_merge_base, RUN_SETUP },
                { "merge-file", cmd_merge_file },
                { "merge-ours", cmd_merge_ours, RUN_SETUP },
+               { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+               { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
                { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
                { "name-rev", cmd_name_rev, RUN_SETUP },
                { "pack-objects", cmd_pack_objects, RUN_SETUP },
index 326e27cf88b1158313df704e5fb5c4f8bbd50a01..fc95e2ca855fed2998b97733f9458d4ed9857cf2 100755 (executable)
@@ -848,32 +848,73 @@ sub project_in_list {
 ## ----------------------------------------------------------------------
 ## HTML aware string manipulation
 
+# Try to chop given string on a word boundary between position
+# $len and $len+$add_len. If there is no word boundary there,
+# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
+# (marking chopped part) would be longer than given string.
 sub chop_str {
        my $str = shift;
        my $len = shift;
        my $add_len = shift || 10;
+       my $where = shift || 'right'; # 'left' | 'center' | 'right'
 
        # allow only $len chars, but don't cut a word if it would fit in $add_len
        # if it doesn't fit, cut it if it's still longer than the dots we would add
-       $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/;
-       my $body = $1;
-       my $tail = $2;
-       if (length($tail) > 4) {
-               $tail = " ...";
-               $body =~ s/&[^;]*$//; # remove chopped character entities
+       # remove chopped character entities entirely
+
+       # when chopping in the middle, distribute $len into left and right part
+       # return early if chopping wouldn't make string shorter
+       if ($where eq 'center') {
+               return $str if ($len + 5 >= length($str)); # filler is length 5
+               $len = int($len/2);
+       } else {
+               return $str if ($len + 4 >= length($str)); # filler is length 4
+       }
+
+       # regexps: ending and beginning with word part up to $add_len
+       my $endre = qr/.{$len}\w{0,$add_len}/;
+       my $begre = qr/\w{0,$add_len}.{$len}/;
+
+       if ($where eq 'left') {
+               $str =~ m/^(.*?)($begre)$/;
+               my ($lead, $body) = ($1, $2);
+               if (length($lead) > 4) {
+                       $body =~ s/^[^;]*;// if ($lead =~ m/&[^;]*$/);
+                       $lead = " ...";
+               }
+               return "$lead$body";
+
+       } elsif ($where eq 'center') {
+               $str =~ m/^($endre)(.*)$/;
+               my ($left, $str)  = ($1, $2);
+               $str =~ m/^(.*?)($begre)$/;
+               my ($mid, $right) = ($1, $2);
+               if (length($mid) > 5) {
+                       $left  =~ s/&[^;]*$//;
+                       $right =~ s/^[^;]*;// if ($mid =~ m/&[^;]*$/);
+                       $mid = " ... ";
+               }
+               return "$left$mid$right";
+
+       } else {
+               $str =~ m/^($endre)(.*)$/;
+               my $body = $1;
+               my $tail = $2;
+               if (length($tail) > 4) {
+                       $body =~ s/&[^;]*$//;
+                       $tail = "... ";
+               }
+               return "$body$tail";
        }
-       return "$body$tail";
 }
 
 # takes the same arguments as chop_str, but also wraps a <span> around the
 # result with a title attribute if it does get chopped. Additionally, the
 # string is HTML-escaped.
 sub chop_and_escape_str {
-       my $str = shift;
-       my $len = shift;
-       my $add_len = shift || 10;
+       my ($str) = @_;
 
-       my $chopped = chop_str($str, $len, $add_len);
+       my $chopped = chop_str(@_);
        if ($chopped eq $str) {
                return esc_html($chopped);
        } else {
@@ -3791,11 +3832,11 @@ sub git_search_grep_body {
                foreach my $line (@$comment) {
                        if ($line =~ m/^(.*)($search_regexp)(.*)$/i) {
                                my ($lead, $match, $trail) = ($1, $2, $3);
-                               $match = chop_str($match, 70, 5);       # in case match is very long
-                               my $contextlen = (80 - len($match))/2;  # is left for the remainder
-                               $contextlen = 30 if ($contextlen > 30); # but not too much
-                               $lead  = chop_str($lead,  $contextlen, 10);
-                               $trail = chop_str($trail, $contextlen, 10);
+                               $match = chop_str($match, 70, 5, 'center');
+                               my $contextlen = int((80 - length($match))/2);
+                               $contextlen = 30 if ($contextlen > 30);
+                               $lead  = chop_str($lead,  $contextlen, 10, 'left');
+                               $trail = chop_str($trail, $contextlen, 10, 'right');
 
                                $lead  = esc_html($lead);
                                $match = esc_html($match);
index 0a58f3f1267dcb4dbd67c89fc165367c6840f1da..61e7160b361f60e6d673ab64aa3d22c1a783d057 100644 (file)
@@ -41,6 +41,7 @@ int main(int argc, char **argv)
        const char *prefix = NULL;
        int prefix_length = -1;
        int no_more_flags = 0;
+       int hashstdin = 0;
 
        git_config(git_default_config);
 
@@ -65,13 +66,20 @@ int main(int argc, char **argv)
                        else if (!strcmp(argv[i], "--help"))
                                usage(hash_object_usage);
                        else if (!strcmp(argv[i], "--stdin")) {
-                               hash_stdin(type, write_object);
+                               if (hashstdin)
+                                       die("Multiple --stdin arguments are not supported");
+                               hashstdin = 1;
                        }
                        else
                                usage(hash_object_usage);
                }
                else {
                        const char *arg = argv[i];
+
+                       if (hashstdin) {
+                               hash_stdin(type, write_object);
+                               hashstdin = 0;
+                       }
                        if (0 <= prefix_length)
                                arg = prefix_filename(prefix, prefix_length,
                                                      arg);
@@ -79,5 +87,7 @@ int main(int argc, char **argv)
                        no_more_flags = 1;
                }
        }
+       if (hashstdin)
+               hash_stdin(type, write_object);
        return 0;
 }
diff --git a/help.c b/help.c
index 8143dcdd38218ef03c47535c7c2e761cf1a868ce..e57a50ed59badbb968ce508b6c1802f1fcb68903 100644 (file)
--- a/help.c
+++ b/help.c
@@ -7,40 +7,49 @@
 #include "builtin.h"
 #include "exec_cmd.h"
 #include "common-cmds.h"
-
-static const char *help_default_format;
-
-static enum help_format {
-       man_format,
-       info_format,
-       web_format,
-} help_format = man_format;
-
-static void parse_help_format(const char *format)
+#include "parse-options.h"
+
+enum help_format {
+       HELP_FORMAT_MAN,
+       HELP_FORMAT_INFO,
+       HELP_FORMAT_WEB,
+};
+
+static int show_all = 0;
+static enum help_format help_format = HELP_FORMAT_MAN;
+static struct option builtin_help_options[] = {
+       OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
+       OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
+       OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
+                       HELP_FORMAT_WEB),
+       OPT_SET_INT('i', "info", &help_format, "show info page",
+                       HELP_FORMAT_INFO),
+};
+
+static const char * const builtin_help_usage[] = {
+       "git-help [--all] [--man|--web|--info] [command]",
+       NULL
+};
+
+static enum help_format parse_help_format(const char *format)
 {
-       if (!format) {
-               help_format = man_format;
-               return;
-       }
-       if (!strcmp(format, "man")) {
-               help_format = man_format;
-               return;
-       }
-       if (!strcmp(format, "info")) {
-               help_format = info_format;
-               return;
-       }
-       if (!strcmp(format, "web") || !strcmp(format, "html")) {
-               help_format = web_format;
-               return;
-       }
+       if (!strcmp(format, "man"))
+               return HELP_FORMAT_MAN;
+       if (!strcmp(format, "info"))
+               return HELP_FORMAT_INFO;
+       if (!strcmp(format, "web") || !strcmp(format, "html"))
+               return HELP_FORMAT_WEB;
        die("unrecognized help format '%s'", format);
 }
 
 static int git_help_config(const char *var, const char *value)
 {
-       if (!strcmp(var, "help.format"))
-               return git_config_string(&help_default_format, var, value);
+       if (!strcmp(var, "help.format")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               help_format = parse_help_format(value);
+               return 0;
+       }
        return git_default_config(var, value);
 }
 
@@ -201,7 +210,7 @@ static unsigned int list_commands_in_dir(struct cmdnames *cmds,
        return longest;
 }
 
-static void list_commands(void)
+static unsigned int load_command_list(void)
 {
        unsigned int longest = 0;
        unsigned int len;
@@ -241,6 +250,14 @@ static void list_commands(void)
        uniq(&other_cmds);
        exclude_cmds(&other_cmds, &main_cmds);
 
+       return longest;
+}
+
+static void list_commands(void)
+{
+       unsigned int longest = load_command_list();
+       const char *exec_path = git_exec_path();
+
        if (main_cmds.cnt) {
                printf("available git commands in '%s'\n", exec_path);
                printf("----------------------------");
@@ -275,6 +292,22 @@ void list_common_cmds_help(void)
        }
 }
 
+static int is_in_cmdlist(struct cmdnames *c, const char *s)
+{
+       int i;
+       for (i = 0; i < c->cnt; i++)
+               if (!strcmp(s, c->names[i]->name))
+                       return 1;
+       return 0;
+}
+
+static int is_git_command(const char *s)
+{
+       load_command_list();
+       return is_in_cmdlist(&main_cmds, s) ||
+               is_in_cmdlist(&other_cmds, s);
+}
+
 static const char *cmd_to_page(const char *git_cmd)
 {
        if (!git_cmd)
@@ -362,50 +395,43 @@ int cmd_version(int argc, const char **argv, const char *prefix)
 
 int cmd_help(int argc, const char **argv, const char *prefix)
 {
-       const char *help_cmd = argv[1];
+       int nongit;
+       const char *alias;
 
-       if (argc < 2) {
-               printf("usage: %s\n\n", git_usage_string);
-               list_common_cmds_help();
-               exit(0);
-       }
+       setup_git_directory_gently(&nongit);
+       git_config(git_help_config);
+
+       argc = parse_options(argc, argv, builtin_help_options,
+                       builtin_help_usage, 0);
 
-       if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) {
+       if (show_all) {
                printf("usage: %s\n\n", git_usage_string);
                list_commands();
+               return 0;
        }
 
-       else if (!strcmp(help_cmd, "--web") || !strcmp(help_cmd, "-w")) {
-               show_html_page(argc > 2 ? argv[2] : NULL);
-       }
-
-       else if (!strcmp(help_cmd, "--info") || !strcmp(help_cmd, "-i")) {
-               show_info_page(argc > 2 ? argv[2] : NULL);
+       if (!argv[0]) {
+               printf("usage: %s\n\n", git_usage_string);
+               list_common_cmds_help();
+               return 0;
        }
 
-       else if (!strcmp(help_cmd, "--man") || !strcmp(help_cmd, "-m")) {
-               show_man_page(argc > 2 ? argv[2] : NULL);
+       alias = alias_lookup(argv[0]);
+       if (alias && !is_git_command(argv[0])) {
+               printf("`git %s' is aliased to `%s'\n", argv[0], alias);
+               return 0;
        }
 
-       else {
-               int nongit;
-
-               setup_git_directory_gently(&nongit);
-               git_config(git_help_config);
-               if (help_default_format)
-                       parse_help_format(help_default_format);
-
-               switch (help_format) {
-               case man_format:
-                       show_man_page(help_cmd);
-                       break;
-               case info_format:
-                       show_info_page(help_cmd);
-                       break;
-               case web_format:
-                       show_html_page(help_cmd);
-                       break;
-               }
+       switch (help_format) {
+       case HELP_FORMAT_MAN:
+               show_man_page(argv[0]);
+               break;
+       case HELP_FORMAT_INFO:
+               show_info_page(argv[0]);
+               break;
+       case HELP_FORMAT_WEB:
+               show_html_page(argv[0]);
+               break;
        }
 
        return 0;
index e9ba6df9d230e8adc12df32b502729c09c8e7d5a..608f697cf3860245510bff907a643d9b6143e724 100644 (file)
@@ -137,6 +137,72 @@ static int has_non_ascii(const char *s)
        return 0;
 }
 
+void log_write_email_headers(struct rev_info *opt, const char *name,
+                            const char **subject_p, const char **extra_headers_p)
+{
+       const char *subject = NULL;
+       const char *extra_headers = opt->extra_headers;
+       if (opt->total > 0) {
+               static char buffer[64];
+               snprintf(buffer, sizeof(buffer),
+                        "Subject: [%s %0*d/%d] ",
+                        opt->subject_prefix,
+                        digits_in_number(opt->total),
+                        opt->nr, opt->total);
+               subject = buffer;
+       } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
+               static char buffer[256];
+               snprintf(buffer, sizeof(buffer),
+                        "Subject: [%s] ",
+                        opt->subject_prefix);
+               subject = buffer;
+       } else {
+               subject = "Subject: ";
+       }
+
+       printf("From %s Mon Sep 17 00:00:00 2001\n", name);
+       if (opt->message_id)
+               printf("Message-Id: <%s>\n", opt->message_id);
+       if (opt->ref_message_id)
+               printf("In-Reply-To: <%s>\nReferences: <%s>\n",
+                      opt->ref_message_id, opt->ref_message_id);
+       if (opt->mime_boundary) {
+               static char subject_buffer[1024];
+               static char buffer[1024];
+               snprintf(subject_buffer, sizeof(subject_buffer) - 1,
+                        "%s"
+                        "MIME-Version: 1.0\n"
+                        "Content-Type: multipart/mixed;"
+                        " boundary=\"%s%s\"\n"
+                        "\n"
+                        "This is a multi-part message in MIME "
+                        "format.\n"
+                        "--%s%s\n"
+                        "Content-Type: text/plain; "
+                        "charset=UTF-8; format=fixed\n"
+                        "Content-Transfer-Encoding: 8bit\n\n",
+                        extra_headers ? extra_headers : "",
+                        mime_boundary_leader, opt->mime_boundary,
+                        mime_boundary_leader, opt->mime_boundary);
+               extra_headers = subject_buffer;
+
+               snprintf(buffer, sizeof(buffer) - 1,
+                        "--%s%s\n"
+                        "Content-Type: text/x-patch;"
+                        " name=\"%s.diff\"\n"
+                        "Content-Transfer-Encoding: 8bit\n"
+                        "Content-Disposition: %s;"
+                        " filename=\"%s.diff\"\n\n",
+                        mime_boundary_leader, opt->mime_boundary,
+                        name,
+                        opt->no_inline ? "attachment" : "inline",
+                        name);
+               opt->diffopt.stat_sep = buffer;
+       }
+       *subject_p = subject;
+       *extra_headers_p = extra_headers;
+}
+
 void show_log(struct rev_info *opt, const char *sep)
 {
        struct strbuf msgbuf;
@@ -188,64 +254,8 @@ void show_log(struct rev_info *opt, const char *sep)
         */
 
        if (opt->commit_format == CMIT_FMT_EMAIL) {
-               char *sha1 = sha1_to_hex(commit->object.sha1);
-               if (opt->total > 0) {
-                       static char buffer[64];
-                       snprintf(buffer, sizeof(buffer),
-                                       "Subject: [%s %0*d/%d] ",
-                                       opt->subject_prefix,
-                                       digits_in_number(opt->total),
-                                       opt->nr, opt->total);
-                       subject = buffer;
-               } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
-                       static char buffer[256];
-                       snprintf(buffer, sizeof(buffer),
-                                       "Subject: [%s] ",
-                                       opt->subject_prefix);
-                       subject = buffer;
-               } else {
-                       subject = "Subject: ";
-               }
-
-               printf("From %s Mon Sep 17 00:00:00 2001\n", sha1);
-               if (opt->message_id)
-                       printf("Message-Id: <%s>\n", opt->message_id);
-               if (opt->ref_message_id)
-                       printf("In-Reply-To: <%s>\nReferences: <%s>\n",
-                              opt->ref_message_id, opt->ref_message_id);
-               if (opt->mime_boundary) {
-                       static char subject_buffer[1024];
-                       static char buffer[1024];
-                       snprintf(subject_buffer, sizeof(subject_buffer) - 1,
-                                "%s"
-                                "MIME-Version: 1.0\n"
-                                "Content-Type: multipart/mixed;"
-                                " boundary=\"%s%s\"\n"
-                                "\n"
-                                "This is a multi-part message in MIME "
-                                "format.\n"
-                                "--%s%s\n"
-                                "Content-Type: text/plain; "
-                                "charset=UTF-8; format=fixed\n"
-                                "Content-Transfer-Encoding: 8bit\n\n",
-                                extra_headers ? extra_headers : "",
-                                mime_boundary_leader, opt->mime_boundary,
-                                mime_boundary_leader, opt->mime_boundary);
-                       extra_headers = subject_buffer;
-
-                       snprintf(buffer, sizeof(buffer) - 1,
-                                "--%s%s\n"
-                                "Content-Type: text/x-patch;"
-                                " name=\"%s.diff\"\n"
-                                "Content-Transfer-Encoding: 8bit\n"
-                                "Content-Disposition: %s;"
-                                " filename=\"%s.diff\"\n\n",
-                                mime_boundary_leader, opt->mime_boundary,
-                                sha1,
-                                opt->no_inline ? "attachment" : "inline",
-                                sha1);
-                       opt->diffopt.stat_sep = buffer;
-               }
+               log_write_email_headers(opt, sha1_to_hex(commit->object.sha1),
+                                       &subject, &extra_headers);
        } else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
                fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout);
                if (opt->commit_format != CMIT_FMT_ONELINE)
index b33f7cd7ac2ef6a2587109c4ee618d63ccedae96..0cc9344eabdd0046edf360958376b2037ef643dc 100644 (file)
@@ -13,5 +13,7 @@ int log_tree_commit(struct rev_info *, struct commit *);
 int log_tree_opt_parse(struct rev_info *, const char **, int);
 void show_log(struct rev_info *opt, const char *sep);
 void show_decorations(struct commit *commit);
+void log_write_email_headers(struct rev_info *opt, const char *name,
+                            const char **subject_p, const char **extra_headers_p);
 
 #endif
diff --git a/merge-recursive.c b/merge-recursive.c
deleted file mode 100644 (file)
index 55ef76f..0000000
+++ /dev/null
@@ -1,1763 +0,0 @@
-/*
- * 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 "tree-walk.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "run-command.h"
-#include "tag.h"
-#include "unpack-trees.h"
-#include "path-list.h"
-#include "xdiff-interface.h"
-#include "interpolate.h"
-#include "attr.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 unsigned commit_list_count(const struct commit_list *l)
-{
-       unsigned c = 0;
-       for (; l; l = l->next )
-               c++;
-       return c;
-}
-
-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 path_list current_file_set = {NULL, 0, 0, 1};
-static struct path_list current_directory_set = {NULL, 0, 0, 1};
-
-static int call_depth = 0;
-static int verbosity = 2;
-static int 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;
-
-       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;
-}
-
-static int unmerged_index(void)
-{
-       int i;
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (ce_stage(ce))
-                       return 1;
-       }
-       return 0;
-}
-
-static struct tree *git_write_tree(void)
-{
-       struct tree *result = NULL;
-
-       if (unmerged_index()) {
-               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)
-{
-       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))
-               path_list_insert(newpath, &current_directory_set);
-       else
-               path_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) != 0)
-               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 path_list *entries)
-{
-       struct path_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 = path_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 path_list *get_unmerged(void)
-{
-       struct path_list *unmerged = xcalloc(1, sizeof(struct path_list));
-       int i;
-
-       unmerged->strdup_paths = 1;
-
-       for (i = 0; i < active_nr; i++) {
-               struct path_list_item *item;
-               struct stage_data *e;
-               struct cache_entry *ce = active_cache[i];
-               if (!ce_stage(ce))
-                       continue;
-
-               item = path_list_lookup(ce->name, unmerged);
-               if (!item) {
-                       item = path_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 path_list *get_renames(struct tree *tree,
-                                       struct tree *o_tree,
-                                       struct tree *a_tree,
-                                       struct tree *b_tree,
-                                       struct path_list *entries)
-{
-       int i;
-       struct path_list *renames;
-       struct diff_options opts;
-
-       renames = xcalloc(1, sizeof(struct path_list));
-       diff_setup(&opts);
-       DIFF_OPT_SET(&opts, RECURSIVE);
-       opts.detect_rename = DIFF_DETECT_RENAME;
-       opts.rename_limit = rename_limit;
-       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 path_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 = path_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 = path_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 = path_list_insert(pair->one->path, renames);
-               item->util = re;
-       }
-       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-       diff_queued_diff.nr = 0;
-       diff_flush(&opts);
-       return renames;
-}
-
-static int update_stages(const char *path, struct diff_filespec *o,
-                        struct diff_filespec *a, struct diff_filespec *b,
-                        int clear)
-{
-       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
-       if (clear)
-               if (remove_file_from_cache(path))
-                       return -1;
-       if (o)
-               if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))
-                       return -1;
-       if (a)
-               if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options))
-                       return -1;
-       if (b)
-               if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options))
-                       return -1;
-       return 0;
-}
-
-static int remove_path(const char *name)
-{
-       int ret;
-       char *slash, *dirs;
-
-       ret = unlink(name);
-       if (ret)
-               return ret;
-       dirs = xstrdup(name);
-       while ((slash = strrchr(name, '/'))) {
-               *slash = '\0';
-               if (rmdir(name) != 0)
-                       break;
-       }
-       free(dirs);
-       return ret;
-}
-
-static int remove_file(int clean, const char *path, int no_wd)
-{
-       int update_cache = index_only || clean;
-       int update_working_directory = !index_only && !no_wd;
-
-       if (update_cache) {
-               if (remove_file_from_cache(path))
-                       return -1;
-       }
-       if (update_working_directory) {
-               unlink(path);
-               if (errno != ENOENT || errno != EISDIR)
-                       return -1;
-               remove_path(path);
-       }
-       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 (path_list_has_path(&current_file_set, newpath) ||
-              path_list_has_path(&current_directory_set, newpath) ||
-              lstat(newpath, &st) == 0)
-               sprintf(p, "_%d", suffix++);
-
-       path_list_insert(newpath, &current_file_set);
-       return newpath;
-}
-
-static int mkdir_p(const char *path, unsigned long mode)
-{
-       /* path points to cache entries, so xstrdup before messing with it */
-       char *buf = xstrdup(path);
-       int result = safe_create_leading_directories(buf);
-       free(buf);
-       return result;
-}
-
-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 = mkdir_p(path, 0777);
-       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 (make_room_for_path(path) < 0) {
-                       update_wd = 0;
-                       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);
-                       mkdir_p(path, 0777);
-                       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);
-       }
- 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;
-}
-
-/*
- * Customizable low-level merge drivers support.
- */
-
-struct ll_merge_driver;
-typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
-                          const char *path,
-                          mmfile_t *orig,
-                          mmfile_t *src1, const char *name1,
-                          mmfile_t *src2, const char *name2,
-                          mmbuffer_t *result);
-
-struct ll_merge_driver {
-       const char *name;
-       const char *description;
-       ll_merge_fn fn;
-       const char *recursive;
-       struct ll_merge_driver *next;
-       char *cmdline;
-};
-
-/*
- * Built-in low-levels
- */
-static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
-                          const char *path_unused,
-                          mmfile_t *orig,
-                          mmfile_t *src1, const char *name1,
-                          mmfile_t *src2, const char *name2,
-                          mmbuffer_t *result)
-{
-       /*
-        * The tentative merge result is "ours" for the final round,
-        * or common ancestor for an internal merge.  Still return
-        * "conflicted merge" status.
-        */
-       mmfile_t *stolen = index_only ? orig : src1;
-
-       result->ptr = stolen->ptr;
-       result->size = stolen->size;
-       stolen->ptr = NULL;
-       return 1;
-}
-
-static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
-                       const char *path_unused,
-                       mmfile_t *orig,
-                       mmfile_t *src1, const char *name1,
-                       mmfile_t *src2, const char *name2,
-                       mmbuffer_t *result)
-{
-       xpparam_t xpp;
-
-       if (buffer_is_binary(orig->ptr, orig->size) ||
-           buffer_is_binary(src1->ptr, src1->size) ||
-           buffer_is_binary(src2->ptr, src2->size)) {
-               warning("Cannot merge binary files: %s vs. %s\n",
-                       name1, name2);
-               return ll_binary_merge(drv_unused, path_unused,
-                                      orig, src1, name1,
-                                      src2, name2,
-                                      result);
-       }
-
-       memset(&xpp, 0, sizeof(xpp));
-       return xdl_merge(orig,
-                        src1, name1,
-                        src2, name2,
-                        &xpp, XDL_MERGE_ZEALOUS,
-                        result);
-}
-
-static int ll_union_merge(const struct ll_merge_driver *drv_unused,
-                         const char *path_unused,
-                         mmfile_t *orig,
-                         mmfile_t *src1, const char *name1,
-                         mmfile_t *src2, const char *name2,
-                         mmbuffer_t *result)
-{
-       char *src, *dst;
-       long size;
-       const int marker_size = 7;
-
-       int status = ll_xdl_merge(drv_unused, path_unused,
-                                 orig, src1, NULL, src2, NULL, result);
-       if (status <= 0)
-               return status;
-       size = result->size;
-       src = dst = result->ptr;
-       while (size) {
-               char ch;
-               if ((marker_size < size) &&
-                   (*src == '<' || *src == '=' || *src == '>')) {
-                       int i;
-                       ch = *src;
-                       for (i = 0; i < marker_size; i++)
-                               if (src[i] != ch)
-                                       goto not_a_marker;
-                       if (src[marker_size] != '\n')
-                               goto not_a_marker;
-                       src += marker_size + 1;
-                       size -= marker_size + 1;
-                       continue;
-               }
-       not_a_marker:
-               do {
-                       ch = *src++;
-                       *dst++ = ch;
-                       size--;
-               } while (ch != '\n' && size);
-       }
-       result->size = dst - result->ptr;
-       return 0;
-}
-
-#define LL_BINARY_MERGE 0
-#define LL_TEXT_MERGE 1
-#define LL_UNION_MERGE 2
-static struct ll_merge_driver ll_merge_drv[] = {
-       { "binary", "built-in binary merge", ll_binary_merge },
-       { "text", "built-in 3-way text merge", ll_xdl_merge },
-       { "union", "built-in union merge", ll_union_merge },
-};
-
-static void create_temp(mmfile_t *src, char *path)
-{
-       int fd;
-
-       strcpy(path, ".merge_file_XXXXXX");
-       fd = xmkstemp(path);
-       if (write_in_full(fd, src->ptr, src->size) != src->size)
-               die("unable to write temp-file");
-       close(fd);
-}
-
-/*
- * User defined low-level merge driver support.
- */
-static int ll_ext_merge(const struct ll_merge_driver *fn,
-                       const char *path,
-                       mmfile_t *orig,
-                       mmfile_t *src1, const char *name1,
-                       mmfile_t *src2, const char *name2,
-                       mmbuffer_t *result)
-{
-       char temp[3][50];
-       char cmdbuf[2048];
-       struct interp table[] = {
-               { "%O" },
-               { "%A" },
-               { "%B" },
-       };
-       struct child_process child;
-       const char *args[20];
-       int status, fd, i;
-       struct stat st;
-
-       if (fn->cmdline == NULL)
-               die("custom merge driver %s lacks command line.", fn->name);
-
-       result->ptr = NULL;
-       result->size = 0;
-       create_temp(orig, temp[0]);
-       create_temp(src1, temp[1]);
-       create_temp(src2, temp[2]);
-
-       interp_set_entry(table, 0, temp[0]);
-       interp_set_entry(table, 1, temp[1]);
-       interp_set_entry(table, 2, temp[2]);
-
-       output(1, "merging %s using %s", path,
-              fn->description ? fn->description : fn->name);
-
-       interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3);
-
-       memset(&child, 0, sizeof(child));
-       child.argv = args;
-       args[0] = "sh";
-       args[1] = "-c";
-       args[2] = cmdbuf;
-       args[3] = NULL;
-
-       status = run_command(&child);
-       if (status < -ERR_RUN_COMMAND_FORK)
-               ; /* failure in run-command */
-       else
-               status = -status;
-       fd = open(temp[1], O_RDONLY);
-       if (fd < 0)
-               goto bad;
-       if (fstat(fd, &st))
-               goto close_bad;
-       result->size = st.st_size;
-       result->ptr = xmalloc(result->size + 1);
-       if (read_in_full(fd, result->ptr, result->size) != result->size) {
-               free(result->ptr);
-               result->ptr = NULL;
-               result->size = 0;
-       }
- close_bad:
-       close(fd);
- bad:
-       for (i = 0; i < 3; i++)
-               unlink(temp[i]);
-       return status;
-}
-
-/*
- * merge.default and merge.driver configuration items
- */
-static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
-static const char *default_ll_merge;
-
-static int read_merge_config(const char *var, const char *value)
-{
-       struct ll_merge_driver *fn;
-       const char *ep, *name;
-       int namelen;
-
-       if (!strcmp(var, "merge.default")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               default_ll_merge = strdup(value);
-               return 0;
-       }
-
-       /*
-        * We are not interested in anything but "merge.<name>.variable";
-        * especially, we do not want to look at variables such as
-        * "merge.summary", "merge.tool", and "merge.verbosity".
-        */
-       if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5)
-               return 0;
-
-       /*
-        * Find existing one as we might be processing merge.<name>.var2
-        * after seeing merge.<name>.var1.
-        */
-       name = var + 6;
-       namelen = ep - name;
-       for (fn = ll_user_merge; fn; fn = fn->next)
-               if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
-                       break;
-       if (!fn) {
-               fn = xcalloc(1, sizeof(struct ll_merge_driver));
-               fn->name = xmemdupz(name, namelen);
-               fn->fn = ll_ext_merge;
-               *ll_user_merge_tail = fn;
-               ll_user_merge_tail = &(fn->next);
-       }
-
-       ep++;
-
-       if (!strcmp("name", ep)) {
-               if (!value)
-                       return config_error_nonbool(var);
-               fn->description = strdup(value);
-               return 0;
-       }
-
-       if (!strcmp("driver", ep)) {
-               if (!value)
-                       return config_error_nonbool(var);
-               /*
-                * merge.<name>.driver specifies the command line:
-                *
-                *      command-line
-                *
-                * The command-line will be interpolated with the following
-                * tokens and is given to the shell:
-                *
-                *    %O - temporary file name for the merge base.
-                *    %A - temporary file name for our version.
-                *    %B - temporary file name for the other branches' version.
-                *
-                * The external merge driver should write the results in the
-                * file named by %A, and signal that it has done with zero exit
-                * status.
-                */
-               fn->cmdline = strdup(value);
-               return 0;
-       }
-
-       if (!strcmp("recursive", ep)) {
-               if (!value)
-                       return config_error_nonbool(var);
-               fn->recursive = strdup(value);
-               return 0;
-       }
-
-       return 0;
-}
-
-static void initialize_ll_merge(void)
-{
-       if (ll_user_merge_tail)
-               return;
-       ll_user_merge_tail = &ll_user_merge;
-       git_config(read_merge_config);
-}
-
-static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)
-{
-       struct ll_merge_driver *fn;
-       const char *name;
-       int i;
-
-       initialize_ll_merge();
-
-       if (ATTR_TRUE(merge_attr))
-               return &ll_merge_drv[LL_TEXT_MERGE];
-       else if (ATTR_FALSE(merge_attr))
-               return &ll_merge_drv[LL_BINARY_MERGE];
-       else if (ATTR_UNSET(merge_attr)) {
-               if (!default_ll_merge)
-                       return &ll_merge_drv[LL_TEXT_MERGE];
-               else
-                       name = default_ll_merge;
-       }
-       else
-               name = merge_attr;
-
-       for (fn = ll_user_merge; fn; fn = fn->next)
-               if (!strcmp(fn->name, name))
-                       return fn;
-
-       for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
-               if (!strcmp(ll_merge_drv[i].name, name))
-                       return &ll_merge_drv[i];
-
-       /* default to the 3-way */
-       return &ll_merge_drv[LL_TEXT_MERGE];
-}
-
-static const char *git_path_check_merge(const char *path)
-{
-       static struct git_attr_check attr_merge_check;
-
-       if (!attr_merge_check.attr)
-               attr_merge_check.attr = git_attr("merge", 5);
-
-       if (git_checkattr(path, 1, &attr_merge_check))
-               return NULL;
-       return attr_merge_check.value;
-}
-
-static int ll_merge(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;
-       const char *ll_driver_name;
-       const struct ll_merge_driver *driver;
-
-       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);
-
-       ll_driver_name = git_path_check_merge(a->path);
-       driver = find_ll_merge_driver(ll_driver_name);
-
-       if (index_only && driver->recursive)
-               driver = find_ll_merge_driver(driver->recursive);
-       merge_status = driver->fn(driver, a->path,
-                                 &orig, &src1, name1, &src2, name2,
-                                 result_buf);
-
-       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;
-
-               result.mode = a->mode == o->mode ? b->mode: a->mode;
-
-               if (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 = ll_merge(&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 (path_list_has_path(&current_directory_set, ren1_dst)) {
-               dst_name1 = del[delp++] = unique_path(ren1_dst, branch1);
-               output(1, "%s is a directory in %s added as %s instead",
-                      ren1_dst, branch2, dst_name1);
-               remove_file(0, ren1_dst, 0);
-       }
-       if (path_list_has_path(&current_directory_set, ren2_dst)) {
-               dst_name2 = del[delp++] = unique_path(ren2_dst, branch2);
-               output(1, "%s is a directory in %s added as %s instead",
-                      ren2_dst, branch1, dst_name2);
-               remove_file(0, ren2_dst, 0);
-       }
-       if (index_only) {
-               remove_file_from_cache(dst_name1);
-               remove_file_from_cache(dst_name2);
-               /*
-                * Uncomment to leave the conflicting names in the resulting tree
-                *
-                * update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1);
-                * update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2);
-                */
-       } else {
-               update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
-               update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
-       }
-       while (delp--)
-               free(del[delp]);
-}
-
-static void conflict_rename_dir(struct rename *ren1,
-                               const char *branch1)
-{
-       char *new_path = unique_path(ren1->pair->two->path, branch1);
-       output(1, "Renamed %s to %s instead", ren1->pair->one->path, new_path);
-       remove_file(0, ren1->pair->two->path, 0);
-       update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
-       free(new_path);
-}
-
-static void conflict_rename_rename_2(struct rename *ren1,
-                                    const char *branch1,
-                                    struct rename *ren2,
-                                    const char *branch2)
-{
-       char *new_path1 = unique_path(ren1->pair->two->path, branch1);
-       char *new_path2 = unique_path(ren2->pair->two->path, branch2);
-       output(1, "Renamed %s to %s and %s to %s instead",
-              ren1->pair->one->path, new_path1,
-              ren2->pair->one->path, new_path2);
-       remove_file(0, ren1->pair->two->path, 0);
-       update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
-       update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
-       free(new_path2);
-       free(new_path1);
-}
-
-static int process_renames(struct path_list *a_renames,
-                          struct path_list *b_renames,
-                          const char *a_branch,
-                          const char *b_branch)
-{
-       int clean_merge = 1, i, j;
-       struct path_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;
-               path_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;
-               path_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 path_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].path,
-                                       b_renames->items[j].path);
-                       if (compare <= 0)
-                               ren1 = a_renames->items[i++].util;
-                       if (compare >= 0)
-                               ren2 = b_renames->items[j++].util;
-               }
-
-               /* TODO: refactor, so that 1/2 are not needed */
-               if (ren1) {
-                       renames1 = a_renames;
-                       renames2 = b_renames;
-                       renames2Dst = &b_by_dst;
-                       branch1 = a_branch;
-                       branch2 = b_branch;
-               } else {
-                       struct rename *tmp;
-                       renames1 = b_renames;
-                       renames2 = a_renames;
-                       renames2Dst = &a_by_dst;
-                       branch1 = b_branch;
-                       branch2 = a_branch;
-                       tmp = ren2;
-                       ren2 = ren1;
-                       ren1 = tmp;
-               }
-               src = ren1->pair->one->path;
-
-               ren1->dst_entry->processed = 1;
-               ren1->src_entry->processed = 1;
-
-               if (ren1->processed)
-                       continue;
-               ren1->processed = 1;
-
-               ren1_src = ren1->pair->one->path;
-               ren1_dst = ren1->pair->two->path;
-
-               if (ren2) {
-                       const char *ren2_src = ren2->pair->one->path;
-                       const char *ren2_dst = ren2->pair->two->path;
-                       /* Renamed in 1 and renamed in 2 */
-                       if (strcmp(ren1_src, ren2_src) != 0)
-                               die("ren1.src != ren2.src");
-                       ren2->dst_entry->processed = 1;
-                       ren2->processed = 1;
-                       if (strcmp(ren1_dst, ren2_dst) != 0) {
-                               clean_merge = 0;
-                               output(1, "CONFLICT (rename/rename): "
-                                      "Rename \"%s\"->\"%s\" in branch \"%s\" "
-                                      "rename \"%s\"->\"%s\" in \"%s\"%s",
-                                      src, ren1_dst, branch1,
-                                      src, ren2_dst, branch2,
-                                      index_only ? " (left unresolved)": "");
-                               if (index_only) {
-                                       remove_file_from_cache(src);
-                                       update_file(0, ren1->pair->one->sha1,
-                                                   ren1->pair->one->mode, src);
-                               }
-                               conflict_rename_rename(ren1, branch1, ren2, branch2);
-                       } else {
-                               struct merge_file_info mfi;
-                               remove_file(1, ren1_src, 1);
-                               mfi = merge_file(ren1->pair->one,
-                                                ren1->pair->two,
-                                                ren2->pair->two,
-                                                branch1,
-                                                branch2);
-                               if (mfi.merge || !mfi.clean)
-                                       output(1, "Renamed %s->%s", src, ren1_dst);
-
-                               if (mfi.merge)
-                                       output(2, "Auto-merged %s", ren1_dst);
-
-                               if (!mfi.clean) {
-                                       output(1, "CONFLICT (content): merge conflict in %s",
-                                              ren1_dst);
-                                       clean_merge = 0;
-
-                                       if (!index_only)
-                                               update_stages(ren1_dst,
-                                                             ren1->pair->one,
-                                                             ren1->pair->two,
-                                                             ren2->pair->two,
-                                                             1 /* clear */);
-                               }
-                               update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
-                       }
-               } else {
-                       /* Renamed in 1, maybe changed in 2 */
-                       struct path_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 (path_list_has_path(&current_directory_set, ren1_dst)) {
-                               clean_merge = 0;
-                               output(1, "CONFLICT (rename/directory): Renamed %s->%s in %s "
-                                      " directory %s added in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      ren1_dst, branch2);
-                               conflict_rename_dir(ren1, branch1);
-                       } else if (sha_eq(src_other.sha1, null_sha1)) {
-                               clean_merge = 0;
-                               output(1, "CONFLICT (rename/delete): Renamed %s->%s in %s "
-                                      "and deleted in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      branch2);
-                               update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
-                       } else if (!sha_eq(dst_other.sha1, null_sha1)) {
-                               const char *new_path;
-                               clean_merge = 0;
-                               try_merge = 1;
-                               output(1, "CONFLICT (rename/add): Renamed %s->%s in %s. "
-                                      "%s added in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      ren1_dst, branch2);
-                               new_path = unique_path(ren1_dst, branch2);
-                               output(1, "Added as %s instead", new_path);
-                               update_file(0, dst_other.sha1, dst_other.mode, new_path);
-                       } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) {
-                               ren2 = item->util;
-                               clean_merge = 0;
-                               ren2->processed = 1;
-                               output(1, "CONFLICT (rename/rename): Renamed %s->%s in %s. "
-                                      "Renamed %s->%s in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      ren2->pair->one->path, ren2->pair->two->path, branch2);
-                               conflict_rename_rename_2(ren1, branch1, ren2, branch2);
-                       } else
-                               try_merge = 1;
-
-                       if (try_merge) {
-                               struct diff_filespec *o, *a, *b;
-                               struct merge_file_info mfi;
-                               src_other.path = (char *)ren1_src;
-
-                               o = ren1->pair->one;
-                               if (a_renames == renames1) {
-                                       a = ren1->pair->two;
-                                       b = &src_other;
-                               } else {
-                                       b = ren1->pair->two;
-                                       a = &src_other;
-                               }
-                               mfi = merge_file(o, a, b,
-                                               a_branch, b_branch);
-
-                               if (mfi.clean &&
-                                   sha_eq(mfi.sha, ren1->pair->two->sha1) &&
-                                   mfi.mode == ren1->pair->two->mode)
-                                       /*
-                                        * This messaged is part of
-                                        * t6022 test. If you change
-                                        * it update the test too.
-                                        */
-                                       output(3, "Skipped %s (merged same as existing)", ren1_dst);
-                               else {
-                                       if (mfi.merge || !mfi.clean)
-                                               output(1, "Renamed %s => %s", ren1_src, ren1_dst);
-                                       if (mfi.merge)
-                                               output(2, "Auto-merged %s", ren1_dst);
-                                       if (!mfi.clean) {
-                                               output(1, "CONFLICT (rename/modify): Merge conflict in %s",
-                                                      ren1_dst);
-                                               clean_merge = 0;
-
-                                               if (!index_only)
-                                                       update_stages(ren1_dst,
-                                                                     o, a, b, 1);
-                                       }
-                                       update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
-                               }
-                       }
-               }
-       }
-       path_list_clear(&a_by_dst, 0);
-       path_list_clear(&b_by_dst, 0);
-
-       return clean_merge;
-}
-
-static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)
-{
-       return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
-}
-
-/* Per entry merge function */
-static int process_entry(const char *path, struct stage_data *entry,
-                        const char *branch1,
-                        const char *branch2)
-{
-       /*
-       printf("processing entry, clean cache: %s\n", index_only ? "yes": "no");
-       print_index_entry("\tpath: ", entry);
-       */
-       int clean_merge = 1;
-       unsigned o_mode = entry->stages[1].mode;
-       unsigned a_mode = entry->stages[2].mode;
-       unsigned b_mode = entry->stages[3].mode;
-       unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
-       unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
-       unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
-
-       if (o_sha && (!a_sha || !b_sha)) {
-               /* Case A: Deleted in one */
-               if ((!a_sha && !b_sha) ||
-                   (sha_eq(a_sha, o_sha) && !b_sha) ||
-                   (!a_sha && sha_eq(b_sha, o_sha))) {
-                       /* Deleted in both or deleted in one and
-                        * unchanged in the other */
-                       if (a_sha)
-                               output(2, "Removed %s", path);
-                       /* do not touch working file if it did not exist */
-                       remove_file(1, path, !a_sha);
-               } else {
-                       /* Deleted in one and changed in the other */
-                       clean_merge = 0;
-                       if (!a_sha) {
-                               output(1, "CONFLICT (delete/modify): %s deleted in %s "
-                                      "and modified in %s. Version %s of %s left in tree.",
-                                      path, branch1,
-                                      branch2, branch2, path);
-                               update_file(0, b_sha, b_mode, path);
-                       } else {
-                               output(1, "CONFLICT (delete/modify): %s deleted in %s "
-                                      "and modified in %s. Version %s of %s left in tree.",
-                                      path, branch2,
-                                      branch1, branch1, path);
-                               update_file(0, a_sha, a_mode, path);
-                       }
-               }
-
-       } else if ((!o_sha && a_sha && !b_sha) ||
-                  (!o_sha && !a_sha && b_sha)) {
-               /* Case B: Added in one. */
-               const char *add_branch;
-               const char *other_branch;
-               unsigned mode;
-               const unsigned char *sha;
-               const char *conf;
-
-               if (a_sha) {
-                       add_branch = branch1;
-                       other_branch = branch2;
-                       mode = a_mode;
-                       sha = a_sha;
-                       conf = "file/directory";
-               } else {
-                       add_branch = branch2;
-                       other_branch = branch1;
-                       mode = b_mode;
-                       sha = b_sha;
-                       conf = "directory/file";
-               }
-               if (path_list_has_path(&current_directory_set, path)) {
-                       const char *new_path = unique_path(path, add_branch);
-                       clean_merge = 0;
-                       output(1, "CONFLICT (%s): There is a directory with name %s in %s. "
-                              "Added %s as %s",
-                              conf, path, other_branch, path, new_path);
-                       remove_file(0, path, 0);
-                       update_file(0, sha, mode, new_path);
-               } else {
-                       output(2, "Added %s", path);
-                       update_file(1, sha, mode, path);
-               }
-       } else if (a_sha && b_sha) {
-               /* Case C: Added in both (check for same permissions) and */
-               /* case D: Modified in both, but differently. */
-               const char *reason = "content";
-               struct merge_file_info mfi;
-               struct diff_filespec o, a, b;
-
-               if (!o_sha) {
-                       reason = "add/add";
-                       o_sha = (unsigned char *)null_sha1;
-               }
-               output(2, "Auto-merged %s", path);
-               o.path = a.path = b.path = (char *)path;
-               hashcpy(o.sha1, o_sha);
-               o.mode = o_mode;
-               hashcpy(a.sha1, a_sha);
-               a.mode = a_mode;
-               hashcpy(b.sha1, b_sha);
-               b.mode = b_mode;
-
-               mfi = merge_file(&o, &a, &b,
-                                branch1, branch2);
-
-               clean_merge = mfi.clean;
-               if (mfi.clean)
-                       update_file(1, mfi.sha, mfi.mode, path);
-               else if (S_ISGITLINK(mfi.mode))
-                       output(1, "CONFLICT (submodule): Merge conflict in %s "
-                              "- needs %s", path, sha1_to_hex(b.sha1));
-               else {
-                       output(1, "CONFLICT (%s): Merge conflict in %s",
-                                       reason, path);
-
-                       if (index_only)
-                               update_file(0, mfi.sha, mfi.mode, path);
-                       else
-                               update_file_flags(mfi.sha, mfi.mode, path,
-                                             0 /* update_cache */, 1 /* update_working_directory */);
-               }
-       } else if (!o_sha && !a_sha && !b_sha) {
-               /*
-                * this entry was deleted altogether. a_mode == 0 means
-                * we had that path and want to actively remove it.
-                */
-               remove_file(1, path, !a_mode);
-       } else
-               die("Fatal merge failure, shouldn't happen.");
-
-       return clean_merge;
-}
-
-static 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_index()) {
-               struct path_list *entries, *re_head, *re_merge;
-               int i;
-               path_list_clear(&current_file_set, 1);
-               path_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].path;
-                       struct stage_data *e = entries->items[i].util;
-                       if (!e->processed
-                               && !process_entry(path, e, branch1, branch2))
-                               clean = 0;
-               }
-
-               path_list_clear(re_merge, 0);
-               path_list_clear(re_head, 0);
-               path_list_clear(entries, 1);
-
-       }
-       else
-               clean = 1;
-
-       if (index_only)
-               *result = git_write_tree();
-
-       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.
- */
-static int merge(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(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];
-       char *name;
-
-       if (strlen(branch) != 40)
-               return branch;
-       sprintf(githead_env, "GITHEAD_%s", branch);
-       name = getenv(githead_env);
-       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)
-{
-       if (!strcasecmp(var, "merge.verbosity")) {
-               verbosity = git_config_int(var, value);
-               return 0;
-       }
-       if (!strcasecmp(var, "diff.renamelimit")) {
-               rename_limit = git_config_int(var, value);
-               return 0;
-       }
-       return git_default_config(var, value);
-}
-
-int main(int argc, char *argv[])
-{
-       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;
-
-       if (argv[0]) {
-               int namelen = strlen(argv[0]);
-               if (8 < namelen &&
-                   !strcmp(argv[0] + namelen - 8, "-subtree"))
-                       subtree_merge = 1;
-       }
-
-       git_config(merge_config);
-       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 (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];
-
-       h1 = get_ref(branch1);
-       h2 = get_ref(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(h1, h2, branch1, branch2, ca, &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());
-
-       return clean ? 0: 1;
-}
diff --git a/merge-recursive.h b/merge-recursive.h
new file mode 100644 (file)
index 0000000..f37630a
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef MERGE_RECURSIVE_H
+#define MERGE_RECURSIVE_H
+
+int merge_recursive(struct commit *h1,
+                   struct commit *h2,
+                   const char *branch1,
+                   const char *branch2,
+                   struct commit_list *ancestors,
+                   struct commit **result);
+
+int merge_trees(struct tree *head,
+               struct tree *merge,
+               struct tree *common,
+               const char *branch1,
+               const char *branch2,
+               struct tree **result);
+
+struct tree *write_tree_from_memory(void);
+
+#endif
index 4bf3be2dd9552b1881ec863835fa2cd28e008090..703f52176bf0b5cbf52cf1f77ba55ba6d87fac94 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -109,9 +109,9 @@ static void add_rfc2047(struct strbuf *sb, const char *line, int len,
        strbuf_addstr(sb, "?=");
 }
 
-static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
-                        const char *line, enum date_mode dmode,
-                        const char *encoding)
+void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
+                 const char *line, enum date_mode dmode,
+                 const char *encoding)
 {
        char *date;
        int namelen;
@@ -620,23 +620,23 @@ static void pp_header(enum cmit_fmt fmt,
                 */
                if (!memcmp(line, "author ", 7)) {
                        strbuf_grow(sb, linelen + 80);
-                       add_user_info("Author", fmt, sb, line + 7, dmode, encoding);
+                       pp_user_info("Author", fmt, sb, line + 7, dmode, encoding);
                }
                if (!memcmp(line, "committer ", 10) &&
                    (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
                        strbuf_grow(sb, linelen + 80);
-                       add_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
+                       pp_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
                }
        }
 }
 
-static void pp_title_line(enum cmit_fmt fmt,
-                         const char **msg_p,
-                         struct strbuf *sb,
-                         const char *subject,
-                         const char *after_subject,
-                         const char *encoding,
-                         int plain_non_ascii)
+void pp_title_line(enum cmit_fmt fmt,
+                  const char **msg_p,
+                  struct strbuf *sb,
+                  const char *subject,
+                  const char *after_subject,
+                  const char *encoding,
+                  int plain_non_ascii)
 {
        struct strbuf title;
 
@@ -685,10 +685,10 @@ static void pp_title_line(enum cmit_fmt fmt,
        strbuf_release(&title);
 }
 
-static void pp_remainder(enum cmit_fmt fmt,
-                        const char **msg_p,
-                        struct strbuf *sb,
-                        int indent)
+void pp_remainder(enum cmit_fmt fmt,
+                 const char **msg_p,
+                 struct strbuf *sb,
+                 int indent)
 {
        int first = 1;
        for (;;) {
index e45f4b3d61c2982ca20c910a7fc11c5bd89204be..657f0c5894c65831b80ceee54d161d0beac1d733 100644 (file)
@@ -37,8 +37,13 @@ static unsigned int hash_name(const char *name, int namelen)
 static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
 {
        void **pos;
-       unsigned int hash = hash_name(ce->name, ce_namelen(ce));
+       unsigned int hash;
 
+       if (ce->ce_flags & CE_HASHED)
+               return;
+       ce->ce_flags |= CE_HASHED;
+       ce->next = NULL;
+       hash = hash_name(ce->name, ce_namelen(ce));
        pos = insert_hash(hash, ce, &istate->name_hash);
        if (pos) {
                ce->next = *pos;
@@ -59,33 +64,18 @@ static void lazy_init_name_hash(struct index_state *istate)
 
 static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
 {
+       ce->ce_flags &= ~CE_UNHASHED;
        istate->cache[nr] = ce;
        if (istate->name_hash_initialized)
                hash_index_entry(istate, ce);
 }
 
-/*
- * We don't actually *remove* it, we can just mark it invalid so that
- * we won't find it in lookups.
- *
- * Not only would we have to search the lists (simple enough), but
- * we'd also have to rehash other hash buckets in case this makes the
- * hash bucket empty (common). So it's much better to just mark
- * it.
- */
-static void remove_hash_entry(struct index_state *istate, struct cache_entry *ce)
-{
-       ce->ce_flags |= CE_UNHASHED;
-}
-
 static void replace_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
 {
        struct cache_entry *old = istate->cache[nr];
 
-       if (ce != old) {
-               remove_hash_entry(istate, old);
-               set_index_entry(istate, nr, ce);
-       }
+       remove_index_entry(old);
+       set_index_entry(istate, nr, ce);
        istate->cache_changed = 1;
 }
 
@@ -413,7 +403,7 @@ int remove_index_entry_at(struct index_state *istate, int pos)
 {
        struct cache_entry *ce = istate->cache[pos];
 
-       remove_hash_entry(istate, ce);
+       remove_index_entry(ce);
        istate->cache_changed = 1;
        istate->cache_nr--;
        if (pos >= istate->cache_nr)
@@ -1176,6 +1166,16 @@ int discard_index(struct index_state *istate)
        return 0;
 }
 
+int unmerged_index(struct index_state *istate)
+{
+       int i;
+       for (i = 0; i < istate->cache_nr; i++) {
+               if (ce_stage(istate->cache[i]))
+                       return 1;
+       }
+       return 0;
+}
+
 #define WRITE_BUFFER_SIZE 8192
 static unsigned char write_buffer[WRITE_BUFFER_SIZE];
 static unsigned long write_buffer_len;
index 326749583221d0f5e81f58608a23ab1dc1601177..a971433db155bbac162cece038b73dc64e682ac0 100644 (file)
@@ -132,6 +132,7 @@ static int run_hook(const char *hook_name)
                                break;
                }
        }
+       close(proc.in);
        return hook_status(finish_command(&proc), hook_name);
 }
 
@@ -414,6 +415,7 @@ static const char *unpack(void)
                if (start_command(&ip))
                        return "index-pack fork failed";
                pack_lockfile = index_pack_lockfile(ip.out);
+               close(ip.out);
                status = finish_command(&ip);
                if (!status) {
                        reprepare_packed_git();
diff --git a/refs.c b/refs.c
index 67d2a502afb60050f0ce750c21ae1a42fa5cb803..c979fb1d9507309785909a8d714ab28b1efd2e88 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -157,6 +157,7 @@ static struct cached_refs {
        struct ref_list *loose;
        struct ref_list *packed;
 } cached_refs;
+static struct ref_list *current_ref;
 
 static void free_ref_list(struct ref_list *list)
 {
@@ -476,6 +477,7 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim,
                error("%s does not point to a valid object!", entry->name);
                return 0;
        }
+       current_ref = entry;
        return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
 }
 
@@ -485,6 +487,16 @@ int peel_ref(const char *ref, unsigned char *sha1)
        unsigned char base[20];
        struct object *o;
 
+       if (current_ref && (current_ref->name == ref
+               || !strcmp(current_ref->name, ref))) {
+               if (current_ref->flag & REF_KNOWS_PEELED) {
+                       hashcpy(sha1, current_ref->peeled);
+                       return 0;
+               }
+               hashcpy(base, current_ref->sha1);
+               goto fallback;
+       }
+
        if (!resolve_ref(ref, base, 1, &flag))
                return -1;
 
@@ -504,9 +516,9 @@ int peel_ref(const char *ref, unsigned char *sha1)
                }
        }
 
-       /* fallback - callers should not call this for unpacked refs */
+fallback:
        o = parse_object(base);
-       if (o->type == OBJ_TAG) {
+       if (o && o->type == OBJ_TAG) {
                o = deref_tag(o, ref, 0);
                if (o) {
                        hashcpy(sha1, o->sha1);
@@ -519,7 +531,7 @@ int peel_ref(const char *ref, unsigned char *sha1)
 static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
                           void *cb_data)
 {
-       int retval;
+       int retval = 0;
        struct ref_list *packed = get_packed_refs();
        struct ref_list *loose = get_loose_refs();
 
@@ -539,15 +551,18 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
                }
                retval = do_one_ref(base, fn, trim, cb_data, entry);
                if (retval)
-                       return retval;
+                       goto end_each;
        }
 
        for (packed = packed ? packed : loose; packed; packed = packed->next) {
                retval = do_one_ref(base, fn, trim, cb_data, packed);
                if (retval)
-                       return retval;
+                       goto end_each;
        }
-       return 0;
+
+end_each:
+       current_ref = NULL;
+       return retval;
 }
 
 int head_ref(each_ref_fn fn, void *cb_data)
index ae1ef5708ccb9031cc240516a32695b2c846ed4e..7e1937286b1cabec84865c7e07e2635feb8bbc70 100644 (file)
--- a/remote.c
+++ b/remote.c
 #include "remote.h"
 #include "refs.h"
 
+struct counted_string {
+       size_t len;
+       const char *s;
+};
+struct rewrite {
+       const char *base;
+       size_t baselen;
+       struct counted_string *instead_of;
+       int instead_of_nr;
+       int instead_of_alloc;
+};
+
 static struct remote **remotes;
-static int allocated_remotes;
+static int remotes_alloc;
+static int remotes_nr;
 
 static struct branch **branches;
-static int allocated_branches;
+static int branches_alloc;
+static int branches_nr;
 
 static struct branch *current_branch;
 static const char *default_remote_name;
 
+static struct rewrite **rewrite;
+static int rewrite_alloc;
+static int rewrite_nr;
+
 #define BUF_SIZE (2048)
 static char buffer[BUF_SIZE];
 
+static const char *alias_url(const char *url)
+{
+       int i, j;
+       char *ret;
+       struct counted_string *longest;
+       int longest_i;
+
+       longest = NULL;
+       longest_i = -1;
+       for (i = 0; i < rewrite_nr; i++) {
+               if (!rewrite[i])
+                       continue;
+               for (j = 0; j < rewrite[i]->instead_of_nr; j++) {
+                       if (!prefixcmp(url, rewrite[i]->instead_of[j].s) &&
+                           (!longest ||
+                            longest->len < rewrite[i]->instead_of[j].len)) {
+                               longest = &(rewrite[i]->instead_of[j]);
+                               longest_i = i;
+                       }
+               }
+       }
+       if (!longest)
+               return url;
+
+       ret = malloc(rewrite[longest_i]->baselen +
+                    (strlen(url) - longest->len) + 1);
+       strcpy(ret, rewrite[longest_i]->base);
+       strcpy(ret + rewrite[longest_i]->baselen, url + longest->len);
+       return ret;
+}
+
 static void add_push_refspec(struct remote *remote, const char *ref)
 {
-       int nr = remote->push_refspec_nr + 1;
-       remote->push_refspec =
-               xrealloc(remote->push_refspec, nr * sizeof(char *));
-       remote->push_refspec[nr-1] = ref;
-       remote->push_refspec_nr = nr;
+       ALLOC_GROW(remote->push_refspec,
+                  remote->push_refspec_nr + 1,
+                  remote->push_refspec_alloc);
+       remote->push_refspec[remote->push_refspec_nr++] = ref;
 }
 
 static void add_fetch_refspec(struct remote *remote, const char *ref)
 {
-       int nr = remote->fetch_refspec_nr + 1;
-       remote->fetch_refspec =
-               xrealloc(remote->fetch_refspec, nr * sizeof(char *));
-       remote->fetch_refspec[nr-1] = ref;
-       remote->fetch_refspec_nr = nr;
+       ALLOC_GROW(remote->fetch_refspec,
+                  remote->fetch_refspec_nr + 1,
+                  remote->fetch_refspec_alloc);
+       remote->fetch_refspec[remote->fetch_refspec_nr++] = ref;
 }
 
 static void add_url(struct remote *remote, const char *url)
 {
-       int nr = remote->url_nr + 1;
-       remote->url =
-               xrealloc(remote->url, nr * sizeof(char *));
-       remote->url[nr-1] = url;
-       remote->url_nr = nr;
+       ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc);
+       remote->url[remote->url_nr++] = url;
+}
+
+static void add_url_alias(struct remote *remote, const char *url)
+{
+       add_url(remote, alias_url(url));
 }
 
 static struct remote *make_remote(const char *name, int len)
 {
-       int i, empty = -1;
+       struct remote *ret;
+       int i;
 
-       for (i = 0; i < allocated_remotes; i++) {
-               if (!remotes[i]) {
-                       if (empty < 0)
-                               empty = i;
-               } else {
-                       if (len ? (!strncmp(name, remotes[i]->name, len) &&
-                                  !remotes[i]->name[len]) :
-                           !strcmp(name, remotes[i]->name))
-                               return remotes[i];
-               }
+       for (i = 0; i < remotes_nr; i++) {
+               if (len ? (!strncmp(name, remotes[i]->name, len) &&
+                          !remotes[i]->name[len]) :
+                   !strcmp(name, remotes[i]->name))
+                       return remotes[i];
        }
 
-       if (empty < 0) {
-               empty = allocated_remotes;
-               allocated_remotes += allocated_remotes ? allocated_remotes : 1;
-               remotes = xrealloc(remotes,
-                                  sizeof(*remotes) * allocated_remotes);
-               memset(remotes + empty, 0,
-                      (allocated_remotes - empty) * sizeof(*remotes));
-       }
-       remotes[empty] = xcalloc(1, sizeof(struct remote));
+       ret = xcalloc(1, sizeof(struct remote));
+       ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc);
+       remotes[remotes_nr++] = ret;
        if (len)
-               remotes[empty]->name = xstrndup(name, len);
+               ret->name = xstrndup(name, len);
        else
-               remotes[empty]->name = xstrdup(name);
-       return remotes[empty];
+               ret->name = xstrdup(name);
+       return ret;
 }
 
 static void add_merge(struct branch *branch, const char *name)
 {
-       int nr = branch->merge_nr + 1;
-       branch->merge_name =
-               xrealloc(branch->merge_name, nr * sizeof(char *));
-       branch->merge_name[nr-1] = name;
-       branch->merge_nr = nr;
+       ALLOC_GROW(branch->merge_name, branch->merge_nr + 1,
+                  branch->merge_alloc);
+       branch->merge_name[branch->merge_nr++] = name;
 }
 
 static struct branch *make_branch(const char *name, int len)
 {
-       int i, empty = -1;
+       struct branch *ret;
+       int i;
        char *refname;
 
-       for (i = 0; i < allocated_branches; i++) {
-               if (!branches[i]) {
-                       if (empty < 0)
-                               empty = i;
-               } else {
-                       if (len ? (!strncmp(name, branches[i]->name, len) &&
-                                  !branches[i]->name[len]) :
-                           !strcmp(name, branches[i]->name))
-                               return branches[i];
-               }
+       for (i = 0; i < branches_nr; i++) {
+               if (len ? (!strncmp(name, branches[i]->name, len) &&
+                          !branches[i]->name[len]) :
+                   !strcmp(name, branches[i]->name))
+                       return branches[i];
        }
 
-       if (empty < 0) {
-               empty = allocated_branches;
-               allocated_branches += allocated_branches ? allocated_branches : 1;
-               branches = xrealloc(branches,
-                                  sizeof(*branches) * allocated_branches);
-               memset(branches + empty, 0,
-                      (allocated_branches - empty) * sizeof(*branches));
-       }
-       branches[empty] = xcalloc(1, sizeof(struct branch));
+       ALLOC_GROW(branches, branches_nr + 1, branches_alloc);
+       ret = xcalloc(1, sizeof(struct branch));
+       branches[branches_nr++] = ret;
        if (len)
-               branches[empty]->name = xstrndup(name, len);
+               ret->name = xstrndup(name, len);
        else
-               branches[empty]->name = xstrdup(name);
+               ret->name = xstrdup(name);
        refname = malloc(strlen(name) + strlen("refs/heads/") + 1);
        strcpy(refname, "refs/heads/");
-       strcpy(refname + strlen("refs/heads/"),
-              branches[empty]->name);
-       branches[empty]->refname = refname;
+       strcpy(refname + strlen("refs/heads/"), ret->name);
+       ret->refname = refname;
+
+       return ret;
+}
+
+static struct rewrite *make_rewrite(const char *base, int len)
+{
+       struct rewrite *ret;
+       int i;
+
+       for (i = 0; i < rewrite_nr; i++) {
+               if (len
+                   ? (len == rewrite[i]->baselen &&
+                      !strncmp(base, rewrite[i]->base, len))
+                   : !strcmp(base, rewrite[i]->base))
+                       return rewrite[i];
+       }
 
-       return branches[empty];
+       ALLOC_GROW(rewrite, rewrite_nr + 1, rewrite_alloc);
+       ret = xcalloc(1, sizeof(struct rewrite));
+       rewrite[rewrite_nr++] = ret;
+       if (len) {
+               ret->base = xstrndup(base, len);
+               ret->baselen = len;
+       }
+       else {
+               ret->base = xstrdup(base);
+               ret->baselen = strlen(base);
+       }
+       return ret;
+}
+
+static void add_instead_of(struct rewrite *rewrite, const char *instead_of)
+{
+       ALLOC_GROW(rewrite->instead_of, rewrite->instead_of_nr + 1, rewrite->instead_of_alloc);
+       rewrite->instead_of[rewrite->instead_of_nr].s = instead_of;
+       rewrite->instead_of[rewrite->instead_of_nr].len = strlen(instead_of);
+       rewrite->instead_of_nr++;
 }
 
 static void read_remotes_file(struct remote *remote)
@@ -154,7 +215,7 @@ static void read_remotes_file(struct remote *remote)
 
                switch (value_list) {
                case 0:
-                       add_url(remote, xstrdup(s));
+                       add_url_alias(remote, xstrdup(s));
                        break;
                case 1:
                        add_push_refspec(remote, xstrdup(s));
@@ -206,7 +267,7 @@ static void read_branches_file(struct remote *remote)
        } else {
                branch = "refs/heads/master";
        }
-       add_url(remote, p);
+       add_url_alias(remote, p);
        add_fetch_refspec(remote, branch);
        remote->fetch_tags = 1; /* always auto-follow */
 }
@@ -236,6 +297,19 @@ static int handle_config(const char *key, const char *value)
                }
                return 0;
        }
+       if (!prefixcmp(key, "url.")) {
+               struct rewrite *rewrite;
+               name = key + 5;
+               subkey = strrchr(name, '.');
+               if (!subkey)
+                       return 0;
+               rewrite = make_rewrite(name, subkey - name);
+               if (!strcmp(subkey, ".insteadof")) {
+                       if (!value)
+                               return config_error_nonbool(key);
+                       add_instead_of(rewrite, xstrdup(value));
+               }
+       }
        if (prefixcmp(key,  "remote."))
                return 0;
        name = key + 7;
@@ -287,6 +361,18 @@ static int handle_config(const char *key, const char *value)
        return 0;
 }
 
+static void alias_all_urls(void)
+{
+       int i, j;
+       for (i = 0; i < remotes_nr; i++) {
+               if (!remotes[i])
+                       continue;
+               for (j = 0; j < remotes[i]->url_nr; j++) {
+                       remotes[i]->url[j] = alias_url(remotes[i]->url[j]);
+               }
+       }
+}
+
 static void read_config(void)
 {
        unsigned char sha1[20];
@@ -303,6 +389,7 @@ static void read_config(void)
                        make_branch(head_ref + strlen("refs/heads/"), 0);
        }
        git_config(handle_config);
+       alias_all_urls();
 }
 
 struct refspec *parse_ref_spec(int nr_refspec, const char **refspec)
@@ -368,7 +455,7 @@ struct remote *remote_get(const char *name)
                        read_branches_file(ret);
        }
        if (!ret->url)
-               add_url(ret, name);
+               add_url_alias(ret, name);
        if (!ret->url)
                return NULL;
        ret->fetch = parse_ref_spec(ret->fetch_refspec_nr, ret->fetch_refspec);
@@ -380,7 +467,7 @@ int for_each_remote(each_remote_fn fn, void *priv)
 {
        int i, result = 0;
        read_config();
-       for (i = 0; i < allocated_remotes && !result; i++) {
+       for (i = 0; i < remotes_nr && !result; i++) {
                struct remote *r = remotes[i];
                if (!r)
                        continue;
@@ -642,9 +729,17 @@ static int match_explicit(struct ref *src, struct ref *dst,
                errs = 1;
 
        if (!dst_value) {
+               unsigned char sha1[20];
+               int flag;
+
                if (!matched_src)
                        return errs;
-               dst_value = matched_src->name;
+               dst_value = resolve_ref(matched_src->name, sha1, 1, &flag);
+               if (!dst_value ||
+                   ((flag & REF_ISSYMREF) &&
+                    prefixcmp(dst_value, "refs/heads/")))
+                       die("%s cannot be resolved to branch.",
+                           matched_src->name);
        }
 
        switch (count_refspec_match(dst_value, dst, &matched_dst)) {
index 86e036d61006a577ad091bdc30e58987871415b0..0f6033fb258c5a44971c7479e1dfe938393ce398 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -6,14 +6,17 @@ struct remote {
 
        const char **url;
        int url_nr;
+       int url_alloc;
 
        const char **push_refspec;
        struct refspec *push;
        int push_refspec_nr;
+       int push_refspec_alloc;
 
        const char **fetch_refspec;
        struct refspec *fetch;
        int fetch_refspec_nr;
+       int fetch_refspec_alloc;
 
        /*
         * -1 to never fetch tags
@@ -100,6 +103,7 @@ struct branch {
        const char **merge_name;
        struct refspec **merge;
        int merge_nr;
+       int merge_alloc;
 };
 
 struct branch *branch_get(const char *name);
index d3e8658104b63886bbade78366466eb4828efc28..84fbdd3af47c40879874d50b173ac633d279d734 100644 (file)
@@ -738,6 +738,10 @@ void init_revisions(struct rev_info *revs, const char *prefix)
        revs->commit_format = CMIT_FMT_DEFAULT;
 
        diff_setup(&revs->diffopt);
+       if (prefix && !revs->diffopt.prefix) {
+               revs->diffopt.prefix = prefix;
+               revs->diffopt.prefix_length = strlen(prefix);
+       }
 }
 
 static void add_pending_commit_list(struct rev_info *revs,
@@ -942,6 +946,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        int left = 1;
        int all_match = 0;
        int regflags = 0;
+       int fixed = 0;
 
        /* First, search for "--" */
        seen_dashdash = 0;
@@ -1238,6 +1243,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                regflags |= REG_ICASE;
                                continue;
                        }
+                       if (!strcmp(arg, "--fixed-strings") ||
+                           !strcmp(arg, "-F")) {
+                               fixed = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--all-match")) {
                                all_match = 1;
                                continue;
@@ -1293,8 +1303,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                }
        }
 
-       if (revs->grep_filter)
+       if (revs->grep_filter) {
                revs->grep_filter->regflags |= regflags;
+               revs->grep_filter->fixed = fixed;
+       }
 
        if (show_merge)
                prepare_show_merge(revs);
index b5f01f8309b6630be7169eb72f613e5deb0ebcab..c8b3b948ecc1cc8b45859a6e1ea11763addfb8b5 100644 (file)
@@ -75,7 +75,7 @@ struct rev_info {
        struct log_info *loginfo;
        int             nr, total;
        const char      *mime_boundary;
-       const char      *message_id;
+       char            *message_id;
        const char      *ref_message_id;
        const char      *add_signoff;
        const char      *extra_headers;
index 476d00c2182e3af82a0cfe495c61c9df1eb44d26..743757c36ec0e5667fd8af28800ac095f1581adb 100644 (file)
@@ -20,12 +20,19 @@ int start_command(struct child_process *cmd)
        int need_in, need_out, need_err;
        int fdin[2], fdout[2], fderr[2];
 
+       /*
+        * In case of errors we must keep the promise to close FDs
+        * that have been passed in via ->in and ->out.
+        */
+
        need_in = !cmd->no_stdin && cmd->in < 0;
        if (need_in) {
-               if (pipe(fdin) < 0)
+               if (pipe(fdin) < 0) {
+                       if (cmd->out > 0)
+                               close(cmd->out);
                        return -ERR_RUN_COMMAND_PIPE;
+               }
                cmd->in = fdin[1];
-               cmd->close_in = 1;
        }
 
        need_out = !cmd->no_stdout
@@ -35,10 +42,11 @@ int start_command(struct child_process *cmd)
                if (pipe(fdout) < 0) {
                        if (need_in)
                                close_pair(fdin);
+                       else if (cmd->in)
+                               close(cmd->in);
                        return -ERR_RUN_COMMAND_PIPE;
                }
                cmd->out = fdout[0];
-               cmd->close_out = 1;
        }
 
        need_err = !cmd->no_stderr && cmd->err < 0;
@@ -46,8 +54,12 @@ int start_command(struct child_process *cmd)
                if (pipe(fderr) < 0) {
                        if (need_in)
                                close_pair(fdin);
+                       else if (cmd->in)
+                               close(cmd->in);
                        if (need_out)
                                close_pair(fdout);
+                       else if (cmd->out)
+                               close(cmd->out);
                        return -ERR_RUN_COMMAND_PIPE;
                }
                cmd->err = fderr[0];
@@ -57,8 +69,12 @@ int start_command(struct child_process *cmd)
        if (cmd->pid < 0) {
                if (need_in)
                        close_pair(fdin);
+               else if (cmd->in)
+                       close(cmd->in);
                if (need_out)
                        close_pair(fdout);
+               else if (cmd->out)
+                       close(cmd->out);
                if (need_err)
                        close_pair(fderr);
                return -ERR_RUN_COMMAND_FORK;
@@ -120,7 +136,7 @@ int start_command(struct child_process *cmd)
 
        if (need_out)
                close(fdout[1]);
-       else if (cmd->out > 1)
+       else if (cmd->out)
                close(cmd->out);
 
        if (need_err)
@@ -157,10 +173,6 @@ static int wait_or_whine(pid_t pid)
 
 int finish_command(struct child_process *cmd)
 {
-       if (cmd->close_in)
-               close(cmd->in);
-       if (cmd->close_out)
-               close(cmd->out);
        return wait_or_whine(cmd->pid);
 }
 
index 1fc781d7668468f9e74bd430b7569dc040440ba8..debe3074b5a01fb5a19e61f07ff66c250cdc4f82 100644 (file)
@@ -14,13 +14,29 @@ enum {
 struct child_process {
        const char **argv;
        pid_t pid;
+       /*
+        * Using .in, .out, .err:
+        * - Specify 0 for no redirections (child inherits stdin, stdout,
+        *   stderr from parent).
+        * - Specify -1 to have a pipe allocated as follows:
+        *     .in: returns the writable pipe end; parent writes to it,
+        *          the readable pipe end becomes child's stdin
+        *     .out, .err: returns the readable pipe end; parent reads from
+        *          it, the writable pipe end becomes child's stdout/stderr
+        *   The caller of start_command() must close the returned FDs
+        *   after it has completed reading from/writing to it!
+        * - Specify > 0 to set a channel to a particular FD as follows:
+        *     .in: a readable FD, becomes child's stdin
+        *     .out: a writable FD, becomes child's stdout/stderr
+        *     .err > 0 not supported
+        *   The specified FD is closed by start_command(), even in case
+        *   of errors!
+        */
        int in;
        int out;
        int err;
        const char *dir;
        const char *const *env;
-       unsigned close_in:1;
-       unsigned close_out:1;
        unsigned no_stdin:1;
        unsigned no_stdout:1;
        unsigned no_stderr:1;
diff --git a/shortlog.h b/shortlog.h
new file mode 100644 (file)
index 0000000..31ff491
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef SHORTLOG_H
+#define SHORTLOG_H
+
+#include "path-list.h"
+
+struct shortlog {
+       struct path_list list;
+       int summary;
+       int wrap_lines;
+       int sort_by_number;
+       int wrap;
+       int in1;
+       int in2;
+
+       char *common_repo_prefix;
+       int email;
+       struct path_list mailmap;
+};
+
+void shortlog_init(struct shortlog *log);
+
+void shortlog_add_commit(struct shortlog *log, struct commit *commit);
+
+void shortlog_output(struct shortlog *log);
+
+#endif
diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh
new file mode 100755 (executable)
index 0000000..cd088b3
--- /dev/null
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+test_description='Various filesystem issues'
+
+. ./test-lib.sh
+
+auml=`perl -CO -e 'print pack("U",0x00E4)'`
+aumlcdiar=`perl -CO -e 'print pack("U",0x0061).pack("U",0x0308)'`
+
+test_expect_success 'see if we expect ' '
+
+       test_case=test_expect_success
+       test_unicode=test_expect_success
+       mkdir junk &&
+       echo good >junk/CamelCase &&
+       echo bad >junk/camelcase &&
+       if test "$(cat junk/CamelCase)" != good
+       then
+               test_case=test_expect_failure
+               say "will test on a case insensitive filesystem"
+       fi &&
+       rm -fr junk &&
+       mkdir junk &&
+       >junk/"$auml" &&
+       case "$(cd junk && echo *)" in
+       "$aumlcdiar")
+               test_unicode=test_expect_failure
+               say "will test on a unicode corrupting filesystem"
+               ;;
+       *)      ;;
+       esac &&
+       rm -fr junk
+'
+
+test_expect_success "setup case tests" '
+
+       touch camelcase &&
+       git add camelcase &&
+       git commit -m "initial" &&
+       git tag initial &&
+       git checkout -b topic &&
+       git mv camelcase tmp &&
+       git mv tmp CamelCase &&
+       git commit -m "rename" &&
+       git checkout -f master
+
+'
+
+$test_case 'rename (case change)' '
+
+       git mv camelcase CamelCase &&
+       git commit -m "rename"
+
+'
+
+$test_case 'merge (case change)' '
+
+       git reset --hard initial &&
+       git merge topic
+
+'
+
+test_expect_success "setup unicode normalization tests" '
+
+  test_create_repo unicode &&
+  cd unicode &&
+  touch "$aumlcdiar" &&
+  git add "$aumlcdiar" &&
+  git commit -m initial
+  git tag initial &&
+  git checkout -b topic &&
+  git mv $aumlcdiar tmp &&
+  git mv tmp "$auml" &&
+  git commit -m rename &&
+  git checkout -f master
+
+'
+
+$test_unicode 'rename (silent unicode normalization)' '
+
+ git mv "$aumlcdiar" "$auml" &&
+ git commit -m rename
+
+'
+
+$test_unicode 'merge (silent unicode normalization)' '
+
+ git reset --hard initial &&
+ git merge topic
+
+'
+
+test_done
diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh
new file mode 100755 (executable)
index 0000000..762af5f
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='test git rev-parse --parseopt'
+. ./test-lib.sh
+
+cat > expect.err <<EOF
+usage: some-command [options] <args>...
+    
+    some-command does foo and bar!
+
+    -h, --help            show the help
+    --foo                 some nifty option --foo
+    --bar ...             some cool option --bar with an argument
+
+An option group Header
+    -C [...]              option C with an optional argument
+
+Extras
+    --extra1              line above used to cause a segfault but no longer does
+
+EOF
+
+test_expect_success 'test --parseopt help output' '
+       git rev-parse --parseopt -- -h 2> output.err <<EOF
+some-command [options] <args>...
+
+some-command does foo and bar!
+--
+h,help    show the help
+
+foo       some nifty option --foo
+bar=      some cool option --bar with an argument
+
+ An option group Header
+C?        option C with an optional argument
+
+Extras
+extra1    line above used to cause a segfault but no longer does
+EOF
+       git diff expect.err output.err
+'
+
+test_done
index d21081d0f19bc52e1f1da54c220bc30a026f1093..38a90adad6874d0073059da542835df210172938 100755 (executable)
@@ -15,6 +15,9 @@ test_expect_success \
     'echo Hello > A &&
      git update-index --add A &&
      git-commit -m "Initial commit." &&
+     echo World >> A &&
+     git update-index --add A &&
+     git-commit -m "Second commit." &&
      HEAD=$(git rev-parse --verify HEAD)'
 
 test_expect_success \
@@ -171,7 +174,9 @@ test_expect_success 'test overriding tracking setup via --no-track' \
      ! test "$(git config branch.my2.merge)" = refs/heads/master'
 
 test_expect_success 'no tracking without .fetch entries' \
-    'git branch --track my6 s &&
+    'git config branch.autosetupmerge true &&
+     git branch my6 s &&
+     git config branch.automsetupmerge false &&
      test -z "$(git config branch.my6.remote)" &&
      test -z "$(git config branch.my6.merge)"'
 
@@ -192,6 +197,21 @@ test_expect_success 'test deleting branch without config' \
     'git branch my7 s &&
      test "$(git branch -d my7 2>&1)" = "Deleted branch my7."'
 
+test_expect_success 'test --track without .fetch entries' \
+    'git branch --track my8 &&
+     test "$(git config branch.my8.remote)" &&
+     test "$(git config branch.my8.merge)"'
+
+test_expect_success \
+    'branch from non-branch HEAD w/autosetupmerge=always' \
+    'git config branch.autosetupmerge always &&
+     git branch my9 HEAD^ &&
+     git config branch.autosetupmerge false'
+
+test_expect_success \
+    'branch from non-branch HEAD w/--track causes failure' \
+    '!(git branch --track my10 HEAD^)'
+
 # Keep this test last, as it changes the current branch
 cat >expect <<EOF
 0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000     branch: Created from master
index 9eec754221d85856613b01ec878ef4cb492aceb0..6b4d1c52bbf1e3edf01dd67794bf221effc581a7 100755 (executable)
@@ -245,6 +245,7 @@ format-patch --inline --stdout initial..master
 format-patch --inline --stdout --subject-prefix=TESTCASE initial..master
 config format.subjectprefix DIFFERENT_PREFIX
 format-patch --inline --stdout initial..master^^
+format-patch --stdout --cover-letter -n initial..master^
 
 diff --abbrev initial..side
 diff -r initial..side
diff --git a/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
new file mode 100644 (file)
index 0000000..0151453
--- /dev/null
@@ -0,0 +1,100 @@
+$ git format-patch --stdout --cover-letter -n initial..master^
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: C O Mitter <committer@example.com>
+Date: Mon, 26 Jun 2006 00:05:00 +0000
+Subject: [DIFFERENT_PREFIX 0/2] *** SUBJECT HERE ***
+
+*** BLURB HERE ***
+
+A U Thor (2):
+      Second
+      Third
+
+ dir/sub |    4 ++++
+ file0   |    3 +++
+ file1   |    3 +++
+ file2   |    3 ---
+ 4 files changed, 10 insertions(+), 3 deletions(-)
+ create mode 100644 file1
+ delete mode 100644 file2
+
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [DIFFERENT_PREFIX 1/2] Second
+
+This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+-- 
+g-i-t--v-e-r-s-i-o-n
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [DIFFERENT_PREFIX 2/2] Third
+
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+-- 
+g-i-t--v-e-r-s-i-o-n
+
+$
index 0a6fe53375bb26e5c3a69d503f6f13833a2020fa..16aa99dc0dbe1fe7e09aa046dfc2dc1ec652963c 100755 (executable)
@@ -88,4 +88,117 @@ test_expect_success 'replay did not screw up the log message' '
 
 '
 
+test_expect_success 'extra headers' '
+
+       git config format.headers "To: R. E. Cipient <rcipient@example.com>
+" &&
+       git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>
+" &&
+       git format-patch --stdout master..side > patch2 &&
+       sed -e "/^$/q" patch2 > hdrs2 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs2 &&
+       grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs2
+       
+'
+
+test_expect_success 'extra headers without newlines' '
+
+       git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
+       git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>" &&
+       git format-patch --stdout master..side >patch3 &&
+       sed -e "/^$/q" patch3 > hdrs3 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs3 &&
+       grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs3
+       
+'
+
+test_expect_success 'extra headers with multiple To:s' '
+
+       git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
+       git config --add format.headers "To: S. E. Cipient <scipient@example.com>" &&
+       git format-patch --stdout master..side > patch4 &&
+       sed -e "/^$/q" patch4 > hdrs4 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>,$" hdrs4 &&
+       grep "^ *S. E. Cipient <scipient@example.com>$" hdrs4
+'
+
+test_expect_success 'additional command line cc' '
+
+       git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" &&
+       git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch5 &&
+       grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch5 &&
+       grep "^ *S. E. Cipient <scipient@example.com>$" patch5
+'
+
+test_expect_success 'multiple files' '
+
+       rm -rf patches/ &&
+       git checkout side &&
+       git format-patch -o patches/ master &&
+       ls patches/0001-Side-changes-1.patch patches/0002-Side-changes-2.patch patches/0003-Side-changes-3-with-n-backslash-n-in-it.patch
+'
+
+test_expect_success 'thread' '
+
+       rm -rf patches/ &&
+       git checkout side &&
+       git format-patch --thread -o patches/ master &&
+       FIRST_MID=$(grep "Message-Id:" patches/0001-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") &&
+       for i in patches/0002-* patches/0003-*
+       do
+         grep "References: $FIRST_MID" $i &&
+         grep "In-Reply-To: $FIRST_MID" $i
+       done
+'
+
+test_expect_success 'thread in-reply-to' '
+
+       rm -rf patches/ &&
+       git checkout side &&
+       git format-patch --in-reply-to="<test.message>" --thread -o patches/ master &&
+       FIRST_MID="<test.message>" &&
+       for i in patches/*
+       do
+         grep "References: $FIRST_MID" $i &&
+         grep "In-Reply-To: $FIRST_MID" $i
+       done
+'
+
+test_expect_success 'thread cover-letter' '
+
+       rm -rf patches/ &&
+       git checkout side &&
+       git format-patch --cover-letter --thread -o patches/ master &&
+       FIRST_MID=$(grep "Message-Id:" patches/0000-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") &&
+       for i in patches/0001-* patches/0002-* patches/0003-* 
+       do
+         grep "References: $FIRST_MID" $i &&
+         grep "In-Reply-To: $FIRST_MID" $i
+       done
+'
+
+test_expect_success 'thread cover-letter in-reply-to' '
+
+       rm -rf patches/ &&
+       git checkout side &&
+       git format-patch --cover-letter --in-reply-to="<test.message>" --thread -o patches/ master &&
+       FIRST_MID="<test.message>" &&
+       for i in patches/*
+       do
+         grep "References: $FIRST_MID" $i &&
+         grep "In-Reply-To: $FIRST_MID" $i
+       done
+'
+
+test_expect_success 'excessive subject' '
+
+       rm -rf patches/ &&
+       git checkout side &&
+       for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >>file &&
+       git update-index file &&
+       git commit -m "This is an excessively long subject line for a message due to the habit some projects have of not having a short, one-line subject at the start of the commit message, but rather sticking a whole paragraph right at the start as the only thing in the commit message. It had better not become the filename for the patch." &&
+       git format-patch -o patches/ master..side &&
+       ls patches/0004-This-is-an-excessively-long-subject-line-for-a-messa.patch
+'
+
 test_done
index 67e080bdbe5a98a9c446af8aee41acc9fb9129b4..0d9cbb62615c0d94da784f63907989988b0e8151 100755 (executable)
@@ -12,6 +12,7 @@ test_expect_success setup '
        echo "         Eight SP indent" >>F &&
        echo "  HT and SP indent" >>F &&
        echo "With trailing SP " >>F &&
+       echo "Carriage ReturnQ" | tr Q "\015" >>F &&
        echo "No problem" >>F
 
 '
@@ -27,6 +28,7 @@ test_expect_success default '
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
        grep With error >/dev/null &&
+       grep Return error >/dev/null &&
        grep No normal >/dev/null
 
 '
@@ -41,6 +43,7 @@ test_expect_success 'without -trail' '
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
        grep With normal >/dev/null &&
+       grep Return normal >/dev/null &&
        grep No normal >/dev/null
 
 '
@@ -56,6 +59,7 @@ test_expect_success 'without -trail (attribute)' '
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
        grep With normal >/dev/null &&
+       grep Return normal >/dev/null &&
        grep No normal >/dev/null
 
 '
@@ -71,6 +75,7 @@ test_expect_success 'without -space' '
        grep Eight normal >/dev/null &&
        grep HT normal >/dev/null &&
        grep With error >/dev/null &&
+       grep Return error >/dev/null &&
        grep No normal >/dev/null
 
 '
@@ -86,6 +91,7 @@ test_expect_success 'without -space (attribute)' '
        grep Eight normal >/dev/null &&
        grep HT normal >/dev/null &&
        grep With error >/dev/null &&
+       grep Return error >/dev/null &&
        grep No normal >/dev/null
 
 '
@@ -101,6 +107,7 @@ test_expect_success 'with indent-non-tab only' '
        grep Eight error >/dev/null &&
        grep HT normal >/dev/null &&
        grep With normal >/dev/null &&
+       grep Return normal >/dev/null &&
        grep No normal >/dev/null
 
 '
@@ -116,6 +123,39 @@ test_expect_success 'with indent-non-tab only (attribute)' '
        grep Eight error >/dev/null &&
        grep HT normal >/dev/null &&
        grep With normal >/dev/null &&
+       grep Return normal >/dev/null &&
+       grep No normal >/dev/null
+
+'
+
+test_expect_success 'with cr-at-eol' '
+
+       rm -f .gitattributes
+       git config core.whitespace cr-at-eol
+       git diff --color >output
+       grep "$blue_grep" output >error
+       grep -v "$blue_grep" output >normal
+
+       grep Eight normal >/dev/null &&
+       grep HT error >/dev/null &&
+       grep With error >/dev/null &&
+       grep Return normal >/dev/null &&
+       grep No normal >/dev/null
+
+'
+
+test_expect_success 'with cr-at-eol (attribute)' '
+
+       git config --unset core.whitespace
+       echo "F whitespace=trailing,cr-at-eol" >.gitattributes
+       git diff --color >output
+       grep "$blue_grep" output >error
+       grep -v "$blue_grep" output >normal
+
+       grep Eight normal >/dev/null &&
+       grep HT error >/dev/null &&
+       grep With error >/dev/null &&
+       grep Return normal >/dev/null &&
        grep No normal >/dev/null
 
 '
diff --git a/t/t4105-apply-fuzz.sh b/t/t4105-apply-fuzz.sh
new file mode 100755 (executable)
index 0000000..0e8d25f
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+test_description='apply with fuzz and offset'
+
+. ./test-lib.sh
+
+dotest () {
+       name="$1" && shift &&
+       test_expect_success "$name" "
+               git checkout-index -f -q -u file &&
+               git apply $* &&
+               diff -u expect file
+       "
+}
+
+test_expect_success setup '
+
+       for i in 1 2 3 4 5 6 7 8 9 10 11 12
+       do
+               echo $i
+       done >file &&
+       git update-index --add file &&
+       for i in 1 2 3 4 5 6 7 a b c d e 8 9 10 11 12
+       do
+               echo $i
+       done >file &&
+       cat file >expect &&
+       git diff >O0.diff &&
+
+       sed -e "s/@@ -5,6 +5,11 @@/@@ -2,6 +2,11 @@/" >O1.diff O0.diff &&
+       sed -e "s/@@ -5,6 +5,11 @@/@@ -7,6 +7,11 @@/" >O2.diff O0.diff &&
+       sed -e "s/@@ -5,6 +5,11 @@/@@ -19,6 +19,11 @@/" >O3.diff O0.diff &&
+
+       sed -e "s/^ 5/ S/" >F0.diff O0.diff &&
+       sed -e "s/^ 5/ S/" >F1.diff O1.diff &&
+       sed -e "s/^ 5/ S/" >F2.diff O2.diff &&
+       sed -e "s/^ 5/ S/" >F3.diff O3.diff
+
+'
+
+dotest 'unmodified patch' O0.diff
+
+dotest 'minus offset' O1.diff
+
+dotest 'plus offset' O2.diff
+
+dotest 'big offset' O3.diff
+
+dotest 'fuzz with no offset' -C2 F0.diff
+
+dotest 'fuzz with minus offset' -C2 F1.diff
+
+dotest 'fuzz with plus offset' -C2 F2.diff
+
+dotest 'fuzz with big offset' -C2 F3.diff
+
+test_done
diff --git a/t/t4125-apply-ws-fuzz.sh b/t/t4125-apply-ws-fuzz.sh
new file mode 100755 (executable)
index 0000000..d6f15be
--- /dev/null
@@ -0,0 +1,103 @@
+#!/bin/sh
+
+test_description='applying patch that has broken whitespaces in context'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       >file &&
+       git add file &&
+
+       # file-0 is full of whitespace breakages
+       for l in a bb c d eeee f ggg h
+       do
+               echo "$l "
+       done >file-0 &&
+
+       # patch-0 creates a whitespace broken file
+       cat file-0 >file &&
+       git diff >patch-0 &&
+       git add file &&
+
+       # file-1 is still full of whitespace breakages,
+       # but has one line updated, without fixing any
+       # whitespaces.
+       # patch-1 records that change.
+       sed -e "s/d/D/" file-0 >file-1 &&
+       cat file-1 >file &&
+       git diff >patch-1 &&
+
+       # patch-all is the effect of both patch-0 and patch-1
+       >file &&
+       git add file &&
+       cat file-1 >file &&
+       git diff >patch-all &&
+
+       # patch-2 is the same as patch-1 but is based
+       # on a version that already has whitespace fixed,
+       # and does not introduce whitespace breakages.
+       sed -e "s/ $//" patch-1 >patch-2 &&
+
+       # If all whitespace breakages are fixed the contents
+       # should look like file-fixed
+       sed -e "s/ $//" file-1 >file-fixed
+
+'
+
+test_expect_success nofix '
+
+       >file &&
+       git add file &&
+
+       # Baseline.  Applying without fixing any whitespace
+       # breakages.
+       git apply --whitespace=nowarn patch-0 &&
+       git apply --whitespace=nowarn patch-1 &&
+
+       # The result should obviously match.
+       diff -u file-1 file
+'
+
+test_expect_success 'withfix (forward)' '
+
+       >file &&
+       git add file &&
+
+       # The first application will munge the context lines
+       # the second patch depends on.  We should be able to
+       # adjust and still apply.
+       git apply --whitespace=fix patch-0 &&
+       git apply --whitespace=fix patch-1 &&
+
+       diff -u file-fixed file
+'
+
+test_expect_success 'withfix (backward)' '
+
+       >file &&
+       git add file &&
+
+       # Now we have a whitespace breakages on our side.
+       git apply --whitespace=nowarn patch-0 &&
+
+       # And somebody sends in a patch based on image
+       # with whitespace already fixed.
+       git apply --whitespace=fix patch-2 &&
+
+       # The result should accept the whitespace fixed
+       # postimage.  But the line with "h" is beyond context
+       # horizon and left unfixed.
+
+       sed -e /h/d file-fixed >fixed-head &&
+       sed -e /h/d file >file-head &&
+       diff -u fixed-head file-head &&
+
+       sed -n -e /h/p file-fixed >fixed-tail &&
+       sed -n -e /h/p file >file-tail &&
+
+       ! diff -u fixed-tail file-tail
+
+'
+
+test_done
diff --git a/t/t5303-hash-object.sh b/t/t5303-hash-object.sh
new file mode 100755 (executable)
index 0000000..543c078
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+test_description=git-hash-object
+
+. ./test-lib.sh
+
+test_expect_success \
+    'git hash-object -w --stdin saves the object' \
+    'obname=$(echo foo | git hash-object -w --stdin) &&
+    obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") &&
+    test -r .git/objects/"$obpath" &&
+    rm -f .git/objects/"$obpath"'
+    
+test_expect_success \
+    'git hash-object --stdin -w saves the object' \
+    'obname=$(echo foo | git hash-object --stdin -w) &&
+    obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") &&
+    test -r .git/objects/"$obpath" &&
+    rm -f .git/objects/"$obpath"'    
+
+test_expect_success \
+    'git hash-object --stdin file1 <file0 first operates on file0, then file1' \
+    'echo foo > file1 &&
+    obname0=$(echo bar | git hash-object --stdin) &&
+    obname1=$(git hash-object file1) &&
+    obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) &&
+    obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) &&
+    test "$obname0" = "$obname0new" &&
+    test "$obname1" = "$obname1new"'
+
+test_expect_success \
+    'git hash-object refuses multiple --stdin arguments' \
+    '! git hash-object --stdin --stdin < file1'
+
+test_done
index 9d2dc33cbd0d1df19b0a9003e545104a982da694..793ffc6600202431193887a12981105c099d40df 100755 (executable)
@@ -100,6 +100,23 @@ test_expect_success 'fetch with wildcard' '
        )
 '
 
+test_expect_success 'fetch with insteadOf' '
+       mk_empty &&
+       (
+               TRASH=$(pwd) &&
+               cd testrepo &&
+               git config url./$TRASH/.insteadOf trash/
+               git config remote.up.url trash/. &&
+               git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
+               git fetch up &&
+
+               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+               test "z$r" = "z$the_commit" &&
+
+               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+       )
+'
+
 test_expect_success 'push without wildcard' '
        mk_empty &&
 
@@ -126,6 +143,20 @@ test_expect_success 'push with wildcard' '
        )
 '
 
+test_expect_success 'push with insteadOf' '
+       mk_empty &&
+       TRASH=$(pwd) &&
+       git config url./$TRASH/.insteadOf trash/ &&
+       git push trash/testrepo refs/heads/master:refs/remotes/origin/master &&
+       (
+               cd testrepo &&
+               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+               test "z$r" = "z$the_commit" &&
+
+               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+       )
+'
+
 test_expect_success 'push with matching heads' '
 
        mk_test heads/master &&
@@ -271,6 +302,49 @@ test_expect_success 'push with HEAD nonexisting at remote' '
        check_push_result $the_commit heads/local
 '
 
+test_expect_success 'push with +HEAD' '
+
+       mk_test heads/master &&
+       git checkout master &&
+       git branch -D local &&
+       git checkout -b local &&
+       git push testrepo master local &&
+       check_push_result $the_commit heads/master &&
+       check_push_result $the_commit heads/local &&
+
+       # Without force rewinding should fail
+       git reset --hard HEAD^ &&
+       ! git push testrepo HEAD &&
+       check_push_result $the_commit heads/local &&
+
+       # With force rewinding should succeed
+       git push testrepo +HEAD &&
+       check_push_result $the_first_commit heads/local
+
+'
+
+test_expect_success 'push with config remote.*.push = HEAD' '
+
+       mk_test heads/local &&
+       git checkout master &&
+       git branch -f local $the_commit &&
+       (
+               cd testrepo &&
+               git checkout local &&
+               git reset --hard $the_first_commit
+       ) &&
+       git config remote.there.url testrepo &&
+       git config remote.there.push HEAD &&
+       git config branch.master.remote there &&
+       git push &&
+       check_push_result $the_commit heads/master &&
+       check_push_result $the_first_commit heads/local
+'
+
+# clean up the cruft left with the previous one
+git config --remove-section remote.there
+git config --remove-section branch.master
+
 test_expect_success 'push with dry-run' '
 
        mk_test heads/master &&
index 86419964b441ebcc15592b872933915472c463d2..79dc58b2ce962ea133c4796d0dff66eb9684b48a 100755 (executable)
@@ -139,4 +139,24 @@ test_expect_success 'binary files cannot be merged' '
        grep "Cannot merge binary files" merge.err
 '
 
+sed -e "s/deerit.$/deerit;/" -e "s/me;$/me./" < new5.txt > new6.txt
+sed -e "s/deerit.$/deerit,/" -e "s/me;$/me,/" < new5.txt > new7.txt
+
+test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' '
+
+       ! git merge-file -p new6.txt new5.txt new7.txt > output &&
+       test 1 = $(grep ======= < output | wc -l)
+
+'
+
+sed -e 's/deerit./&\n\n\n\n/' -e "s/locavit,/locavit;/" < new6.txt > new8.txt
+sed -e 's/deerit./&\n\n\n\n/' -e "s/locavit,/locavit --/" < new7.txt > new9.txt
+
+test_expect_success 'ZEALOUS_ALNUM' '
+
+       ! git merge-file -p new8.txt new5.txt new9.txt > merge.out &&
+       test 1 = $(grep ======= < merge.out | wc -l)
+
+'
+
 test_done
diff --git a/t/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh
new file mode 100755 (executable)
index 0000000..3900a05
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test_description='subtree merge strategy'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       s="1 2 3 4 5 6 7 8"
+       for i in $s; do echo $i; done >hello &&
+       git add hello &&
+       git commit -m initial &&
+       git checkout -b side &&
+       echo >>hello world &&
+       git add hello &&
+       git commit -m second &&
+       git checkout master &&
+       for i in mundo $s; do echo $i; done >hello &&
+       git add hello &&
+       git commit -m master
+
+'
+
+test_expect_success 'subtree available and works like recursive' '
+
+       git merge -s subtree side &&
+       for i in mundo $s world; do echo $i; done >expect &&
+       diff -u expect hello
+
+'
+
+test_done
index ec71123f4be187c1399c90dc806dbae53cda5b7f..4908e878fee48069fbdc339f17ad6e865111ae00 100755 (executable)
@@ -260,7 +260,7 @@ test_expect_success 'bisect starting with a detached HEAD' '
        git checkout master^ &&
        HEAD=$(git rev-parse --verify HEAD) &&
        git bisect start &&
-       test $HEAD = $(cat .git/head-name) &&
+       test $HEAD = $(cat .git/BISECT_START) &&
        git bisect reset &&
        test $HEAD = $(git rev-parse --verify HEAD)
 
index dbf1ace29ef8ad178a0ad8539e6bde30482ee60f..63915cd87b74aaddb943f45057bda10d283b4351 100755 (executable)
@@ -83,13 +83,13 @@ test_expect_success "checkout with unrelated dirty tree without -m" '
        fill 0 1 2 3 4 5 6 7 8 >same &&
        cp same kept
        git checkout side >messages &&
-       git diff same kept
+       diff -u same kept
        (cat > messages.expect <<EOF
 M      same
 EOF
 ) &&
        touch messages.expect &&
-       git diff messages.expect messages
+       diff -u messages.expect messages
 '
 
 test_expect_success "checkout -m with dirty tree" '
@@ -103,29 +103,22 @@ test_expect_success "checkout -m with dirty tree" '
        test "$(git symbolic-ref HEAD)" = "refs/heads/side" &&
 
        (cat >expect.messages <<EOF
-Merging side with local
-Merging:
-ab76817 Side M one, D two, A three
-virtual local
-found 1 common ancestor(s):
-7329388 Initial A one, A two
-Auto-merged one
 M      one
 EOF
 ) &&
-       git diff expect.messages messages &&
+       diff -u expect.messages messages &&
 
        fill "M one" "A three" "D       two" >expect.master &&
        git diff --name-status master >current.master &&
-       diff expect.master current.master &&
+       diff -u expect.master current.master &&
 
        fill "M one" >expect.side &&
        git diff --name-status side >current.side &&
-       diff expect.side current.side &&
+       diff -u expect.side current.side &&
 
        : >expect.index &&
        git diff --cached >current.index &&
-       diff expect.index current.index
+       diff -u expect.index current.index
 '
 
 test_expect_success "checkout -m with dirty tree, renamed" '
@@ -143,7 +136,7 @@ test_expect_success "checkout -m with dirty tree, renamed" '
 
        git checkout -m renamer &&
        fill 1 3 4 5 7 8 >expect &&
-       diff expect uno &&
+       diff -u expect uno &&
        ! test -f one &&
        git diff --cached >current &&
        ! test -s current
@@ -168,7 +161,7 @@ test_expect_success 'checkout -m with merge conflict' '
        git diff master:one :3:uno |
        sed -e "1,/^@@/d" -e "/^ /d" -e "s/^-/d/" -e "s/^+/a/" >current &&
        fill d2 aT d7 aS >expect &&
-       diff current expect &&
+       diff -u current expect &&
        git diff --cached two >current &&
        ! test -s current
 '
@@ -185,7 +178,7 @@ If you want to create a new branch from this checkout, you may do so
 HEAD is now at 7329388... Initial A one, A two
 EOF
 ) &&
-       git diff messages.expect messages &&
+       diff -u messages.expect messages &&
        H=$(git rev-parse --verify HEAD) &&
        M=$(git show-ref -s --verify refs/heads/master) &&
        test "z$H" = "z$M" &&
@@ -286,4 +279,62 @@ test_expect_success 'checkout with ambiguous tag/branch names' '
 
 '
 
+test_expect_success 'switch branches while in subdirectory' '
+
+       git reset --hard &&
+       git checkout master &&
+
+       mkdir subs &&
+       (
+               cd subs &&
+               git checkout side
+       ) &&
+       ! test -f subs/one &&
+       rm -fr subs
+
+'
+
+test_expect_success 'checkout specific path while in subdirectory' '
+
+       git reset --hard &&
+       git checkout side &&
+       mkdir subs &&
+       >subs/bero &&
+       git add subs/bero &&
+       git commit -m "add subs/bero" &&
+
+       git checkout master &&
+       mkdir -p subs &&
+       (
+               cd subs &&
+               git checkout side -- bero
+       ) &&
+       test -f subs/bero
+
+'
+
+test_expect_success \
+    'checkout w/--track sets up tracking' '
+    git config branch.autosetupmerge false &&
+    git checkout master &&
+    git checkout --track -b track1 &&
+    test "$(git config branch.track1.remote)" &&
+    test "$(git config branch.track1.merge)"'
+
+test_expect_success \
+    'checkout w/autosetupmerge=always sets up tracking' '
+    git config branch.autosetupmerge always &&
+    git checkout master &&
+    git checkout -b track2 &&
+    test "$(git config branch.track2.remote)" &&
+    test "$(git config branch.track2.merge)"
+    git config branch.autosetupmerge false'
+
+test_expect_success \
+    'checkout w/--track from non-branch HEAD fails' '
+    git checkout -b delete-me master &&
+    rm .git/refs/heads/delete-me &&
+    test refs/heads/delete-me = "$(git symbolic-ref HEAD)" &&
+    !(git checkout --track -b track)'
+
 test_done
index 2efaed441d6ae238ce5d6a6105230e088d5e56ff..cbbfa9cb4986403cb214bba6c2216c85471469c9 100755 (executable)
@@ -15,16 +15,22 @@ test_expect_success \
     'Setup helper tool' \
     '(echo "#!/bin/sh"
       echo shift
+      echo output=1
+      echo "while test -f commandline\$output; do output=\$((\$output+1)); done"
       echo for a
       echo do
       echo "  echo \"!\$a!\""
-      echo "done >commandline"
-      echo "cat > msgtxt"
+      echo "done >commandline\$output"
+      echo "cat > msgtxt\$output"
       ) >fake.sendmail &&
      chmod +x ./fake.sendmail &&
      git add fake.sendmail &&
      GIT_AUTHOR_NAME="A" git commit -a -m "Second."'
 
+clean_fake_sendmail() {
+       rm -f commandline* msgtxt*
+}
+
 test_expect_success 'Extract patches' '
     patches=`git format-patch -n HEAD^1`
 '
@@ -39,7 +45,7 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'Verify commandline' \
-    'diff commandline expected'
+    'diff commandline1 expected'
 
 cat >expected-show-all-headers <<\EOF
 0001-Second.patch
@@ -82,7 +88,7 @@ z8=zzzzzzzz
 z64=$z8$z8$z8$z8$z8$z8$z8$z8
 z512=$z64$z64$z64$z64$z64$z64$z64$z64
 test_expect_success 'reject long lines' '
-       rm -f commandline &&
+       clean_fake_sendmail &&
        cp $patches longline.patch &&
        echo $z512$z512 >>longline.patch &&
        ! git send-email \
@@ -95,7 +101,7 @@ test_expect_success 'reject long lines' '
 '
 
 test_expect_success 'no patch was sent' '
-       ! test -e commandline
+       ! test -e commandline1
 '
 
 test_expect_success 'allow long lines with --no-validate' '
@@ -109,6 +115,7 @@ test_expect_success 'allow long lines with --no-validate' '
 '
 
 test_expect_success 'Invalid In-Reply-To' '
+       clean_fake_sendmail &&
        git send-email \
                --from="Example <nobody@example.com>" \
                --to=nobody@example.com \
@@ -116,17 +123,47 @@ test_expect_success 'Invalid In-Reply-To' '
                --smtp-server="$(pwd)/fake.sendmail" \
                $patches
                2>errors
-       ! grep "^In-Reply-To: < *>" msgtxt
+       ! grep "^In-Reply-To: < *>" msgtxt1
 '
 
 test_expect_success 'Valid In-Reply-To when prompting' '
+       clean_fake_sendmail &&
        (echo "From Example <from@example.com>"
         echo "To Example <to@example.com>"
         echo ""
        ) | env GIT_SEND_EMAIL_NOTTY=1 git send-email \
                --smtp-server="$(pwd)/fake.sendmail" \
                $patches 2>errors &&
-       ! grep "^In-Reply-To: < *>" msgtxt
+       ! grep "^In-Reply-To: < *>" msgtxt1
+'
+
+test_expect_success 'setup fake editor' '
+       (echo "#!/bin/sh" &&
+        echo "echo fake edit >>\$1"
+       ) >fake-editor &&
+       chmod +x fake-editor
+'
+
+test_expect_success '--compose works' '
+       clean_fake_sendmail &&
+       echo y | \
+               GIT_EDITOR=$(pwd)/fake-editor \
+               GIT_SEND_EMAIL_NOTTY=1 \
+               git send-email \
+               --compose --subject foo \
+               --from="Example <nobody@example.com>" \
+               --to=nobody@example.com \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               $patches \
+               2>errors
+'
+
+test_expect_success 'first message is compose text' '
+       grep "^fake edit" msgtxt1
+'
+
+test_expect_success 'second message is patch' '
+       grep "Subject:.*Second" msgtxt2
 '
 
 test_done
diff --git a/thread-utils.c b/thread-utils.c
new file mode 100644 (file)
index 0000000..55e7e29
--- /dev/null
@@ -0,0 +1,48 @@
+#include "cache.h"
+
+#ifdef _WIN32
+#  define WIN32_LEAN_AND_MEAN
+#  include <windows.h>
+#elif defined(hpux) || defined(__hpux) || defined(_hpux)
+#  include <sys/pstat.h>
+#endif
+
+/*
+ * By doing this in two steps we can at least get
+ * the function to be somewhat coherent, even
+ * with this disgusting nest of #ifdefs.
+ */
+#ifndef _SC_NPROCESSORS_ONLN
+#  ifdef _SC_NPROC_ONLN
+#    define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN
+#  elif defined _SC_CRAY_NCPU
+#    define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU
+#  endif
+#endif
+
+int online_cpus(void)
+{
+#ifdef _SC_NPROCESSORS_ONLN
+       long ncpus;
+#endif
+
+#ifdef _WIN32
+       SYSTEM_INFO info;
+       GetSystemInfo(&info);
+
+       if ((int)info.dwNumberOfProcessors > 0)
+               return (int)info.dwNumberOfProcessors;
+#elif defined(hpux) || defined(__hpux) || defined(_hpux)
+       struct pst_dynamic psd;
+
+       if (!pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0))
+               return (int)psd.psd_proc_cnt;
+#endif
+
+#ifdef _SC_NPROCESSORS_ONLN
+       if ((ncpus = (long)sysconf(_SC_NPROCESSORS_ONLN)) > 0)
+               return (int)ncpus;
+#endif
+
+       return 1;
+}
diff --git a/thread-utils.h b/thread-utils.h
new file mode 100644 (file)
index 0000000..cce4b77
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef THREAD_COMPAT_H
+#define THREAD_COMPAT_H
+
+extern int online_cpus(void);
+
+#endif /* THREAD_COMPAT_H */
index ec558f9005fab372f9bf62d8c3df9ea34f5222bb..3e448d8974eb6d738fec2c35cc5a9ffbc8764411 100644 (file)
@@ -85,6 +85,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                int any_dirs = 0;
                char *cache_name;
                int ce_stage;
+               int skip_entry = 0;
 
                /* Find the first name in the input. */
 
@@ -122,13 +123,13 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
 
 #if DBRT_DEBUG > 1
                if (first)
-                       printf("index %s\n", first);
+                       fprintf(stderr, "index %s\n", first);
 #endif
                for (i = 0; i < len; i++) {
                        if (!posns[i] || posns[i] == df_conflict_list)
                                continue;
 #if DBRT_DEBUG > 1
-                       printf("%d %s\n", i + 1, posns[i]->name);
+                       fprintf(stderr, "%d %s\n", i + 1, posns[i]->name);
 #endif
                        if (!first || entcmp(first, firstdir,
                                             posns[i]->name,
@@ -153,6 +154,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                        any_files = 1;
                        src[0] = active_cache[o->pos];
                        remove = o->pos;
+                       if (o->skip_unmerged && ce_stage(src[0]))
+                               skip_entry = 1;
                }
 
                for (i = 0; i < len; i++) {
@@ -181,6 +184,12 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                                continue;
                        }
 
+                       if (skip_entry) {
+                               subposns[i] = df_conflict_list;
+                               posns[i] = posns[i]->next;
+                               continue;
+                       }
+
                        if (!o->merge)
                                ce_stage = 0;
                        else if (i + 1 < o->head_idx)
@@ -205,23 +214,31 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                        posns[i] = posns[i]->next;
                }
                if (any_files) {
-                       if (o->merge) {
+                       if (skip_entry) {
+                               o->pos++;
+                               while (o->pos < active_nr &&
+                                      !strcmp(active_cache[o->pos]->name,
+                                              src[0]->name))
+                                       o->pos++;
+                       } else if (o->merge) {
                                int ret;
 
 #if DBRT_DEBUG > 1
-                               printf("%s:\n", first);
+                               fprintf(stderr, "%s:\n", first);
                                for (i = 0; i < src_size; i++) {
-                                       printf(" %d ", i);
+                                       fprintf(stderr, " %d ", i);
                                        if (src[i])
-                                               printf("%s\n", sha1_to_hex(src[i]->sha1));
+                                               fprintf(stderr, "%06x %s\n", src[i]->ce_mode, sha1_to_hex(src[i]->sha1));
                                        else
-                                               printf("\n");
+                                               fprintf(stderr, "\n");
                                }
 #endif
                                ret = o->fn(src, o, remove);
+                               if (ret < 0)
+                                       return ret;
 
 #if DBRT_DEBUG > 1
-                               printf("Added %d entries\n", ret);
+                               fprintf(stderr, "Added %d entries\n", ret);
 #endif
                                o->pos += ret;
                        } else {
@@ -286,34 +303,36 @@ static void unlink_entry(char *name, char *last_symlink)
 }
 
 static struct checkout state;
-static void check_updates(struct cache_entry **src, int nr,
-                       struct unpack_trees_options *o)
+static void check_updates(struct unpack_trees_options *o)
 {
        unsigned cnt = 0, total = 0;
        struct progress *progress = NULL;
        char last_symlink[PATH_MAX];
+       int i;
 
        if (o->update && o->verbose_update) {
-               for (total = cnt = 0; cnt < nr; cnt++) {
-                       struct cache_entry *ce = src[cnt];
+               for (total = cnt = 0; cnt < active_nr; cnt++) {
+                       struct cache_entry *ce = active_cache[cnt];
                        if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
                                total++;
                }
 
                progress = start_progress_delay("Checking out files",
-                                               total, 50, 2);
+                                               total, 50, 1);
                cnt = 0;
        }
 
        *last_symlink = '\0';
-       while (nr--) {
-               struct cache_entry *ce = *src++;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
 
                if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
                        display_progress(progress, ++cnt);
                if (ce->ce_flags & CE_REMOVE) {
                        if (o->update)
                                unlink_entry(ce->name, last_symlink);
+                       remove_cache_entry_at(i);
+                       i--;
                        continue;
                }
                if (ce->ce_flags & CE_UPDATE) {
@@ -354,23 +373,34 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                        posns[i] = create_tree_entry_list(t+i);
 
                if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "",
-                                    o, &df_conflict_list))
+                                    o, &df_conflict_list)) {
+                       if (o->gently) {
+                               discard_cache();
+                               read_cache();
+                       }
                        return -1;
+               }
        }
 
-       if (o->trivial_merges_only && o->nontrivial_merge)
-               die("Merge requires file-level merging");
+       if (o->trivial_merges_only && o->nontrivial_merge) {
+               if (o->gently) {
+                       discard_cache();
+                       read_cache();
+               }
+               return o->gently ? -1 :
+                       error("Merge requires file-level merging");
+       }
 
-       check_updates(active_cache, active_nr, o);
+       check_updates(o);
        return 0;
 }
 
 /* Here come the merge functions */
 
-static void reject_merge(struct cache_entry *ce)
+static int reject_merge(struct cache_entry *ce)
 {
-       die("Entry '%s' would be overwritten by merge. Cannot merge.",
-           ce->name);
+       return error("Entry '%s' would be overwritten by merge. Cannot merge.",
+                    ce->name);
 }
 
 static int same(struct cache_entry *a, struct cache_entry *b)
@@ -388,18 +418,18 @@ static int same(struct cache_entry *a, struct cache_entry *b)
  * When a CE gets turned into an unmerged entry, we
  * want it to be up-to-date
  */
-static void verify_uptodate(struct cache_entry *ce,
+static int verify_uptodate(struct cache_entry *ce,
                struct unpack_trees_options *o)
 {
        struct stat st;
 
        if (o->index_only || o->reset)
-               return;
+               return 0;
 
        if (!lstat(ce->name, &st)) {
                unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
                if (!changed)
-                       return;
+                       return 0;
                /*
                 * NEEDSWORK: the current default policy is to allow
                 * submodule to be out of sync wrt the supermodule
@@ -408,12 +438,13 @@ static void verify_uptodate(struct cache_entry *ce,
                 * checked out.
                 */
                if (S_ISGITLINK(ce->ce_mode))
-                       return;
+                       return 0;
                errno = 0;
        }
        if (errno == ENOENT)
-               return;
-       die("Entry '%s' not uptodate. Cannot merge.", ce->name);
+               return 0;
+       return o->gently ? -1 :
+               error("Entry '%s' not uptodate. Cannot merge.", ce->name);
 }
 
 static void invalidate_ce_path(struct cache_entry *ce)
@@ -479,7 +510,8 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
                 * ce->name is an entry in the subdirectory.
                 */
                if (!ce_stage(ce)) {
-                       verify_uptodate(ce, o);
+                       if (verify_uptodate(ce, o))
+                               return -1;
                        ce->ce_flags |= CE_REMOVE;
                }
                cnt++;
@@ -498,8 +530,9 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
                d.exclude_per_dir = o->dir->exclude_per_dir;
        i = read_directory(&d, ce->name, pathbuf, namelen+1, NULL);
        if (i)
-               die("Updating '%s' would lose untracked files in it",
-                   ce->name);
+               return o->gently ? -1 :
+                       error("Updating '%s' would lose untracked files in it",
+                             ce->name);
        free(pathbuf);
        return cnt;
 }
@@ -508,16 +541,16 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
  * We do not want to remove or overwrite a working tree file that
  * is not tracked, unless it is ignored.
  */
-static void verify_absent(struct cache_entry *ce, const char *action,
-               struct unpack_trees_options *o)
+static int verify_absent(struct cache_entry *ce, const char *action,
+                        struct unpack_trees_options *o)
 {
        struct stat st;
 
        if (o->index_only || o->reset || !o->update)
-               return;
+               return 0;
 
        if (has_symlink_leading_path(ce->name, NULL))
-               return;
+               return 0;
 
        if (!lstat(ce->name, &st)) {
                int cnt;
@@ -528,7 +561,7 @@ static void verify_absent(struct cache_entry *ce, const char *action,
                         * ce->name is explicitly excluded, so it is Ok to
                         * overwrite it.
                         */
-                       return;
+                       return 0;
                if (S_ISDIR(st.st_mode)) {
                        /*
                         * We are checking out path "foo" and
@@ -557,7 +590,7 @@ static void verify_absent(struct cache_entry *ce, const char *action,
                         * deleted entries here.
                         */
                        o->pos += cnt;
-                       return;
+                       return 0;
                }
 
                /*
@@ -569,12 +602,14 @@ static void verify_absent(struct cache_entry *ce, const char *action,
                if (0 <= cnt) {
                        struct cache_entry *ce = active_cache[cnt];
                        if (ce->ce_flags & CE_REMOVE)
-                               return;
+                               return 0;
                }
 
-               die("Untracked working tree file '%s' "
-                   "would be %s by merge.", ce->name, action);
+               return o->gently ? -1 :
+                       error("Untracked working tree file '%s' "
+                             "would be %s by merge.", ce->name, action);
        }
+       return 0;
 }
 
 static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
@@ -590,14 +625,16 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
                 * a match.
                 */
                if (same(old, merge)) {
-                       memcpy(merge, old, offsetof(struct cache_entry, name));
+                       copy_cache_entry(merge, old);
                } else {
-                       verify_uptodate(old, o);
+                       if (verify_uptodate(old, o))
+                               return -1;
                        invalidate_ce_path(old);
                }
        }
        else {
-               verify_absent(merge, "overwritten", o);
+               if (verify_absent(merge, "overwritten", o))
+                       return -1;
                invalidate_ce_path(merge);
        }
 
@@ -609,10 +646,12 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
 static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
                struct unpack_trees_options *o)
 {
-       if (old)
-               verify_uptodate(old, o);
-       else
-               verify_absent(ce, "removed", o);
+       if (old) {
+               if (verify_uptodate(old, o))
+                       return -1;
+       } else
+               if (verify_absent(ce, "removed", o))
+                       return -1;
        ce->ce_flags |= CE_REMOVE;
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
        invalidate_ce_path(ce);
@@ -700,16 +739,15 @@ int threeway_merge(struct cache_entry **stages,
        /* #14, #14ALT, #2ALT */
        if (remote && !df_conflict_head && head_match && !remote_match) {
                if (index && !same(index, remote) && !same(index, head))
-                       reject_merge(index);
+                       return o->gently ? -1 : reject_merge(index);
                return merged_entry(remote, index, o);
        }
        /*
         * If we have an entry in the index cache, then we want to
         * make sure that it matches head.
         */
-       if (index && !same(index, head)) {
-               reject_merge(index);
-       }
+       if (index && !same(index, head))
+               return o->gently ? -1 : reject_merge(index);
 
        if (head) {
                /* #5ALT, #15 */
@@ -759,8 +797,10 @@ int threeway_merge(struct cache_entry **stages,
                        remove_entry(remove);
                        if (index)
                                return deleted_entry(index, index, o);
-                       else if (ce && !head_deleted)
-                               verify_absent(ce, "removed", o);
+                       else if (ce && !head_deleted) {
+                               if (verify_absent(ce, "removed", o))
+                                       return -1;
+                       }
                        return 0;
                }
                /*
@@ -776,7 +816,8 @@ int threeway_merge(struct cache_entry **stages,
         * conflict resolution files.
         */
        if (index) {
-               verify_uptodate(index, o);
+               if (verify_uptodate(index, o))
+                       return -1;
        }
 
        remove_entry(remove);
@@ -856,11 +897,11 @@ int twoway_merge(struct cache_entry **src,
                        /* all other failures */
                        remove_entry(remove);
                        if (oldtree)
-                               reject_merge(oldtree);
+                               return o->gently ? -1 : reject_merge(oldtree);
                        if (current)
-                               reject_merge(current);
+                               return o->gently ? -1 : reject_merge(current);
                        if (newtree)
-                               reject_merge(newtree);
+                               return o->gently ? -1 : reject_merge(newtree);
                        return -1;
                }
        }
@@ -887,7 +928,8 @@ int bind_merge(struct cache_entry **src,
                return error("Cannot do a bind merge of %d trees\n",
                             o->merge_size);
        if (a && old)
-               die("Entry '%s' overlaps.  Cannot bind.", a->name);
+               return o->gently ? -1 :
+                       error("Entry '%s' overlaps.  Cannot bind.", a->name);
        if (!a)
                return keep_entry(old, o);
        else
index 197a0044aa71fc9ca310b36b279b8382487a40a7..a2df544d040adc21f7d854ad50c53e61cf74c9ae 100644 (file)
@@ -16,6 +16,8 @@ struct unpack_trees_options {
        int trivial_merges_only;
        int verbose_update;
        int aggressive;
+       int skip_unmerged;
+       int gently;
        const char *prefix;
        int pos;
        struct dir_struct *dir;
diff --git a/ws.c b/ws.c
index d09b9df89a7e19367640d4c6ad64ff828d01d26f..ba7e834ca819b1d2ccce6cf125aa9f34efea8d5c 100644 (file)
--- a/ws.c
+++ b/ws.c
@@ -14,6 +14,7 @@ static struct whitespace_rule {
        { "trailing-space", WS_TRAILING_SPACE },
        { "space-before-tab", WS_SPACE_BEFORE_TAB },
        { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB },
+       { "cr-at-eol", WS_CR_AT_EOL },
 };
 
 unsigned parse_whitespace_rule(const char *string)
@@ -124,6 +125,7 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
        int written = 0;
        int trailing_whitespace = -1;
        int trailing_newline = 0;
+       int trailing_carriage_return = 0;
        int i;
 
        /* Logic is simpler if we temporarily ignore the trailing newline. */
@@ -131,6 +133,11 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
                trailing_newline = 1;
                len--;
        }
+       if ((ws_rule & WS_CR_AT_EOL) &&
+           len > 0 && line[len - 1] == '\r') {
+               trailing_carriage_return = 1;
+               len--;
+       }
 
        /* Check for trailing whitespace. */
        if (ws_rule & WS_TRAILING_SPACE) {
@@ -176,8 +183,10 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
        }
 
        if (stream) {
-               /* Now the rest of the line starts at written.
-                * The non-highlighted part ends at trailing_whitespace. */
+               /*
+                * Now the rest of the line starts at "written".
+                * The non-highlighted part ends at "trailing_whitespace".
+                */
                if (trailing_whitespace == -1)
                        trailing_whitespace = len;
 
@@ -196,8 +205,114 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
                            len - trailing_whitespace, 1, stream);
                        fputs(reset, stream);
                }
+               if (trailing_carriage_return)
+                       fputc('\r', stream);
                if (trailing_newline)
                        fputc('\n', stream);
        }
        return result;
 }
+
+/* Copy the line to the buffer while fixing whitespaces */
+int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *error_count)
+{
+       /*
+        * len is number of bytes to be copied from src, starting
+        * at src.  Typically src[len-1] is '\n', unless this is
+        * the incomplete last line.
+        */
+       int i;
+       int add_nl_to_tail = 0;
+       int add_cr_to_tail = 0;
+       int fixed = 0;
+       int last_tab_in_indent = -1;
+       int last_space_in_indent = -1;
+       int need_fix_leading_space = 0;
+       char *buf;
+
+       /*
+        * Strip trailing whitespace
+        */
+       if ((ws_rule & WS_TRAILING_SPACE) &&
+           (2 <= len && isspace(src[len-2]))) {
+               if (src[len - 1] == '\n') {
+                       add_nl_to_tail = 1;
+                       len--;
+                       if (1 < len && src[len - 1] == '\r') {
+                               add_cr_to_tail = !!(ws_rule & WS_CR_AT_EOL);
+                               len--;
+                       }
+               }
+               if (0 < len && isspace(src[len - 1])) {
+                       while (0 < len && isspace(src[len-1]))
+                               len--;
+                       fixed = 1;
+               }
+       }
+
+       /*
+        * Check leading whitespaces (indent)
+        */
+       for (i = 0; i < len; i++) {
+               char ch = src[i];
+               if (ch == '\t') {
+                       last_tab_in_indent = i;
+                       if ((ws_rule & WS_SPACE_BEFORE_TAB) &&
+                           0 <= last_space_in_indent)
+                           need_fix_leading_space = 1;
+               } else if (ch == ' ') {
+                       last_space_in_indent = i;
+                       if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
+                           8 <= i - last_tab_in_indent)
+                               need_fix_leading_space = 1;
+               } else
+                       break;
+       }
+
+       buf = dst;
+       if (need_fix_leading_space) {
+               /* Process indent ourselves */
+               int consecutive_spaces = 0;
+               int last = last_tab_in_indent + 1;
+
+               if (ws_rule & WS_INDENT_WITH_NON_TAB) {
+                       /* have "last" point at one past the indent */
+                       if (last_tab_in_indent < last_space_in_indent)
+                               last = last_space_in_indent + 1;
+                       else
+                               last = last_tab_in_indent + 1;
+               }
+
+               /*
+                * between src[0..last-1], strip the funny spaces,
+                * updating them to tab as needed.
+                */
+               for (i = 0; i < last; i++) {
+                       char ch = src[i];
+                       if (ch != ' ') {
+                               consecutive_spaces = 0;
+                               *dst++ = ch;
+                       } else {
+                               consecutive_spaces++;
+                               if (consecutive_spaces == 8) {
+                                       *dst++ = '\t';
+                                       consecutive_spaces = 0;
+                               }
+                       }
+               }
+               while (0 < consecutive_spaces--)
+                       *dst++ = ' ';
+               len -= last;
+               src += last;
+               fixed = 1;
+       }
+
+       memcpy(dst, src, len);
+       if (add_cr_to_tail)
+               dst[len++] = '\r';
+       if (add_nl_to_tail)
+               dst[len++] = '\n';
+       if (fixed && error_count)
+               (*error_count)++;
+       return dst + len - buf;
+}
index c00ddaa6e987407743d2c8877f9ca6e772f89c86..413082e1fdf537d230a0f58940cee7466b965d0e 100644 (file)
@@ -53,6 +53,7 @@ extern "C" {
 #define XDL_MERGE_MINIMAL 0
 #define XDL_MERGE_EAGER 1
 #define XDL_MERGE_ZEALOUS 2
+#define XDL_MERGE_ZEALOUS_ALNUM 3
 
 typedef struct s_mmfile {
        char *ptr;
index b83b3348cc3aab66b13cb565a0a0fabaef4b689b..82b3573e7ada8c6df13ac24a78650b80af91ea73 100644 (file)
@@ -248,10 +248,76 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
        return 0;
 }
 
+static int line_contains_alnum(const char *ptr, long size)
+{
+       while (size--)
+               if (isalnum(*(ptr++)))
+                       return 1;
+       return 0;
+}
+
+static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)
+{
+       for (; chg; chg--, i++)
+               if (line_contains_alnum(xe->xdf2.recs[i]->ptr,
+                               xe->xdf2.recs[i]->size))
+                       return 1;
+       return 0;
+}
+
+/*
+ * This function merges m and m->next, marking everything between those hunks
+ * as conflicting, too.
+ */
+static void xdl_merge_two_conflicts(xdmerge_t *m)
+{
+       xdmerge_t *next_m = m->next;
+       m->chg1 = next_m->i1 + next_m->chg1 - m->i1;
+       m->chg2 = next_m->i2 + next_m->chg2 - m->i2;
+       m->next = next_m->next;
+       free(next_m);
+}
+
+/*
+ * If there are less than 3 non-conflicting lines between conflicts,
+ * it appears simpler -- because it takes up less (or as many) lines --
+ * if the lines are moved into the conflicts.
+ */
+static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m,
+                                     int simplify_if_no_alnum)
+{
+       int result = 0;
+
+       if (!m)
+               return result;
+       for (;;) {
+               xdmerge_t *next_m = m->next;
+               int begin, end;
+
+               if (!next_m)
+                       return result;
+
+               begin = m->i1 + m->chg1;
+               end = next_m->i1;
+
+               if (m->mode != 0 || next_m->mode != 0 ||
+                   (end - begin > 3 &&
+                    (!simplify_if_no_alnum ||
+                     lines_contain_alnum(xe1, begin, end - begin)))) {
+                       m = next_m;
+               } else {
+                       result++;
+                       xdl_merge_two_conflicts(m);
+               }
+       }
+}
+
 /*
  * level == 0: mark all overlapping changes as conflict
  * level == 1: mark overlapping changes as conflict only if not identical
  * level == 2: analyze non-identical changes for minimal conflict set
+ * level == 3: analyze non-identical changes for minimal conflict set, but
+ *             treat hunks not containing any letter or number as conflicting
  *
  * returns < 0 on error, == 0 for no conflicts, else number of conflicts
  */
@@ -355,7 +421,9 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
        if (!changes)
                changes = c;
        /* refine conflicts */
-       if (level > 1 && xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0) {
+       if (level > 1 &&
+           (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 ||
+            xdl_simplify_non_conflicts(xe1, changes, level > 2) < 0)) {
                xdl_cleanup_merge(changes);
                return -1;
        }