Merge branch 'rs/archive'
authorJunio C Hamano <gitster@pobox.com>
Thu, 17 Jul 2008 00:12:18 +0000 (17:12 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 17 Jul 2008 00:12:18 +0000 (17:12 -0700)
* rs/archive:
archive: remove extra arguments parsing code
archive: unify file attribute handling
archive: centralize archive entry writing
archive: add baselen member to struct archiver_args
add context pointer to read_tree_recursive()
archive: remove args member from struct archiver

103 files changed:
Documentation/RelNotes-1.5.6.4.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.0.txt
Documentation/SubmittingPatches
Documentation/git-add.txt
Documentation/git-am.txt
Documentation/git-branch.txt
Documentation/git-cherry-pick.txt
Documentation/git-merge.txt
Documentation/git-rebase.txt
Documentation/git-revert.txt
Documentation/git-submodule.txt
Documentation/gittutorial.txt
Documentation/pretty-formats.txt
Documentation/user-manual.txt
Makefile
alias.c
attr.c
branch.c
builtin-blame.c
builtin-branch.c
builtin-commit-tree.c
builtin-commit.c
builtin-describe.c
builtin-fmt-merge-msg.c
builtin-grep.c
builtin-init-db.c
builtin-mailinfo.c
builtin-merge-recursive.c
builtin-merge.c [new file with mode: 0644]
builtin-read-tree.c
builtin-remote.c
builtin-rerere.c
builtin-shortlog.c
builtin-tag.c
builtin.h
cache-tree.c
cache-tree.h
cache.h
commit.c
commit.h
compat/mingw.c
compat/mingw.h
config.c
contrib/completion/git-completion.bash
contrib/emacs/git.el
contrib/examples/git-merge.sh [new file with mode: 0755]
convert.c
diff-lib.c
diff.c
diff.h
exec_cmd.c
exec_cmd.h
git-add--interactive.perl
git-am.sh
git-compat-util.h
git-cvsserver.perl
git-gui/git-gui.sh
git-gui/lib/merge.tcl
git-merge.sh [deleted file]
git-quiltimport.sh
git-rebase--interactive.sh
git-rebase.sh
git-submodule.sh
git-svn.perl
git.c
help.c
index-pack.c
parse-options.c
parse-options.h
pretty.c
read-cache.c
rerere.c [new file with mode: 0644]
rerere.h [new file with mode: 0644]
revision.c
sha1_file.c
shortlog.h
t/Makefile
t/aggregate-results.sh
t/lib-git-svn.sh
t/t3401-rebase-partial.sh
t/t3404-rebase-interactive.sh
t/t3407-rebase-abort.sh
t/t4150-am.sh
t/t4200-rerere.sh
t/t5100-mailinfo.sh
t/t5100/0010 [new file with mode: 0644]
t/t5100/info0010 [new file with mode: 0644]
t/t5100/msg0010 [new file with mode: 0644]
t/t5100/patch0010 [new file with mode: 0644]
t/t5100/sample.mbox
t/t6008-rev-list-submodule.sh
t/t6011-rev-list-with-bad-commit.sh [new file with mode: 0755]
t/t6021-merge-criss-cross.sh
t/t7600-merge.sh
t/t7601-merge-pull-config.sh [new file with mode: 0755]
t/t7602-merge-octopus-many.sh [new file with mode: 0755]
t/t7603-merge-reduce-heads.sh [new file with mode: 0755]
t/t7604-merge-custom-message.sh [new file with mode: 0755]
t/t7605-merge-resolve.sh [new file with mode: 0755]
t/t9106-git-svn-commit-diff-clobber.sh
t/t9110-git-svn-use-svm-props.sh
t/t9600-cvsimport.sh
tree-diff.c
diff --git a/Documentation/RelNotes-1.5.6.4.txt b/Documentation/RelNotes-1.5.6.4.txt
new file mode 100644 (file)
index 0000000..1304188
--- /dev/null
@@ -0,0 +1,43 @@
+GIT v1.5.6.4 Release Notes
+==========================
+
+Fixes since v1.5.6.3
+--------------------
+
+* Various commands could overflow its internal buffer on a platform
+  with small PATH_MAX value in a repository that has contents with
+  long pathnames.
+
+* There wasn't a way to make --pretty=format:%<> specifiers to honor
+  .mailmap name rewriting for authors and committers.  Now you can with
+  %aN and %cN.
+
+* Bash completion wasted too many cycles; this has been optimized to be
+  usable again.
+
+* Bash completion lost ref part when completing something like "git show
+  pu:Makefile".
+
+* "git-cvsserver" did not clean up its temporary working area after annotate
+  request.
+
+* "git-daemon" called syslog() from its signal handler, which was a
+  no-no.
+
+* "git-fetch" into an empty repository used to remind that the fetch will
+   be huge by saying "no common commits", but this was an unnecessary
+   noise; it is already known by the user anyway.
+
+* "git-mailinfo" (hence "git-am") did not correctly handle in-body [PATCH]
+  line to override the commit title taken from the mail Subject header.
+
+* "git-rebase -i -p" lost parents that are not involved in the history
+  being rewritten.
+
+Contains other various documentation fixes.
+
+--
+exec >/var/tmp/1
+echo O=$(git describe maint)
+O=v1.5.6.3-21-gebcce31
+git shortlog --no-merges $O..maint
index 9125ee01a6c4d4cb6d9104b2ac008b05b92e3a3a..89ea1e9385bdea0ec42891c1f5ac7763cdbf8a73 100644 (file)
@@ -110,6 +110,9 @@ Updates since v1.5.6
 * "git-add -i" has a new action 'e/dit' to allow you edit the patch hunk
   manually.
 
+* git-am records the original tip of the branch in ORIG_HEAD before it
+  starts applying patches.
+
 * git-apply can handle a patch that touches the same path more than once
   much better than before.
 
@@ -126,6 +129,14 @@ Updates since v1.5.6
   similar to the way git-checkout reports by how many commits your branch
   is ahead/behind.
 
+* git-branch's --contains option used to always require a commit parameter
+  to limit the branches with; it now defaults to list branches that
+  contains HEAD if this parameter is omitted.
+
+* git-branch's --merged and --no-merged option used to always limit the
+  branches relative to the HEAD, but they can now take an optional commit
+  argument that is used in place of HEAD.
+
 * git-bundle can read the revision arguments from the standard input.
 
 * git-cherry-pick can replay a root commit now.
@@ -144,6 +155,9 @@ Updates since v1.5.6
 * fast-export learned to export and import marks file; this can be used to
   interface with fast-import incrementally.
 
+* git-rebase records the original tip of branch in ORIG_HEAD before it is
+  rewound.
+
 * "git rerere" can be told to update the index with auto-reused resolution
   with rerere.autoupdate configuration variable.
 
@@ -152,6 +166,8 @@ Updates since v1.5.6
 
 * git-send-mail can talk not just over SSL but over TLS now.
 
+* git-shortlog honors custom output format specified with "--pretty=format:".
+
 * "git-stash save" learned --keep-index option.  This lets you stash away the
   local changes and bring the changes staged in the index to your working
   tree for examination and testing.
@@ -185,6 +201,6 @@ this release, unless otherwise noted.
 
 ---
 exec >/var/tmp/1
-O=v1.5.6.3-315-g10ce020
+O=v1.5.6.3-350-g499027b
 echo O=$(git describe refs/heads/master)
 git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
index b1164753e13dbe015d073346bc0b7c992532efe8..fdfa5364414358e006ac20a20672f6c4408cde5d 100644 (file)
@@ -301,7 +301,7 @@ If it does not apply correctly, there can be various reasons.
   patch appropriately.
 
 * Your MUA corrupted your patch; "am" would complain that
-  the patch does not apply.  Look at .dotest/ subdirectory and
+  the patch does not apply.  Look at .git/rebase/ subdirectory and
   see what 'patch' file contains and check for the common
   corruption patterns mentioned above.
 
index 46dd56c12a8167c3861a304e820c664e3c92ca4f..3558905a92a20f1f615a48821638ce239ecb6232 100644 (file)
@@ -187,8 +187,9 @@ update::
    "Update>>".  When the prompt ends with double '>>', you can
    make more than one selection, concatenated with whitespace or
    comma.  Also you can say ranges.  E.g. "2-5 7,9" to choose
-   2,3,4,5,7,9 from the list.  You can say '*' to choose
-   everything.
+   2,3,4,5,7,9 from the list.  If the second number in a range is
+   omitted, all remaining patches are taken.  E.g. "7-" to choose
+   7,8,9 from the list.  You can say '*' to choose everything.
 +
 What you chose are then highlighted with '*',
 like this:
index eeb23b25b0d70c4ccc3eb9d091971b1f20ad4b4b..2d7f162594cb69d5a5f6d8ef1714111ad0bbc67a 100644 (file)
@@ -140,11 +140,17 @@ aborts in the middle,.  You can recover from this in one of two ways:
   the index file to bring it in a state that the patch should
   have produced.  Then run the command with '--resolved' option.
 
-The command refuses to process new mailboxes while `.dotest`
+The command refuses to process new mailboxes while `.git/rebase`
 directory exists, so if you decide to start over from scratch,
-run `rm -f -r .dotest` before running the command with mailbox
+run `rm -f -r .git/rebase` before running the command with mailbox
 names.
 
+Before any patches are applied, ORIG_HEAD is set to the tip of the
+current branch.  This is useful if you have problems with multiple
+commits, like running 'git am' on the wrong branch or an error in the
+commits that is more easily fixed by changing the mailbox (e.g.
+errors in the "From:" lines).
+
 
 SEE ALSO
 --------
index b3e62ed011f0a99ce3f173a3f22758edc457f72b..fc5a4a602fb7cbc5e73f7029a0b82bba05d0b913 100644 (file)
@@ -8,24 +8,27 @@ git-branch - List, create, or delete branches
 SYNOPSIS
 --------
 [verse]
-'git branch' [--color | --no-color] [-r | -a] [--merged | --no-merged]
-          [-v [--abbrev=<length> | --no-abbrev]]
-          [--contains <commit>]
+'git branch' [--color | --no-color] [-r | -a]
+       [-v [--abbrev=<length> | --no-abbrev]]
+       [(--merged | --no-merged | --contains) [<commit>]]
 'git branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
 'git branch' (-d | -D) [-r] <branchname>...
 
 DESCRIPTION
 -----------
-With no arguments given a list of existing branches
-will be shown, the current branch will be highlighted with an asterisk.
-Option `-r` causes the remote-tracking branches to be listed,
-and option `-a` shows both.
-With `--contains <commit>`, shows only the branches that
-contains the named commit (in other words, the branches whose
-tip commits are descendant of the named commit).
-With `--merged`, only branches merged into HEAD will be listed, and
-with `--no-merged` only branches not merged into HEAD will be listed.
+
+With no arguments, existing branches are listed, the current branch will
+be highlighted with an asterisk.  Option `-r` causes the remote-tracking
+branches to be listed, and option `-a` shows both.
+
+With `--contains`, shows only the branches that contains the named commit
+(in other words, the branches whose tip commits are descendant of the
+named commit).  With `--merged`, only branches merged into the named
+commit (i.e. the branches whose tip commits are reachable from the named
+commit) will be listed.  With `--no-merged` only branches not merged into
+the named commit will be listed.  Missing <commit> argument defaults to
+'HEAD' (i.e. the tip of the current branch).
 
 In its second form, a new branch named <branchname> will be created.
 It will start out with a head equal to the one given as <start-point>.
index a691173ba13370330b97907cf751342cf1c0fe44..50fb3d5d545351ce5caf1576f529dc537d8353da 100644 (file)
@@ -58,14 +58,14 @@ OPTIONS
        Usually the command automatically creates a commit with
        a commit log message stating which commit was
        cherry-picked.  This flag applies the change necessary
-       to cherry-pick the named commit to your working tree,
-       but does not make the commit.  In addition, when this
-       option is used, your working tree does not have to match
+       to cherry-pick the named commit to your working tree
+       and the index, but does not make the commit.  In addition,
+       when this option is used, your index does not have to match
        the HEAD commit.  The cherry-pick is done against the
-       beginning state of your working tree.
+       beginning state of your index.
 +
 This is useful when cherry-picking more than one commits'
-effect to your working tree in a row.
+effect to your index in a row.
 
 -s::
 --signoff::
index 62f99b5f3b8991c93d0f2e06cd382312069bccaf..019e4ca8f503e146814bf7b9f6e2ef2920d940e2 100644 (file)
@@ -81,7 +81,9 @@ Otherwise, merge will refuse to do any harm to your repository
 (that is, it may fetch the objects from remote, and it may even
 update the local branch used to keep track of the remote branch
 with `git pull remote rbranch:lbranch`, but your working tree,
-`.git/HEAD` pointer and index file are left intact).
+`.git/HEAD` pointer and index file are left intact).  In addition,
+merge always sets `.git/ORIG_HEAD` to the original state of HEAD so
+a problematic merge can be removed by using `git reset ORIG_HEAD`.
 
 You may have local modifications in the working tree files.  In
 other words, 'git-diff' is allowed to report changes.
index f3459c7de7c9629f2abfdb684a359bb264ea82ed..51afc87e173ca135e4a663ce58b8378c1aef1d25 100644 (file)
@@ -26,7 +26,8 @@ of commits that would be shown by `git log <upstream>..HEAD`.
 
 The current branch is reset to <upstream>, or <newbase> if the
 --onto option was supplied.  This has the exact same effect as
-`git reset --hard <upstream>` (or <newbase>).
+`git reset --hard <upstream>` (or <newbase>).  ORIG_HEAD is set
+to point at the tip of the branch before the reset.
 
 The commits that were previously saved into the temporary area are
 then reapplied to the current branch, one by one, in order. Note that
@@ -38,7 +39,7 @@ It is possible that a merge failure will prevent this process from being
 completely automatic.  You will have to resolve any such merge failure
 and run `git rebase --continue`.  Another option is to bypass the commit
 that caused the merge failure with `git rebase --skip`.  To restore the
-original <branch> and remove the .dotest working files, use the command
+original <branch> and remove the .git/rebase working files, use the command
 `git rebase --abort` instead.
 
 Assume the following history exists and the current branch is "topic":
index 5411edca96b50e28996357bf75ce4e32796ee620..271850f51134ee9b991048b5da07775b5dde65fc 100644 (file)
@@ -43,16 +43,16 @@ OPTIONS
 -n::
 --no-commit::
        Usually the command automatically creates a commit with
-       a commit log message stating which commit was reverted.
-       This flag applies the change necessary to revert the
-       named commit to your working tree, but does not make the
-       commit.  In addition, when this option is used, your
-       working tree does not have to match the HEAD commit.
-       The revert is done against the beginning state of your
-       working tree.
+       a commit log message stating which commit was
+       reverted.  This flag applies the change necessary
+       to revert the named commit to your working tree
+       and the index, but does not make the commit.  In addition,
+       when this option is used, your index does not have to match
+       the HEAD commit.  The revert is done against the
+       beginning state of your index.
 +
 This is useful when reverting more than one commits'
-effect to your working tree in a row.
+effect to your index in a row.
 
 -s::
 --signoff::
index 105fc2dcdceb454a7a45e7d4c92f2fd43f44c6c7..76702a0a5aa2b8825096ebf37f76d090a1c2627b 100644 (file)
@@ -9,7 +9,7 @@ git-submodule - Initialize, update or inspect submodules
 SYNOPSIS
 --------
 [verse]
-'git submodule' [--quiet] add [-b branch] [--] <repository> [<path>]
+'git submodule' [--quiet] add [-b branch] [--] <repository> <path>
 'git submodule' [--quiet] status [--cached] [--] [<path>...]
 'git submodule' [--quiet] init [--] [<path>...]
 'git submodule' [--quiet] update [--init] [--] [<path>...]
@@ -20,14 +20,31 @@ COMMANDS
 --------
 add::
        Add the given repository as a submodule at the given path
-       to the changeset to be committed next.  If path is a valid
-       repository within the project, it is added as is. Otherwise,
-       repository is cloned at the specified path. path is added to the
-       changeset and registered in .gitmodules.   If no path is
-       specified, the path is deduced from the repository specification.
-       If the repository url begins with ./ or ../, it is stored as
-       given but resolved as a relative path from the main project's
-       url when cloning.
+       to the changeset to be committed next to the current
+       project: the current project is termed termed the "superproject".
++
+This requires two arguments: <repository> and <path>.
++
+<repository> is the URL of the new submodule's origin repository.
+This may be either an absolute URL, or (if it begins with ./
+or ../), the location relative to the superproject's origin
+repository.
++
+<path> is the relative location for the cloned submodule to
+exist in the superproject. If <path> does not exist, then the
+submodule is created by cloning from the named URL. If <path> does
+exist and is already a valid git repository, then this is added
+to the changeset without cloning. This second form is provided
+to ease creating a new submodule from scratch, and presumes
+the user will later push the submodule to the given URL.
++
+In either case, the given URL is recorded into .gitmodules for
+use by subsequent users cloning the superproject. If the URL is
+given relative to the superproject's repository, the presumption
+is the superproject and submodule repositories will be kept
+together in the same relative location, and only the
+superproject's URL need be provided: git-submodule will correctly
+locate the submodule using the relative URL in .gitmodules.
 
 status::
        Show the status of the submodules. This will print the SHA-1 of the
@@ -85,6 +102,7 @@ OPTIONS
 <path>::
        Path to submodule(s). When specified this will restrict the command
        to only operate on the submodules found at the specified paths.
+       (This argument is required with add).
 
 FILES
 -----
index e71b561172030bbd4c524891f6525fadd160b54b..48d1454a90cf9453e5e3c9fa01b3dbc369a58f1f 100644 (file)
@@ -274,7 +274,7 @@ same machine, wants to contribute.
 Bob begins with:
 
 ------------------------------------------------
-$ git clone /home/alice/project myrepo
+bob$ git clone /home/alice/project myrepo
 ------------------------------------------------
 
 This creates a new directory "myrepo" containing a clone of Alice's
@@ -285,7 +285,7 @@ Bob then makes some changes and commits them:
 
 ------------------------------------------------
 (edit files)
-$ git commit -a
+bob$ git commit -a
 (repeat as necessary)
 ------------------------------------------------
 
@@ -293,8 +293,8 @@ When he's ready, he tells Alice to pull changes from the repository
 at /home/bob/myrepo.  She does this with:
 
 ------------------------------------------------
-$ cd /home/alice/project
-$ git pull /home/bob/myrepo master
+alice$ cd /home/alice/project
+alice$ git pull /home/bob/myrepo master
 ------------------------------------------------
 
 This merges the changes from Bob's "master" branch into Alice's
@@ -306,21 +306,47 @@ is the default.)
 The "pull" command thus performs two operations: it fetches changes
 from a remote branch, then merges them into the current branch.
 
+Note that in general, Alice would want her local changes committed before
+initiating this "pull".  If Bob's work conflicts with what Alice did since
+their histories forked, Alice will use her working tree and the index to
+resolve conflicts, and existing local changes will interfere with the
+conflict resolution process (git will still perform the fetch but will
+refuse to merge --- Alice will have to get rid of her local changes in
+some way and pull again when this happens).
+
+Alice can peek at what Bob did without merging first, using the "fetch"
+command; this allows Alice to inspect what Bob did, using a special
+symbol "FETCH_HEAD", in order to determine if he has anything worth
+pulling, like this:
+
+------------------------------------------------
+alice$ git fetch /home/bob/myrepo master
+alice$ git log -p ..FETCH_HEAD
+------------------------------------------------
+
+This operation is safe even if Alice has uncommitted local changes.
+
+After inspecting what Bob did, if there is nothing urgent, Alice may
+decide to continue working without pulling from Bob.  If Bob's history
+does have something Alice would immediately need, Alice may choose to
+stash her work-in-progress first, do a "pull", and then finally unstash
+her work-in-progress on top of the resulting history.
+
 When you are working in a small closely knit group, it is not
 unusual to interact with the same repository over and over
 again.  By defining 'remote' repository shorthand, you can make
 it easier:
 
 ------------------------------------------------
-$ git remote add bob /home/bob/myrepo
+alice$ git remote add bob /home/bob/myrepo
 ------------------------------------------------
 
-With this, Alice can perform the first operation alone using the
+With this, Alice can perform the first part of the "pull" operation alone using the
 'git-fetch' command without merging them with her own branch,
 using:
 
 -------------------------------------
-$ git fetch bob
+alice$ git fetch bob
 -------------------------------------
 
 Unlike the longhand form, when Alice fetches from Bob using a
@@ -329,7 +355,7 @@ fetched is stored in a remote tracking branch, in this case
 `bob/master`.  So after this:
 
 -------------------------------------
-$ git log -p master..bob/master
+alice$ git log -p master..bob/master
 -------------------------------------
 
 shows a list of all the changes that Bob made since he branched from
@@ -339,14 +365,14 @@ After examining those changes, Alice
 could merge the changes into her master branch:
 
 -------------------------------------
-$ git merge bob/master
+alice$ git merge bob/master
 -------------------------------------
 
 This `merge` can also be done by 'pulling from her own remote
 tracking branch', like this:
 
 -------------------------------------
-$ git pull . remotes/bob/master
+alice$ git pull . remotes/bob/master
 -------------------------------------
 
 Note that git pull always merges into the current branch,
@@ -355,7 +381,7 @@ regardless of what else is given on the command line.
 Later, Bob can update his repo with Alice's latest changes using
 
 -------------------------------------
-$ git pull
+bob$ git pull
 -------------------------------------
 
 Note that he doesn't need to give the path to Alice's repository;
@@ -364,7 +390,7 @@ repository in the repository configuration, and that location is
 used for pulls:
 
 -------------------------------------
-$ git config --get remote.origin.url
+bob$ git config --get remote.origin.url
 /home/alice/project
 -------------------------------------
 
@@ -376,7 +402,7 @@ Git also keeps a pristine copy of Alice's master branch under the
 name "origin/master":
 
 -------------------------------------
-$ git branch -r
+bob$ git branch -r
   origin/master
 -------------------------------------
 
@@ -384,7 +410,7 @@ If Bob later decides to work from a different host, he can still
 perform clones and pulls using the ssh protocol:
 
 -------------------------------------
-$ git clone alice.org:/home/alice/project myrepo
+bob$ git clone alice.org:/home/alice/project myrepo
 -------------------------------------
 
 Alternatively, git has a native protocol, or can use rsync or http;
index 69e6d2fa4459313e4a6bdda77847d74d9219e2e1..c11d4957714db202a012209e2437b9e050a28ae0 100644 (file)
@@ -101,6 +101,7 @@ The placeholders are:
 - '%P': parent hashes
 - '%p': abbreviated parent hashes
 - '%an': author name
+- '%aN': author name (respecting .mailmap)
 - '%ae': author email
 - '%ad': author date
 - '%aD': author date, RFC2822 style
@@ -108,6 +109,7 @@ The placeholders are:
 - '%at': author date, UNIX timestamp
 - '%ai': author date, ISO 8601 format
 - '%cn': committer name
+- '%cN': committer name (respecting .mailmap)
 - '%ce': committer email
 - '%cd': committer date
 - '%cD': committer date, RFC2822 style
index 92d400753d192b4b6df256fc3b872d775ecaacd4..8761ee7e7d7ffe1202daff13c68a81dd2b19e18f 100644 (file)
@@ -2431,7 +2431,7 @@ $ git rebase origin
 -------------------------------------------------
 
 This will remove each of your commits from mywork, temporarily saving
-them as patches (in a directory named ".dotest"), update mywork to
+them as patches (in a directory named ".git/rebase"), update mywork to
 point at the latest version of origin, then apply each of the saved
 patches to the new mywork.  The result will look like:
 
index 4796565ab3a8e7c9bbe7f6fd092713064dce2c1a..9b52071b7304b5b3b2f88802210f7ed13274c0bd 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-parse-remote.sh
 SCRIPT_SH += git-pull.sh
@@ -363,6 +362,7 @@ LIB_H += quote.h
 LIB_H += reflog-walk.h
 LIB_H += refs.h
 LIB_H += remote.h
+LIB_H += rerere.h
 LIB_H += revision.h
 LIB_H += run-command.h
 LIB_H += sha1-lookup.h
@@ -447,6 +447,7 @@ LIB_OBJS += read-cache.o
 LIB_OBJS += reflog-walk.o
 LIB_OBJS += refs.o
 LIB_OBJS += remote.o
+LIB_OBJS += rerere.o
 LIB_OBJS += revision.o
 LIB_OBJS += run-command.o
 LIB_OBJS += server-info.o
@@ -513,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
 BUILTIN_OBJS += builtin-ls-tree.o
 BUILTIN_OBJS += builtin-mailinfo.o
 BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
 BUILTIN_OBJS += builtin-merge-base.o
 BUILTIN_OBJS += builtin-merge-file.o
 BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/alias.c b/alias.c
index 995f3e6a0ac84fa71c3196de6673225d4a5d8231..ccb1108c94436035d0da8b1d6f08f859b68294a3 100644 (file)
--- a/alias.c
+++ b/alias.c
@@ -21,3 +21,57 @@ char *alias_lookup(const char *alias)
        git_config(alias_lookup_cb, NULL);
        return alias_val;
 }
+
+int split_cmdline(char *cmdline, const char ***argv)
+{
+       int src, dst, count = 0, size = 16;
+       char quoted = 0;
+
+       *argv = xmalloc(sizeof(char*) * size);
+
+       /* split alias_string */
+       (*argv)[count++] = cmdline;
+       for (src = dst = 0; cmdline[src];) {
+               char c = cmdline[src];
+               if (!quoted && isspace(c)) {
+                       cmdline[dst++] = 0;
+                       while (cmdline[++src]
+                                       && isspace(cmdline[src]))
+                               ; /* skip */
+                       if (count >= size) {
+                               size += 16;
+                               *argv = xrealloc(*argv, sizeof(char*) * size);
+                       }
+                       (*argv)[count++] = cmdline + dst;
+               } else if (!quoted && (c == '\'' || c == '"')) {
+                       quoted = c;
+                       src++;
+               } else if (c == quoted) {
+                       quoted = 0;
+                       src++;
+               } else {
+                       if (c == '\\' && quoted != '\'') {
+                               src++;
+                               c = cmdline[src];
+                               if (!c) {
+                                       free(*argv);
+                                       *argv = NULL;
+                                       return error("cmdline ends with \\");
+                               }
+                       }
+                       cmdline[dst++] = c;
+                       src++;
+               }
+       }
+
+       cmdline[dst] = 0;
+
+       if (quoted) {
+               free(*argv);
+               *argv = NULL;
+               return error("unclosed quote");
+       }
+
+       return count;
+}
+
diff --git a/attr.c b/attr.c
index 0fb47d34346ca96addde5cb3722fa5e586057285..17f6a4dca521d9690377f2e93a0192d8a874d2ad 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -459,7 +459,9 @@ static void prepare_attr_stack(const char *path, int dirlen)
 {
        struct attr_stack *elem, *info;
        int len;
-       char pathbuf[PATH_MAX];
+       struct strbuf pathbuf;
+
+       strbuf_init(&pathbuf, dirlen+2+strlen(GITATTRIBUTES_FILE));
 
        /*
         * At the bottom of the attribute stack is the built-in
@@ -510,13 +512,14 @@ static void prepare_attr_stack(const char *path, int dirlen)
                        len = strlen(attr_stack->origin);
                        if (dirlen <= len)
                                break;
-                       memcpy(pathbuf, path, dirlen);
-                       memcpy(pathbuf + dirlen, "/", 2);
-                       cp = strchr(pathbuf + len + 1, '/');
+                       strbuf_reset(&pathbuf);
+                       strbuf_add(&pathbuf, path, dirlen);
+                       strbuf_addch(&pathbuf, '/');
+                       cp = strchr(pathbuf.buf + len + 1, '/');
                        strcpy(cp + 1, GITATTRIBUTES_FILE);
-                       elem = read_attr(pathbuf, 0);
+                       elem = read_attr(pathbuf.buf, 0);
                        *cp = '\0';
-                       elem->origin = strdup(pathbuf);
+                       elem->origin = strdup(pathbuf.buf);
                        elem->prev = attr_stack;
                        attr_stack = elem;
                        debug_push(elem);
index 56e949232cac1fe9dbe23aa93b50fe2f7c55c07d..b1e59f2196b933ab7169a30efc5d1d340f8f9c5c 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -166,7 +166,7 @@ void create_branch(const char *head,
 void remove_branch_state(void)
 {
        unlink(git_path("MERGE_HEAD"));
-       unlink(git_path("rr-cache/MERGE_RR"));
+       unlink(git_path("MERGE_RR"));
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("SQUASH_MSG"));
 }
index 06c7de429799f4af5303253775f21b7f37723b94..8827f1e0c694be107b8c0e8112599c81b1c1c12a 100644 (file)
@@ -153,6 +153,10 @@ struct blame_entry {
         */
        char guilty;
 
+       /* true if the entry has been scanned for copies in the current parent
+        */
+       char scanned;
+
        /* the line number of the first line of this group in the
         * suspect's file; internally all line numbers are 0 based.
         */
@@ -1008,7 +1012,8 @@ static int find_move_in_parent(struct scoreboard *sb,
        while (made_progress) {
                made_progress = 0;
                for (e = sb->ent; e; e = e->next) {
-                       if (e->guilty || !same_suspect(e->suspect, target))
+                       if (e->guilty || !same_suspect(e->suspect, target) ||
+                           ent_score(sb, e) < blame_move_score)
                                continue;
                        find_copy_in_blob(sb, e, parent, split, &file_p);
                        if (split[1].suspect &&
@@ -1033,6 +1038,7 @@ struct blame_list {
  */
 static struct blame_list *setup_blame_list(struct scoreboard *sb,
                                           struct origin *target,
+                                          int min_score,
                                           int *num_ents_p)
 {
        struct blame_entry *e;
@@ -1040,18 +1046,32 @@ static struct blame_list *setup_blame_list(struct scoreboard *sb,
        struct blame_list *blame_list = NULL;
 
        for (e = sb->ent, num_ents = 0; e; e = e->next)
-               if (!e->guilty && same_suspect(e->suspect, target))
+               if (!e->scanned && !e->guilty &&
+                   same_suspect(e->suspect, target) &&
+                   min_score < ent_score(sb, e))
                        num_ents++;
        if (num_ents) {
                blame_list = xcalloc(num_ents, sizeof(struct blame_list));
                for (e = sb->ent, i = 0; e; e = e->next)
-                       if (!e->guilty && same_suspect(e->suspect, target))
+                       if (!e->scanned && !e->guilty &&
+                           same_suspect(e->suspect, target) &&
+                           min_score < ent_score(sb, e))
                                blame_list[i++].ent = e;
        }
        *num_ents_p = num_ents;
        return blame_list;
 }
 
+/*
+ * Reset the scanned status on all entries.
+ */
+static void reset_scanned_flag(struct scoreboard *sb)
+{
+       struct blame_entry *e;
+       for (e = sb->ent; e; e = e->next)
+               e->scanned = 0;
+}
+
 /*
  * For lines target is suspected for, see if we can find code movement
  * across file boundary from the parent commit.  porigin is the path
@@ -1070,7 +1090,7 @@ static int find_copy_in_parent(struct scoreboard *sb,
        struct blame_list *blame_list;
        int num_ents;
 
-       blame_list = setup_blame_list(sb, target, &num_ents);
+       blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
        if (!blame_list)
                return 1; /* nothing remains for this target */
 
@@ -1144,18 +1164,21 @@ static int find_copy_in_parent(struct scoreboard *sb,
                                split_blame(sb, split, blame_list[j].ent);
                                made_progress = 1;
                        }
+                       else
+                               blame_list[j].ent->scanned = 1;
                        decref_split(split);
                }
                free(blame_list);
 
                if (!made_progress)
                        break;
-               blame_list = setup_blame_list(sb, target, &num_ents);
+               blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
                if (!blame_list) {
                        retval = 1;
                        break;
                }
        }
+       reset_scanned_flag(sb);
        diff_flush(&diff_opts);
        diff_tree_release_paths(&diff_opts);
        return retval;
index ff71f3d8a6dd784ae08f89b8a98136e4cd369295..7dae22c0197c2d3d062b0fa34c15b6c9a489eb31 100644 (file)
@@ -46,7 +46,12 @@ enum color_branch {
        COLOR_BRANCH_CURRENT = 4,
 };
 
-static int mergefilter = -1;
+static enum merge_filter {
+       NO_FILTER = 0,
+       SHOW_NOT_MERGED,
+       SHOW_MERGED,
+} merge_filter;
+static unsigned char merge_filter_ref[20];
 
 static int parse_branch_color_slot(const char *var, int ofs)
 {
@@ -234,13 +239,15 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
        if ((kind & ref_list->kinds) == 0)
                return 0;
 
-       if (mergefilter > -1) {
+       if (merge_filter != NO_FILTER) {
                branch.item = lookup_commit_reference_gently(sha1, 1);
                if (!branch.item)
                        die("Unable to lookup tip of branch %s", refname);
-               if (mergefilter == 0 && has_commit(head_sha1, &branch))
+               if (merge_filter == SHOW_NOT_MERGED &&
+                   has_commit(merge_filter_ref, &branch))
                        return 0;
-               if (mergefilter == 1 && !has_commit(head_sha1, &branch))
+               if (merge_filter == SHOW_MERGED &&
+                   !has_commit(merge_filter_ref, &branch))
                        return 0;
        }
 
@@ -443,6 +450,20 @@ static int opt_parse_with_commit(const struct option *opt, const char *arg, int
        return 0;
 }
 
+static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
+{
+       merge_filter = ((opt->long_name[0] == 'n')
+                       ? SHOW_NOT_MERGED
+                       : SHOW_MERGED);
+       if (unset)
+               merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */
+       if (!arg)
+               arg = "HEAD";
+       if (get_sha1(arg, merge_filter_ref))
+               die("malformed object name %s", arg);
+       return 0;
+}
+
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
        int delete = 0, rename = 0, force_create = 0;
@@ -460,13 +481,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_BOOLEAN( 0 , "color",  &branch_use_color, "use colored output"),
                OPT_SET_INT('r', NULL,     &kinds, "act on remote-tracking branches",
                        REF_REMOTE_BRANCH),
-               OPT_CALLBACK(0, "contains", &with_commit, "commit",
-                            "print only branches that contain the commit",
-                            opt_parse_with_commit),
+               {
+                       OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
+                       "print only branches that contain the commit",
+                       PARSE_OPT_LASTARG_DEFAULT,
+                       opt_parse_with_commit, (intptr_t)"HEAD",
+               },
                {
                        OPTION_CALLBACK, 0, "with", &with_commit, "commit",
                        "print only branches that contain the commit",
-                       PARSE_OPT_HIDDEN, opt_parse_with_commit,
+                       PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
+                       opt_parse_with_commit, (intptr_t) "HEAD",
                },
                OPT__ABBREV(&abbrev),
 
@@ -479,7 +504,18 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
                OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
                OPT_BOOLEAN('f', NULL, &force_create, "force creation (when already exists)"),
-               OPT_SET_INT(0, "merged", &mergefilter, "list only merged branches", 1),
+               {
+                       OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
+                       "commit", "print only not merged branches",
+                       PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
+                       opt_parse_merge_filter, (intptr_t) "HEAD",
+               },
+               {
+                       OPTION_CALLBACK, 0, "merged", &merge_filter_ref,
+                       "commit", "print only merged branches",
+                       PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
+                       opt_parse_merge_filter, (intptr_t) "HEAD",
+               },
                OPT_END(),
        };
 
@@ -489,9 +525,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                branch_use_color = git_use_color_default;
 
        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);
 
        head = resolve_ref("HEAD", head_sha1, 0, NULL);
        if (!head)
@@ -504,6 +537,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                        die("HEAD not found below refs/heads!");
                head += 11;
        }
+       hashcpy(merge_filter_ref, head_sha1);
+
+       argc = parse_options(argc, argv, options, builtin_branch_usage, 0);
+       if (!!delete + !!rename + !!force_create > 1)
+               usage_with_options(builtin_branch_usage, options);
 
        if (delete)
                return delete_branches(argc, argv, delete > 1, kinds);
index 3881f6c2f56f317726a688e44b98806987036999..7a9a309be0543da7d27e7710ef82271f2582e0a9 100644 (file)
@@ -45,41 +45,19 @@ static const char commit_utf8_warn[] =
 "You may want to amend it after fixing the message, or set the config\n"
 "variable i18n.commitencoding to the encoding your project uses.\n";
 
-int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+int commit_tree(const char *msg, unsigned char *tree,
+               struct commit_list *parents, unsigned char *ret)
 {
-       int i;
-       struct commit_list *parents = NULL;
-       unsigned char tree_sha1[20];
-       unsigned char commit_sha1[20];
-       struct strbuf buffer;
        int encoding_is_utf8;
+       struct strbuf buffer;
 
-       git_config(git_default_config, NULL);
-
-       if (argc < 2)
-               usage(commit_tree_usage);
-       if (get_sha1(argv[1], tree_sha1))
-               die("Not a valid object name %s", argv[1]);
-
-       check_valid(tree_sha1, OBJ_TREE);
-       for (i = 2; i < argc; i += 2) {
-               unsigned char sha1[20];
-               const char *a, *b;
-               a = argv[i]; b = argv[i+1];
-               if (!b || strcmp(a, "-p"))
-                       usage(commit_tree_usage);
-
-               if (get_sha1(b, sha1))
-                       die("Not a valid object name %s", b);
-               check_valid(sha1, OBJ_COMMIT);
-               new_parent(lookup_commit(sha1), &parents);
-       }
+       check_valid(tree, OBJ_TREE);
 
        /* Not having i18n.commitencoding is the same as having utf-8 */
        encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
 
        strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
-       strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree_sha1));
+       strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
 
        /*
         * NOTE! This ordering means that the same exact tree merged with a
@@ -102,14 +80,47 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
        strbuf_addch(&buffer, '\n');
 
        /* And add the comment */
-       if (strbuf_read(&buffer, 0, 0) < 0)
-               die("git-commit-tree: read returned %s", strerror(errno));
+       strbuf_addstr(&buffer, msg);
 
        /* And check the encoding */
        if (encoding_is_utf8 && !is_utf8(buffer.buf))
                fprintf(stderr, commit_utf8_warn);
 
-       if (!write_sha1_file(buffer.buf, buffer.len, commit_type, commit_sha1)) {
+       return write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
+}
+
+int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       struct commit_list *parents = NULL;
+       unsigned char tree_sha1[20];
+       unsigned char commit_sha1[20];
+       struct strbuf buffer = STRBUF_INIT;
+
+       git_config(git_default_config, NULL);
+
+       if (argc < 2)
+               usage(commit_tree_usage);
+       if (get_sha1(argv[1], tree_sha1))
+               die("Not a valid object name %s", argv[1]);
+
+       for (i = 2; i < argc; i += 2) {
+               unsigned char sha1[20];
+               const char *a, *b;
+               a = argv[i]; b = argv[i+1];
+               if (!b || strcmp(a, "-p"))
+                       usage(commit_tree_usage);
+
+               if (get_sha1(b, sha1))
+                       die("Not a valid object name %s", b);
+               check_valid(sha1, OBJ_COMMIT);
+               new_parent(lookup_commit(sha1), &parents);
+       }
+
+       if (strbuf_read(&buffer, 0, 0) < 0)
+               die("git-commit-tree: read returned %s", strerror(errno));
+
+       if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1)) {
                printf("%s\n", sha1_to_hex(commit_sha1));
                return 0;
        }
index 745c11e7735ffcb070683d1af607b4ad85f48e23..bdc83df55bcaeb51eb51b1a3198033eb69236409 100644 (file)
@@ -22,6 +22,7 @@
 #include "utf8.h"
 #include "parse-options.h"
 #include "path-list.h"
+#include "rerere.h"
 #include "unpack-trees.h"
 
 static const char * const builtin_commit_usage[] = {
index e515f9ca9b5d0ec13e96a7866e27bdd9e852b435..62a19056ef05989b0b48d0939758a66175b3f442 100644 (file)
@@ -20,7 +20,7 @@ static int tags;      /* But allow any tags if --tags is specified */
 static int longformat;
 static int abbrev = DEFAULT_ABBREV;
 static int max_candidates = 10;
-const char *pattern = NULL;
+static const char *pattern;
 static int always;
 
 struct commit_name {
index b892621ab50346ff29a0a1335de76e3f2fbbc723..dbd7d2ddae2d4b44bcccf9977d435cf334a0c418 100644 (file)
@@ -159,23 +159,24 @@ static int handle_line(char *line)
 }
 
 static void print_joined(const char *singular, const char *plural,
-               struct list *list)
+               struct list *list, struct strbuf *out)
 {
        if (list->nr == 0)
                return;
        if (list->nr == 1) {
-               printf("%s%s", singular, list->list[0]);
+               strbuf_addf(out, "%s%s", singular, list->list[0]);
        } else {
                int i;
-               printf("%s", plural);
+               strbuf_addstr(out, plural);
                for (i = 0; i < list->nr - 1; i++)
-                       printf("%s%s", i > 0 ? ", " : "", list->list[i]);
-               printf(" and %s", list->list[list->nr - 1]);
+                       strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]);
+               strbuf_addf(out, " and %s", list->list[list->nr - 1]);
        }
 }
 
 static void shortlog(const char *name, unsigned char *sha1,
-               struct commit *head, struct rev_info *rev, int limit)
+               struct commit *head, struct rev_info *rev, int limit,
+               struct strbuf *out)
 {
        int i, count = 0;
        struct commit *commit;
@@ -232,15 +233,15 @@ static void shortlog(const char *name, unsigned char *sha1,
        }
 
        if (count > limit)
-               printf("\n* %s: (%d commits)\n", name, count);
+               strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
        else
-               printf("\n* %s:\n", name);
+               strbuf_addf(out, "\n* %s:\n", name);
 
        for (i = 0; i < subjects.nr; i++)
                if (i >= limit)
-                       printf("  ...\n");
+                       strbuf_addf(out, "  ...\n");
                else
-                       printf("  %s\n", subjects.list[i]);
+                       strbuf_addf(out, "  %s\n", subjects.list[i]);
 
        clear_commit_marks((struct commit *)branch, flags);
        clear_commit_marks(head, flags);
@@ -251,43 +252,13 @@ static void shortlog(const char *name, unsigned char *sha1,
        free_list(&subjects);
 }
 
-int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
-{
-       int limit = 20, i = 0;
+int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
+       int limit = 20, i = 0, pos = 0;
        char line[1024];
-       FILE *in = stdin;
-       const char *sep = "";
+       char *p = line, *sep = "";
        unsigned char head_sha1[20];
        const char *current_branch;
 
-       git_config(fmt_merge_msg_config, NULL);
-
-       while (argc > 1) {
-               if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary"))
-                       merge_summary = 1;
-               else if (!strcmp(argv[1], "--no-log")
-                               || !strcmp(argv[1], "--no-summary"))
-                       merge_summary = 0;
-               else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
-                       if (argc < 3)
-                               die ("Which file?");
-                       if (!strcmp(argv[2], "-"))
-                               in = stdin;
-                       else {
-                               fclose(in);
-                               in = fopen(argv[2], "r");
-                               if (!in)
-                                       die("cannot open %s", argv[2]);
-                       }
-                       argc--; argv++;
-               } else
-                       break;
-               argc--; argv++;
-       }
-
-       if (argc > 1)
-               usage(fmt_merge_msg_usage);
-
        /* get current branch */
        current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
        if (!current_branch)
@@ -295,75 +266,127 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
        if (!prefixcmp(current_branch, "refs/heads/"))
                current_branch += 11;
 
-       while (fgets(line, sizeof(line), in)) {
+       /* get a line */
+       while (pos < in->len) {
+               int len;
+               char *newline;
+
+               p = in->buf + pos;
+               newline = strchr(p, '\n');
+               len = newline ? newline - p : strlen(p);
+               pos += len + !!newline;
                i++;
-               if (line[0] == 0)
-                       continue;
-               if (handle_line(line))
-                       die ("Error in line %d: %s", i, line);
+               p[len] = 0;
+               if (handle_line(p))
+                       die ("Error in line %d: %.*s", i, len, p);
        }
 
-       printf("Merge ");
+       strbuf_addstr(out, "Merge ");
        for (i = 0; i < srcs.nr; i++) {
                struct src_data *src_data = srcs.payload[i];
                const char *subsep = "";
 
-               printf(sep);
+               strbuf_addstr(out, sep);
                sep = "; ";
 
                if (src_data->head_status == 1) {
-                       printf(srcs.list[i]);
+                       strbuf_addstr(out, srcs.list[i]);
                        continue;
                }
                if (src_data->head_status == 3) {
                        subsep = ", ";
-                       printf("HEAD");
+                       strbuf_addstr(out, "HEAD");
                }
                if (src_data->branch.nr) {
-                       printf(subsep);
+                       strbuf_addstr(out, subsep);
                        subsep = ", ";
-                       print_joined("branch ", "branches ", &src_data->branch);
+                       print_joined("branch ", "branches ", &src_data->branch,
+                                       out);
                }
                if (src_data->r_branch.nr) {
-                       printf(subsep);
+                       strbuf_addstr(out, subsep);
                        subsep = ", ";
                        print_joined("remote branch ", "remote branches ",
-                                       &src_data->r_branch);
+                                       &src_data->r_branch, out);
                }
                if (src_data->tag.nr) {
-                       printf(subsep);
+                       strbuf_addstr(out, subsep);
                        subsep = ", ";
-                       print_joined("tag ", "tags ", &src_data->tag);
+                       print_joined("tag ", "tags ", &src_data->tag, out);
                }
                if (src_data->generic.nr) {
-                       printf(subsep);
-                       print_joined("commit ", "commits ", &src_data->generic);
+                       strbuf_addstr(out, subsep);
+                       print_joined("commit ", "commits ", &src_data->generic,
+                                       out);
                }
                if (strcmp(".", srcs.list[i]))
-                       printf(" of %s", srcs.list[i]);
+                       strbuf_addf(out, " of %s", srcs.list[i]);
        }
 
        if (!strcmp("master", current_branch))
-               putchar('\n');
+               strbuf_addch(out, '\n');
        else
-               printf(" into %s\n", current_branch);
+               strbuf_addf(out, " into %s\n", current_branch);
 
        if (merge_summary) {
                struct commit *head;
                struct rev_info rev;
 
                head = lookup_commit(head_sha1);
-               init_revisions(&rev, prefix);
+               init_revisions(&rev, NULL);
                rev.commit_format = CMIT_FMT_ONELINE;
                rev.ignore_merges = 1;
                rev.limited = 1;
 
                for (i = 0; i < origins.nr; i++)
                        shortlog(origins.list[i], origins.payload[i],
-                                       head, &rev, limit);
+                                       head, &rev, limit, out);
        }
+       return 0;
+}
+
+int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
+{
+       FILE *in = stdin;
+       struct strbuf input, output;
+       int ret;
+
+       git_config(fmt_merge_msg_config, NULL);
+
+       while (argc > 1) {
+               if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary"))
+                       merge_summary = 1;
+               else if (!strcmp(argv[1], "--no-log")
+                               || !strcmp(argv[1], "--no-summary"))
+                       merge_summary = 0;
+               else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
+                       if (argc < 3)
+                               die ("Which file?");
+                       if (!strcmp(argv[2], "-"))
+                               in = stdin;
+                       else {
+                               fclose(in);
+                               in = fopen(argv[2], "r");
+                               if (!in)
+                                       die("cannot open %s", argv[2]);
+                       }
+                       argc--; argv++;
+               } else
+                       break;
+               argc--; argv++;
+       }
+
+       if (argc > 1)
+               usage(fmt_merge_msg_usage);
 
-       /* No cleanup yet; is standalone anyway */
+       strbuf_init(&input, 0);
+       if (strbuf_read(&input, fileno(in), 0) < 0)
+               die("could not read input file %s", strerror(errno));
+       strbuf_init(&output, 0);
 
+       ret = fmt_merge_msg(merge_summary, &input, &output);
+       if (ret)
+               return ret;
+       printf("%s", output.buf);
        return 0;
 }
index ef299108f529fa5fa931d765828f04535c3a6351..647535061c350b68f1583fc12b049a37eb17481c 100644 (file)
@@ -427,33 +427,35 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
        struct name_entry entry;
        char *down;
        int tn_len = strlen(tree_name);
-       char *path_buf = xmalloc(PATH_MAX + tn_len + 100);
+       struct strbuf pathbuf;
+
+       strbuf_init(&pathbuf, PATH_MAX + tn_len);
 
        if (tn_len) {
-               tn_len = sprintf(path_buf, "%s:", tree_name);
-               down = path_buf + tn_len;
-               strcat(down, base);
-       }
-       else {
-               down = path_buf;
-               strcpy(down, base);
+               strbuf_add(&pathbuf, tree_name, tn_len);
+               strbuf_addch(&pathbuf, ':');
+               tn_len = pathbuf.len;
        }
-       len = strlen(path_buf);
+       strbuf_addstr(&pathbuf, base);
+       len = pathbuf.len;
 
        while (tree_entry(tree, &entry)) {
-               strcpy(path_buf + len, entry.path);
+               int te_len = tree_entry_len(entry.path, entry.sha1);
+               pathbuf.len = len;
+               strbuf_add(&pathbuf, entry.path, te_len);
 
                if (S_ISDIR(entry.mode))
                        /* Match "abc/" against pathspec to
                         * decide if we want to descend into "abc"
                         * directory.
                         */
-                       strcpy(path_buf + len + tree_entry_len(entry.path, entry.sha1), "/");
+                       strbuf_addch(&pathbuf, '/');
 
+               down = pathbuf.buf + tn_len;
                if (!pathspec_matches(paths, down))
                        ;
                else if (S_ISREG(entry.mode))
-                       hit |= grep_sha1(opt, entry.sha1, path_buf, tn_len);
+                       hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
                else if (S_ISDIR(entry.mode)) {
                        enum object_type type;
                        struct tree_desc sub;
@@ -469,6 +471,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
                        free(data);
                }
        }
+       strbuf_release(&pathbuf);
        return hit;
 }
 
index e23b8438c741651f48bbf529f89d0336cc718fed..5ba213a595fb9d7de4d36ff5cab888bde2035722 100644 (file)
@@ -115,18 +115,8 @@ static void copy_templates(const char *template_dir)
 
        if (!template_dir)
                template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
-       if (!template_dir) {
-               /*
-                * if the hard-coded template is relative, it is
-                * interpreted relative to the exec_dir
-                */
-               template_dir = DEFAULT_GIT_TEMPLATE_DIR;
-               if (!is_absolute_path(template_dir)) {
-                       struct strbuf d = STRBUF_INIT;
-                       strbuf_addf(&d, "%s/%s", git_exec_path(), template_dir);
-                       template_dir = strbuf_detach(&d, NULL);
-               }
-       }
+       if (!template_dir)
+               template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
        strcpy(template_path, template_dir);
        template_len = strlen(template_path);
        if (template_path[template_len-1] != '/') {
index 962aa34c8ea3f603abcbf7f8f94cf6914105cbef..13f0502b9e0bca9e4119896c01786b99cc54441e 100644 (file)
@@ -334,7 +334,9 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over
                return 1;
        if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
                for (i = 0; header[i]; i++) {
-                       if (!memcmp("Subject: ", header[i], 9)) {
+                       if (!memcmp("Subject", header[i], 7)) {
+                               if (!hdr_data[i])
+                                       hdr_data[i] = xmalloc(linesize + 20);
                                if (! handle_header(line, hdr_data[i], 0)) {
                                        return 1;
                                }
index 385e742eefb6f62b58705dfb98a977bdc76ea4d2..652a2c32bb0913108e0435de86392764da3ad611 100644 (file)
@@ -42,14 +42,6 @@ static struct tree *shift_tree_object(struct tree *one, struct tree *two)
  * - *(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));
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644 (file)
index 0000000..129b4e6
--- /dev/null
@@ -0,0 +1,1156 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+#include "rerere.h"
+
+#define DEFAULT_TWOHEAD (1<<0)
+#define DEFAULT_OCTOPUS (1<<1)
+#define NO_FAST_FORWARD (1<<2)
+#define NO_TRIVIAL      (1<<3)
+
+struct strategy {
+       const char *name;
+       unsigned attr;
+};
+
+static const char * const builtin_merge_usage[] = {
+       "git-merge [options] <remote>...",
+       "git-merge [options] <msg> HEAD <remote>",
+       NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static struct strategy **use_strategies;
+static size_t use_strategies_nr, use_strategies_alloc;
+static const char *branch;
+
+static struct strategy all_strategy[] = {
+       { "recur",      NO_TRIVIAL },
+       { "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
+       { "octopus",    DEFAULT_OCTOPUS },
+       { "resolve",    0 },
+       { "stupid",     0 },
+       { "ours",       NO_FAST_FORWARD | NO_TRIVIAL },
+       { "subtree",    NO_FAST_FORWARD | NO_TRIVIAL },
+};
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+                               const char *arg, int unset)
+{
+       struct strbuf *buf = opt->value;
+
+       if (unset)
+               strbuf_setlen(buf, 0);
+       else {
+               strbuf_addf(buf, "%s\n\n", arg);
+               have_message = 1;
+       }
+       return 0;
+}
+
+static struct strategy *get_strategy(const char *name)
+{
+       int i;
+
+       if (!name)
+               return NULL;
+
+       for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+               if (!strcmp(name, all_strategy[i].name))
+                       return &all_strategy[i];
+       return NULL;
+}
+
+static void append_strategy(struct strategy *s)
+{
+       ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc);
+       use_strategies[use_strategies_nr++] = s;
+}
+
+static int option_parse_strategy(const struct option *opt,
+                                const char *name, int unset)
+{
+       int i;
+       struct strategy *s;
+
+       if (unset)
+               return 0;
+
+       s = get_strategy(name);
+
+       if (s)
+               append_strategy(s);
+       else {
+               struct strbuf err;
+               strbuf_init(&err, 0);
+               for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+                       strbuf_addf(&err, " %s", all_strategy[i].name);
+               fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
+               fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+               exit(1);
+       }
+       return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+                         const char *arg, int unset)
+{
+       show_diffstat = unset;
+       return 0;
+}
+
+static struct option builtin_merge_options[] = {
+       { OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+               "do not show a diffstat at the end of the merge",
+               PARSE_OPT_NOARG, option_parse_n },
+       OPT_BOOLEAN(0, "stat", &show_diffstat,
+               "show a diffstat at the end of the merge"),
+       OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+       OPT_BOOLEAN(0, "log", &option_log,
+               "add list of one-line log to merge commit message"),
+       OPT_BOOLEAN(0, "squash", &squash,
+               "create a single commit instead of doing a merge"),
+       OPT_BOOLEAN(0, "commit", &option_commit,
+               "perform a commit if the merge succeeds (default)"),
+       OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+               "allow fast forward (default)"),
+       OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+               "merge strategy to use", option_parse_strategy),
+       OPT_CALLBACK('m', "message", &merge_msg, "message",
+               "message to be used for the merge commit (if any)",
+               option_parse_message),
+       OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+       unlink(git_path("MERGE_HEAD"));
+       unlink(git_path("MERGE_MSG"));
+}
+
+static void save_state(void)
+{
+       int len;
+       struct child_process cp;
+       struct strbuf buffer = STRBUF_INIT;
+       const char *argv[] = {"stash", "create", NULL};
+
+       memset(&cp, 0, sizeof(cp));
+       cp.argv = argv;
+       cp.out = -1;
+       cp.git_cmd = 1;
+
+       if (start_command(&cp))
+               die("could not run stash.");
+       len = strbuf_read(&buffer, cp.out, 1024);
+       close(cp.out);
+
+       if (finish_command(&cp) || len < 0)
+               die("stash failed");
+       else if (!len)
+               return;
+       strbuf_setlen(&buffer, buffer.len-1);
+       if (get_sha1(buffer.buf, stash))
+               die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+       int i = 0;
+       const char *args[6];
+
+       args[i++] = "read-tree";
+       if (verbose)
+               args[i++] = "-v";
+       args[i++] = "--reset";
+       args[i++] = "-u";
+       args[i++] = sha1_to_hex(sha1);
+       args[i] = NULL;
+
+       if (run_command_v_opt(args, RUN_GIT_CMD))
+               die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+       struct strbuf sb;
+       const char *args[] = { "stash", "apply", NULL, NULL };
+
+       if (is_null_sha1(stash))
+               return;
+
+       reset_hard(head, 1);
+
+       strbuf_init(&sb, 0);
+       args[2] = sha1_to_hex(stash);
+
+       /*
+        * It is OK to ignore error here, for example when there was
+        * nothing to restore.
+        */
+       run_command_v_opt(args, RUN_GIT_CMD);
+
+       strbuf_release(&sb);
+       refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+       printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+       drop_save();
+}
+
+static void squash_message(void)
+{
+       struct rev_info rev;
+       struct commit *commit;
+       struct strbuf out;
+       struct commit_list *j;
+       int fd;
+
+       printf("Squash commit -- not updating HEAD\n");
+       fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+       if (fd < 0)
+               die("Could not write to %s", git_path("SQUASH_MSG"));
+
+       init_revisions(&rev, NULL);
+       rev.ignore_merges = 1;
+       rev.commit_format = CMIT_FMT_MEDIUM;
+
+       commit = lookup_commit(head);
+       commit->object.flags |= UNINTERESTING;
+       add_pending_object(&rev, &commit->object, NULL);
+
+       for (j = remoteheads; j; j = j->next)
+               add_pending_object(&rev, &j->item->object, NULL);
+
+       setup_revisions(0, NULL, &rev, NULL);
+       if (prepare_revision_walk(&rev))
+               die("revision walk setup failed");
+
+       strbuf_init(&out, 0);
+       strbuf_addstr(&out, "Squashed commit of the following:\n");
+       while ((commit = get_revision(&rev)) != NULL) {
+               strbuf_addch(&out, '\n');
+               strbuf_addf(&out, "commit %s\n",
+                       sha1_to_hex(commit->object.sha1));
+               pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+                       NULL, NULL, rev.date_mode, 0);
+       }
+       write(fd, out.buf, out.len);
+       close(fd);
+       strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+       struct child_process hook;
+       const char *argv[3], *env[2];
+       char index[PATH_MAX];
+
+       argv[0] = git_path("hooks/%s", name);
+       if (access(argv[0], X_OK) < 0)
+               return 0;
+
+       snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+       env[0] = index;
+       env[1] = NULL;
+
+       if (squash)
+               argv[1] = "1";
+       else
+               argv[1] = "0";
+       argv[2] = NULL;
+
+       memset(&hook, 0, sizeof(hook));
+       hook.argv = argv;
+       hook.no_stdin = 1;
+       hook.stdout_to_stderr = 1;
+       hook.env = env;
+
+       return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+       struct strbuf reflog_message;
+
+       strbuf_init(&reflog_message, 0);
+       if (!msg)
+               strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+       else {
+               printf("%s\n", msg);
+               strbuf_addf(&reflog_message, "%s: %s",
+                       getenv("GIT_REFLOG_ACTION"), msg);
+       }
+       if (squash) {
+               squash_message();
+       } else {
+               if (!merge_msg.len)
+                       printf("No merge message -- not updating HEAD\n");
+               else {
+                       const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+                       update_ref(reflog_message.buf, "HEAD",
+                               new_head, head, 0,
+                               DIE_ON_ERR);
+                       /*
+                        * We ignore errors in 'gc --auto', since the
+                        * user should see them.
+                        */
+                       run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+               }
+       }
+       if (new_head && show_diffstat) {
+               struct diff_options opts;
+               diff_setup(&opts);
+               opts.output_format |=
+                       DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+               opts.detect_rename = DIFF_DETECT_RENAME;
+               if (diff_use_color_default > 0)
+                       DIFF_OPT_SET(&opts, COLOR_DIFF);
+               if (diff_setup_done(&opts) < 0)
+                       die("diff_setup_done failed");
+               diff_tree_sha1(head, new_head, "", &opts);
+               diffcore_std(&opts);
+               diff_flush(&opts);
+       }
+
+       /* Run a post-merge hook */
+       run_hook("post-merge");
+
+       strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+       struct object *remote_head;
+       unsigned char branch_head[20], buf_sha[20];
+       struct strbuf buf;
+       const char *ptr;
+       int len, early;
+
+       memset(branch_head, 0, sizeof(branch_head));
+       remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+       if (!remote_head)
+               die("'%s' does not point to a commit", remote);
+
+       strbuf_init(&buf, 0);
+       strbuf_addstr(&buf, "refs/heads/");
+       strbuf_addstr(&buf, remote);
+       resolve_ref(buf.buf, branch_head, 0, 0);
+
+       if (!hashcmp(remote_head->sha1, branch_head)) {
+               strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+                       sha1_to_hex(branch_head), remote);
+               return;
+       }
+
+       /* See if remote matches <name>^^^.. or <name>~<number> */
+       for (len = 0, ptr = remote + strlen(remote);
+            remote < ptr && ptr[-1] == '^';
+            ptr--)
+               len++;
+       if (len)
+               early = 1;
+       else {
+               early = 0;
+               ptr = strrchr(remote, '~');
+               if (ptr) {
+                       int seen_nonzero = 0;
+
+                       len++; /* count ~ */
+                       while (*++ptr && isdigit(*ptr)) {
+                               seen_nonzero |= (*ptr != '0');
+                               len++;
+                       }
+                       if (*ptr)
+                               len = 0; /* not ...~<number> */
+                       else if (seen_nonzero)
+                               early = 1;
+                       else if (len == 1)
+                               early = 1; /* "name~" is "name~1"! */
+               }
+       }
+       if (len) {
+               struct strbuf truname = STRBUF_INIT;
+               strbuf_addstr(&truname, "refs/heads/");
+               strbuf_addstr(&truname, remote);
+               strbuf_setlen(&truname, len+11);
+               if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
+                       strbuf_addf(msg,
+                                   "%s\t\tbranch '%s'%s of .\n",
+                                   sha1_to_hex(remote_head->sha1),
+                                   truname.buf,
+                                   (early ? " (early part)" : ""));
+                       return;
+               }
+       }
+
+       if (!strcmp(remote, "FETCH_HEAD") &&
+                       !access(git_path("FETCH_HEAD"), R_OK)) {
+               FILE *fp;
+               struct strbuf line;
+               char *ptr;
+
+               strbuf_init(&line, 0);
+               fp = fopen(git_path("FETCH_HEAD"), "r");
+               if (!fp)
+                       die("could not open %s for reading: %s",
+                               git_path("FETCH_HEAD"), strerror(errno));
+               strbuf_getline(&line, fp, '\n');
+               fclose(fp);
+               ptr = strstr(line.buf, "\tnot-for-merge\t");
+               if (ptr)
+                       strbuf_remove(&line, ptr-line.buf+1, 13);
+               strbuf_addbuf(msg, &line);
+               strbuf_release(&line);
+               return;
+       }
+       strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+               sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+       if (branch && !prefixcmp(k, "branch.") &&
+               !prefixcmp(k + 7, branch) &&
+               !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+               const char **argv;
+               int argc;
+               char *buf;
+
+               buf = xstrdup(v);
+               argc = split_cmdline(buf, &argv);
+               argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+               memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+               argc++;
+               parse_options(argc, argv, builtin_merge_options,
+                             builtin_merge_usage, 0);
+               free(buf);
+       }
+
+       if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+               show_diffstat = git_config_bool(k, v);
+       else if (!strcmp(k, "pull.twohead"))
+               return git_config_string(&pull_twohead, k, v);
+       else if (!strcmp(k, "pull.octopus"))
+               return git_config_string(&pull_octopus, k, v);
+       else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary"))
+               option_log = git_config_bool(k, v);
+       return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+                            unsigned char *one)
+{
+       int i, nr_trees = 0;
+       struct tree *trees[MAX_UNPACK_TREES];
+       struct tree_desc t[MAX_UNPACK_TREES];
+       struct unpack_trees_options opts;
+
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = 2;
+       opts.src_index = &the_index;
+       opts.dst_index = &the_index;
+       opts.update = 1;
+       opts.verbose_update = 1;
+       opts.trivial_merges_only = 1;
+       opts.merge = 1;
+       trees[nr_trees] = parse_tree_indirect(common);
+       if (!trees[nr_trees++])
+               return -1;
+       trees[nr_trees] = parse_tree_indirect(head);
+       if (!trees[nr_trees++])
+               return -1;
+       trees[nr_trees] = parse_tree_indirect(one);
+       if (!trees[nr_trees++])
+               return -1;
+       opts.fn = threeway_merge;
+       cache_tree_free(&active_cache_tree);
+       for (i = 0; i < nr_trees; i++) {
+               parse_tree(trees[i]);
+               init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+       }
+       if (unpack_trees(nr_trees, t, &opts))
+               return -1;
+       return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+       if (write_cache_as_tree(sha1, 0, NULL))
+               die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(const char *strategy, struct commit_list *common,
+                             const char *head_arg)
+{
+       const char **args;
+       int i = 0, ret;
+       struct commit_list *j;
+       struct strbuf buf;
+
+       args = xmalloc((4 + commit_list_count(common) +
+                       commit_list_count(remoteheads)) * sizeof(char *));
+       strbuf_init(&buf, 0);
+       strbuf_addf(&buf, "merge-%s", strategy);
+       args[i++] = buf.buf;
+       for (j = common; j; j = j->next)
+               args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+       args[i++] = "--";
+       args[i++] = head_arg;
+       for (j = remoteheads; j; j = j->next)
+               args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+       args[i] = NULL;
+       ret = run_command_v_opt(args, RUN_GIT_CMD);
+       strbuf_release(&buf);
+       i = 1;
+       for (j = common; j; j = j->next)
+               free((void *)args[i++]);
+       i += 2;
+       for (j = remoteheads; j; j = j->next)
+               free((void *)args[i++]);
+       free(args);
+       return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+                            struct diff_options *opt, void *data)
+{
+       int *count = data;
+
+       (*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+       const struct index_state *state = &the_index;
+       int i, ret = 0;
+
+       for (i = 0; i < state->cache_nr; i++)
+               if (ce_stage(state->cache[i]))
+                       ret++;
+
+       return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+       struct tree *trees[MAX_UNPACK_TREES];
+       struct unpack_trees_options opts;
+       struct tree_desc t[MAX_UNPACK_TREES];
+       int i, fd, nr_trees = 0;
+       struct dir_struct dir;
+       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+       if (read_cache_unmerged())
+               die("you need to resolve your current index first");
+
+       fd = hold_locked_index(lock_file, 1);
+
+       memset(&trees, 0, sizeof(trees));
+       memset(&opts, 0, sizeof(opts));
+       memset(&t, 0, sizeof(t));
+       dir.show_ignored = 1;
+       dir.exclude_per_dir = ".gitignore";
+       opts.dir = &dir;
+
+       opts.head_idx = 1;
+       opts.src_index = &the_index;
+       opts.dst_index = &the_index;
+       opts.update = 1;
+       opts.verbose_update = 1;
+       opts.merge = 1;
+       opts.fn = twoway_merge;
+
+       trees[nr_trees] = parse_tree_indirect(head);
+       if (!trees[nr_trees++])
+               return -1;
+       trees[nr_trees] = parse_tree_indirect(remote);
+       if (!trees[nr_trees++])
+               return -1;
+       for (i = 0; i < nr_trees; i++) {
+               parse_tree(trees[i]);
+               init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+       }
+       if (unpack_trees(nr_trees, t, &opts))
+               return -1;
+       if (write_cache(fd, active_cache, active_nr) ||
+               commit_locked_index(lock_file))
+               die("unable to write new index file");
+       return 0;
+}
+
+static void split_merge_strategies(const char *string, struct strategy **list,
+                                  int *nr, int *alloc)
+{
+       char *p, *q, *buf;
+
+       if (!string)
+               return;
+
+       buf = xstrdup(string);
+       q = buf;
+       for (;;) {
+               p = strchr(q, ' ');
+               if (!p) {
+                       ALLOC_GROW(*list, *nr + 1, *alloc);
+                       (*list)[(*nr)++].name = xstrdup(q);
+                       free(buf);
+                       return;
+               } else {
+                       *p = '\0';
+                       ALLOC_GROW(*list, *nr + 1, *alloc);
+                       (*list)[(*nr)++].name = xstrdup(q);
+                       q = ++p;
+               }
+       }
+}
+
+static void add_strategies(const char *string, unsigned attr)
+{
+       struct strategy *list = NULL;
+       int list_alloc = 0, list_nr = 0, i;
+
+       memset(&list, 0, sizeof(list));
+       split_merge_strategies(string, &list, &list_nr, &list_alloc);
+       if (list != NULL) {
+               for (i = 0; i < list_nr; i++) {
+                       struct strategy *s;
+
+                       s = get_strategy(list[i].name);
+                       if (s)
+                               append_strategy(s);
+               }
+               return;
+       }
+       for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+               if (all_strategy[i].attr & attr)
+                       append_strategy(&all_strategy[i]);
+
+}
+
+static int merge_trivial(void)
+{
+       unsigned char result_tree[20], result_commit[20];
+       struct commit_list parent;
+
+       write_tree_trivial(result_tree);
+       printf("Wonderful.\n");
+       parent.item = remoteheads->item;
+       parent.next = NULL;
+       commit_tree(merge_msg.buf, result_tree, &parent, result_commit);
+       finish(result_commit, "In-index merge");
+       drop_save();
+       return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+                           unsigned char *result_tree,
+                           const char *wt_strategy)
+{
+       struct commit_list *parents = NULL, *j;
+       struct strbuf buf = STRBUF_INIT;
+       unsigned char result_commit[20];
+
+       free_commit_list(common);
+       if (allow_fast_forward) {
+               parents = remoteheads;
+               commit_list_insert(lookup_commit(head), &parents);
+               parents = reduce_heads(parents);
+       } else {
+               struct commit_list **pptr = &parents;
+
+               pptr = &commit_list_insert(lookup_commit(head),
+                               pptr)->next;
+               for (j = remoteheads; j; j = j->next)
+                       pptr = &commit_list_insert(j->item, pptr)->next;
+       }
+       free_commit_list(remoteheads);
+       strbuf_addch(&merge_msg, '\n');
+       commit_tree(merge_msg.buf, result_tree, parents, result_commit);
+       strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
+       finish(result_commit, buf.buf);
+       strbuf_release(&buf);
+       drop_save();
+       return 0;
+}
+
+static int suggest_conflicts(void)
+{
+       FILE *fp;
+       int pos;
+
+       fp = fopen(git_path("MERGE_MSG"), "a");
+       if (!fp)
+               die("Could open %s for writing", git_path("MERGE_MSG"));
+       fprintf(fp, "\nConflicts:\n");
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+
+               if (ce_stage(ce)) {
+                       fprintf(fp, "\t%s\n", ce->name);
+                       while (pos + 1 < active_nr &&
+                                       !strcmp(ce->name,
+                                               active_cache[pos + 1]->name))
+                               pos++;
+               }
+       }
+       fclose(fp);
+       rerere();
+       printf("Automatic merge failed; "
+                       "fix conflicts and then commit the result.\n");
+       return 1;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+       struct commit *second_token = NULL;
+       if (argc > 1) {
+               unsigned char second_sha1[20];
+
+               if (get_sha1(argv[1], second_sha1))
+                       return NULL;
+               second_token = lookup_commit_reference_gently(second_sha1, 0);
+               if (!second_token)
+                       die("'%s' is not a commit", argv[1]);
+               if (hashcmp(second_token->object.sha1, head))
+                       return NULL;
+       }
+       return second_token;
+}
+
+static int evaluate_result(void)
+{
+       int cnt = 0;
+       struct rev_info rev;
+
+       if (read_cache() < 0)
+               die("failed to read the cache");
+
+       /* Check how many files differ. */
+       init_revisions(&rev, "");
+       setup_revisions(0, NULL, &rev, NULL);
+       rev.diffopt.output_format |=
+               DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = count_diff_files;
+       rev.diffopt.format_callback_data = &cnt;
+       run_diff_files(&rev, 0);
+
+       /*
+        * Check how many unmerged entries are
+        * there.
+        */
+       cnt += count_unmerged_entries();
+
+       return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+       unsigned char result_tree[20];
+       struct strbuf buf;
+       const char *head_arg;
+       int flag, head_invalid = 0, i;
+       int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+       struct commit_list *common = NULL;
+       const char *best_strategy = NULL, *wt_strategy = NULL;
+       struct commit_list **remotes = &remoteheads;
+
+       setup_work_tree();
+       if (unmerged_cache())
+               die("You are in the middle of a conflicted merge.");
+
+       /*
+        * Check if we are _not_ on a detached HEAD, i.e. if there is a
+        * current branch.
+        */
+       branch = resolve_ref("HEAD", head, 0, &flag);
+       if (branch && !prefixcmp(branch, "refs/heads/"))
+               branch += 11;
+       if (is_null_sha1(head))
+               head_invalid = 1;
+
+       git_config(git_merge_config, NULL);
+
+       /* for color.ui */
+       if (diff_use_color_default == -1)
+               diff_use_color_default = git_use_color_default;
+
+       argc = parse_options(argc, argv, builtin_merge_options,
+                       builtin_merge_usage, 0);
+
+       if (squash) {
+               if (!allow_fast_forward)
+                       die("You cannot combine --squash with --no-ff.");
+               option_commit = 0;
+       }
+
+       if (!argc)
+               usage_with_options(builtin_merge_usage,
+                       builtin_merge_options);
+
+       /*
+        * This could be traditional "merge <msg> HEAD <commit>..."  and
+        * the way we can tell it is to see if the second token is HEAD,
+        * but some people might have misused the interface and used a
+        * committish that is the same as HEAD there instead.
+        * Traditional format never would have "-m" so it is an
+        * additional safety measure to check for it.
+        */
+       strbuf_init(&buf, 0);
+
+       if (!have_message && is_old_style_invocation(argc, argv)) {
+               strbuf_addstr(&merge_msg, argv[0]);
+               head_arg = argv[1];
+               argv += 2;
+               argc -= 2;
+       } else if (head_invalid) {
+               struct object *remote_head;
+               /*
+                * If the merged head is a valid one there is no reason
+                * to forbid "git merge" into a branch yet to be born.
+                * We do the same for "git pull".
+                */
+               if (argc != 1)
+                       die("Can merge only exactly one commit into "
+                               "empty head");
+               remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+               if (!remote_head)
+                       die("%s - not something we can merge", argv[0]);
+               update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+                               DIE_ON_ERR);
+               reset_hard(remote_head->sha1, 0);
+               return 0;
+       } else {
+               struct strbuf msg;
+
+               /* We are invoked directly as the first-class UI. */
+               head_arg = "HEAD";
+
+               /*
+                * All the rest are the commits being merged;
+                * prepare the standard merge summary message to
+                * be appended to the given message.  If remote
+                * is invalid we will die later in the common
+                * codepath so we discard the error in this
+                * loop.
+                */
+               strbuf_init(&msg, 0);
+               for (i = 0; i < argc; i++)
+                       merge_name(argv[i], &msg);
+               fmt_merge_msg(option_log, &msg, &merge_msg);
+               if (merge_msg.len)
+                       strbuf_setlen(&merge_msg, merge_msg.len-1);
+       }
+
+       if (head_invalid || !argc)
+               usage_with_options(builtin_merge_usage,
+                       builtin_merge_options);
+
+       strbuf_addstr(&buf, "merge");
+       for (i = 0; i < argc; i++)
+               strbuf_addf(&buf, " %s", argv[i]);
+       setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+       strbuf_reset(&buf);
+
+       for (i = 0; i < argc; i++) {
+               struct object *o;
+
+               o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+               if (!o)
+                       die("%s - not something we can merge", argv[i]);
+               remotes = &commit_list_insert(lookup_commit(o->sha1),
+                       remotes)->next;
+
+               strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+               setenv(buf.buf, argv[i], 1);
+               strbuf_reset(&buf);
+       }
+
+       if (!use_strategies) {
+               if (!remoteheads->next)
+                       add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+               else
+                       add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+       }
+
+       for (i = 0; i < use_strategies_nr; i++) {
+               if (use_strategies[i]->attr & NO_FAST_FORWARD)
+                       allow_fast_forward = 0;
+               if (use_strategies[i]->attr & NO_TRIVIAL)
+                       allow_trivial = 0;
+       }
+
+       if (!remoteheads->next)
+               common = get_merge_bases(lookup_commit(head),
+                               remoteheads->item, 1);
+       else {
+               struct commit_list *list = remoteheads;
+               commit_list_insert(lookup_commit(head), &list);
+               common = get_octopus_merge_bases(list);
+               free(list);
+       }
+
+       update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+               DIE_ON_ERR);
+
+       if (!common)
+               ; /* No common ancestors found. We need a real merge. */
+       else if (!remoteheads->next && !common->next &&
+                       common->item == remoteheads->item) {
+               /*
+                * If head can reach all the merge then we are up to date.
+                * but first the most common case of merging one remote.
+                */
+               finish_up_to_date("Already up-to-date.");
+               return 0;
+       } else if (allow_fast_forward && !remoteheads->next &&
+                       !common->next &&
+                       !hashcmp(common->item->object.sha1, head)) {
+               /* Again the most common case of merging one remote. */
+               struct strbuf msg;
+               struct object *o;
+               char hex[41];
+
+               strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+               printf("Updating %s..%s\n",
+                       hex,
+                       find_unique_abbrev(remoteheads->item->object.sha1,
+                       DEFAULT_ABBREV));
+               refresh_cache(REFRESH_QUIET);
+               strbuf_init(&msg, 0);
+               strbuf_addstr(&msg, "Fast forward");
+               if (have_message)
+                       strbuf_addstr(&msg,
+                               " (no commit created; -m option ignored)");
+               o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+                       0, NULL, OBJ_COMMIT);
+               if (!o)
+                       return 1;
+
+               if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+                       return 1;
+
+               finish(o->sha1, msg.buf);
+               drop_save();
+               return 0;
+       } else if (!remoteheads->next && common->next)
+               ;
+               /*
+                * We are not doing octopus and not fast forward.  Need
+                * a real merge.
+                */
+       else if (!remoteheads->next && !common->next && option_commit) {
+               /*
+                * We are not doing octopus, not fast forward, and have
+                * only one common.
+                */
+               refresh_cache(REFRESH_QUIET);
+               if (allow_trivial) {
+                       /* See if it is really trivial. */
+                       git_committer_info(IDENT_ERROR_ON_NO_NAME);
+                       printf("Trying really trivial in-index merge...\n");
+                       if (!read_tree_trivial(common->item->object.sha1,
+                                       head, remoteheads->item->object.sha1))
+                               return merge_trivial();
+                       printf("Nope.\n");
+               }
+       } else {
+               /*
+                * An octopus.  If we can reach all the remote we are up
+                * to date.
+                */
+               int up_to_date = 1;
+               struct commit_list *j;
+
+               for (j = remoteheads; j; j = j->next) {
+                       struct commit_list *common_one;
+
+                       /*
+                        * Here we *have* to calculate the individual
+                        * merge_bases again, otherwise "git merge HEAD^
+                        * HEAD^^" would be missed.
+                        */
+                       common_one = get_merge_bases(lookup_commit(head),
+                               j->item, 1);
+                       if (hashcmp(common_one->item->object.sha1,
+                               j->item->object.sha1)) {
+                               up_to_date = 0;
+                               break;
+                       }
+               }
+               if (up_to_date) {
+                       finish_up_to_date("Already up-to-date. Yeeah!");
+                       return 0;
+               }
+       }
+
+       /* We are going to make a new commit. */
+       git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+       /*
+        * At this point, we need a real merge.  No matter what strategy
+        * we use, it would operate on the index, possibly affecting the
+        * working tree, and when resolved cleanly, have the desired
+        * tree in the index -- this means that the index must be in
+        * sync with the head commit.  The strategies are responsible
+        * to ensure this.
+        */
+       if (use_strategies_nr != 1) {
+               /*
+                * Stash away the local changes so that we can try more
+                * than one.
+                */
+               save_state();
+       } else {
+               memcpy(stash, null_sha1, 20);
+       }
+
+       for (i = 0; i < use_strategies_nr; i++) {
+               int ret;
+               if (i) {
+                       printf("Rewinding the tree to pristine...\n");
+                       restore_state();
+               }
+               if (use_strategies_nr != 1)
+                       printf("Trying merge strategy %s...\n",
+                               use_strategies[i]->name);
+               /*
+                * Remember which strategy left the state in the working
+                * tree.
+                */
+               wt_strategy = use_strategies[i]->name;
+
+               ret = try_merge_strategy(use_strategies[i]->name,
+                       common, head_arg);
+               if (!option_commit && !ret) {
+                       merge_was_ok = 1;
+                       /*
+                        * This is necessary here just to avoid writing
+                        * the tree, but later we will *not* exit with
+                        * status code 1 because merge_was_ok is set.
+                        */
+                       ret = 1;
+               }
+
+               if (ret) {
+                       /*
+                        * The backend exits with 1 when conflicts are
+                        * left to be resolved, with 2 when it does not
+                        * handle the given merge at all.
+                        */
+                       if (ret == 1) {
+                               int cnt = evaluate_result();
+
+                               if (best_cnt <= 0 || cnt <= best_cnt) {
+                                       best_strategy = use_strategies[i]->name;
+                                       best_cnt = cnt;
+                               }
+                       }
+                       if (merge_was_ok)
+                               break;
+                       else
+                               continue;
+               }
+
+               /* Automerge succeeded. */
+               write_tree_trivial(result_tree);
+               automerge_was_ok = 1;
+               break;
+       }
+
+       /*
+        * If we have a resulting tree, that means the strategy module
+        * auto resolved the merge cleanly.
+        */
+       if (automerge_was_ok)
+               return finish_automerge(common, result_tree, wt_strategy);
+
+       /*
+        * Pick the result from the best strategy and have the user fix
+        * it up.
+        */
+       if (!best_strategy) {
+               restore_state();
+               if (use_strategies_nr > 1)
+                       fprintf(stderr,
+                               "No merge strategy handled the merge.\n");
+               else
+                       fprintf(stderr, "Merge with strategy %s failed.\n",
+                               use_strategies[0]->name);
+               return 2;
+       } else if (best_strategy == wt_strategy)
+               ; /* We already have its result in the working tree. */
+       else {
+               printf("Rewinding the tree to pristine...\n");
+               restore_state();
+               printf("Using the %s to prepare resolving by hand.\n",
+                       best_strategy);
+               try_merge_strategy(best_strategy, common, head_arg);
+       }
+
+       if (squash)
+               finish(NULL, NULL);
+       else {
+               int fd;
+               struct commit_list *j;
+
+               for (j = remoteheads; j; j = j->next)
+                       strbuf_addf(&buf, "%s\n",
+                               sha1_to_hex(j->item->object.sha1));
+               fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+               if (fd < 0)
+                       die("Could open %s for writing",
+                               git_path("MERGE_HEAD"));
+               if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+                       die("Could not write to %s", git_path("MERGE_HEAD"));
+               close(fd);
+               strbuf_addch(&merge_msg, '\n');
+               fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+               if (fd < 0)
+                       die("Could open %s for writing", git_path("MERGE_MSG"));
+               if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+                       merge_msg.len)
+                       die("Could not write to %s", git_path("MERGE_MSG"));
+               close(fd);
+       }
+
+       if (merge_was_ok) {
+               fprintf(stderr, "Automatic merge went well; "
+                       "stopped before committing as requested\n");
+               return 0;
+       } else
+               return suggest_conflicts();
+}
index 5a09e17f1ad4fe19cf4e7cc29235d14d35405168..72a6de302f88728af17ce5c5c6983c5267afc6f6 100644 (file)
@@ -29,30 +29,6 @@ static int list_tree(unsigned char *sha1)
        return 0;
 }
 
-static int read_cache_unmerged(void)
-{
-       int i;
-       struct cache_entry **dst;
-       struct cache_entry *last = NULL;
-
-       read_cache();
-       dst = active_cache;
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (ce_stage(ce)) {
-                       remove_name_hash(ce);
-                       if (last && !strcmp(ce->name, last->name))
-                               continue;
-                       cache_tree_invalidate_path(active_cache_tree, ce->name);
-                       last = ce;
-                       continue;
-               }
-               *dst++ = ce;
-       }
-       active_nr = dst - active_cache;
-       return !!last;
-}
-
 static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
 {
        struct tree_desc desc;
index 145dd8568c7a644344d8bb25ba395b10c5835c5c..1491354a9d5d22546acde09021b0f666f6740257 100644 (file)
@@ -29,12 +29,6 @@ static inline int postfixcmp(const char *string, const char *postfix)
        return strcmp(string + len1 - len2, postfix);
 }
 
-static inline const char *skip_prefix(const char *name, const char *prefix)
-{
-       return !name ? "" :
-               prefixcmp(name, prefix) ?  name : name + strlen(prefix);
-}
-
 static int opt_parse_track(const struct option *opt, const char *arg, int not)
 {
        struct path_list *list = opt->value;
@@ -182,12 +176,18 @@ static int config_read_branches(const char *key, const char *value, void *cb)
                        info->remote = xstrdup(value);
                } else {
                        char *space = strchr(value, ' ');
-                       value = skip_prefix(value, "refs/heads/");
+                       const char *ptr = skip_prefix(value, "refs/heads/");
+                       if (ptr)
+                               value = ptr;
                        while (space) {
                                char *merge;
                                merge = xstrndup(value, space - value);
                                path_list_append(merge, &info->merge);
-                               value = skip_prefix(space + 1, "refs/heads/");
+                               ptr = skip_prefix(space + 1, "refs/heads/");
+                               if (ptr)
+                                       value = ptr;
+                               else
+                                       value = space + 1;
                                space = strchr(value, ' ');
                        }
                        path_list_append(xstrdup(value), &info->merge);
@@ -219,7 +219,12 @@ static int handle_one_branch(const char *refname,
        refspec.dst = (char *)refname;
        if (!remote_find_tracking(states->remote, &refspec)) {
                struct path_list_item *item;
-               const char *name = skip_prefix(refspec.src, "refs/heads/");
+               const char *name, *ptr;
+               ptr = skip_prefix(refspec.src, "refs/heads/");
+               if (ptr)
+                       name = ptr;
+               else
+                       name = refspec.src;
                /* symbolic refs pointing nowhere were handled already */
                if ((flags & REF_ISSYMREF) ||
                                unsorted_path_list_has_path(&states->tracked,
@@ -248,6 +253,7 @@ static int get_ref_states(const struct ref *ref, struct ref_states *states)
                struct path_list *target = &states->tracked;
                unsigned char sha1[20];
                void *util = NULL;
+               const char *ptr;
 
                if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
                        target = &states->new;
@@ -256,8 +262,10 @@ static int get_ref_states(const struct ref *ref, struct ref_states *states)
                        if (hashcmp(sha1, ref->new_sha1))
                                util = &states;
                }
-               path_list_append(skip_prefix(ref->name, "refs/heads/"),
-                               target)->util = util;
+               ptr = skip_prefix(ref->name, "refs/heads/");
+               if (!ptr)
+                       ptr = ref->name;
+               path_list_append(ptr, target)->util = util;
        }
        free_refs(fetch_map);
 
@@ -522,10 +530,15 @@ static int show(int argc, const char **argv)
                                        "es" : "");
                        for (i = 0; i < states.remote->push_refspec_nr; i++) {
                                struct refspec *spec = states.remote->push + i;
+                               const char *p = "", *q = "";
+                               if (spec->src)
+                                       p = skip_prefix(spec->src, "refs/heads/");
+                               if (spec->dst)
+                                       q = skip_prefix(spec->dst, "refs/heads/");
                                printf(" %s%s%s%s", spec->force ? "+" : "",
-                                       skip_prefix(spec->src, "refs/heads/"),
+                                       p ? p : spec->src,
                                        spec->dst ? ":" : "",
-                                       skip_prefix(spec->dst, "refs/heads/"));
+                                       q ? q : spec->dst);
                        }
                        printf("\n");
                }
index 839b26e8e0a10a498a81bf27d907543e7c256cb8..5d40e16932a4d8f3761b9aaebc21b630be904f8a 100644 (file)
@@ -1,11 +1,10 @@
 #include "builtin.h"
 #include "cache.h"
 #include "path-list.h"
+#include "rerere.h"
 #include "xdiff/xdiff.h"
 #include "xdiff-interface.h"
 
-#include <time.h>
-
 static const char git_rerere_usage[] =
 "git-rerere [clear | status | diff | gc]";
 
@@ -13,14 +12,6 @@ static const char git_rerere_usage[] =
 static int cutoff_noresolve = 15;
 static int cutoff_resolve = 60;
 
-/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
-static int rerere_enabled = -1;
-
-/* automatically update cleanly resolved paths to the index */
-static int rerere_autoupdate;
-
-static char *merge_rr_path;
-
 static const char *rr_path(const char *name, const char *file)
 {
        return git_path("rr-cache/%s/%s", name, file);
@@ -38,179 +29,6 @@ static int has_resolution(const char *name)
        return !stat(rr_path(name, "postimage"), &st);
 }
 
-static void read_rr(struct path_list *rr)
-{
-       unsigned char sha1[20];
-       char buf[PATH_MAX];
-       FILE *in = fopen(merge_rr_path, "r");
-       if (!in)
-               return;
-       while (fread(buf, 40, 1, in) == 1) {
-               int i;
-               char *name;
-               if (get_sha1_hex(buf, sha1))
-                       die("corrupt MERGE_RR");
-               buf[40] = '\0';
-               name = xstrdup(buf);
-               if (fgetc(in) != '\t')
-                       die("corrupt MERGE_RR");
-               for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++)
-                       ; /* do nothing */
-               if (i == sizeof(buf))
-                       die("filename too long");
-               path_list_insert(buf, rr)->util = name;
-       }
-       fclose(in);
-}
-
-static struct lock_file write_lock;
-
-static int write_rr(struct path_list *rr, int out_fd)
-{
-       int i;
-       for (i = 0; i < rr->nr; i++) {
-               const char *path;
-               int length;
-               if (!rr->items[i].util)
-                       continue;
-               path = rr->items[i].path;
-               length = strlen(path) + 1;
-               if (write_in_full(out_fd, rr->items[i].util, 40) != 40 ||
-                   write_in_full(out_fd, "\t", 1) != 1 ||
-                   write_in_full(out_fd, path, length) != length)
-                       die("unable to write rerere record");
-       }
-       if (commit_lock_file(&write_lock) != 0)
-               die("unable to write rerere record");
-       return 0;
-}
-
-static int handle_file(const char *path,
-        unsigned char *sha1, const char *output)
-{
-       SHA_CTX ctx;
-       char buf[1024];
-       int hunk = 0, hunk_no = 0;
-       struct strbuf one, two;
-       FILE *f = fopen(path, "r");
-       FILE *out = NULL;
-
-       if (!f)
-               return error("Could not open %s", path);
-
-       if (output) {
-               out = fopen(output, "w");
-               if (!out) {
-                       fclose(f);
-                       return error("Could not write %s", output);
-               }
-       }
-
-       if (sha1)
-               SHA1_Init(&ctx);
-
-       strbuf_init(&one, 0);
-       strbuf_init(&two,  0);
-       while (fgets(buf, sizeof(buf), f)) {
-               if (!prefixcmp(buf, "<<<<<<< "))
-                       hunk = 1;
-               else if (!prefixcmp(buf, "======="))
-                       hunk = 2;
-               else if (!prefixcmp(buf, ">>>>>>> ")) {
-                       if (strbuf_cmp(&one, &two) > 0)
-                               strbuf_swap(&one, &two);
-                       hunk_no++;
-                       hunk = 0;
-                       if (out) {
-                               fputs("<<<<<<<\n", out);
-                               fwrite(one.buf, one.len, 1, out);
-                               fputs("=======\n", out);
-                               fwrite(two.buf, two.len, 1, out);
-                               fputs(">>>>>>>\n", out);
-                       }
-                       if (sha1) {
-                               SHA1_Update(&ctx, one.buf ? one.buf : "",
-                                           one.len + 1);
-                               SHA1_Update(&ctx, two.buf ? two.buf : "",
-                                           two.len + 1);
-                       }
-                       strbuf_reset(&one);
-                       strbuf_reset(&two);
-               } else if (hunk == 1)
-                       strbuf_addstr(&one, buf);
-               else if (hunk == 2)
-                       strbuf_addstr(&two, buf);
-               else if (out)
-                       fputs(buf, out);
-       }
-       strbuf_release(&one);
-       strbuf_release(&two);
-
-       fclose(f);
-       if (out)
-               fclose(out);
-       if (sha1)
-               SHA1_Final(sha1, &ctx);
-       if (hunk) {
-               if (output)
-                       unlink(output);
-               return error("Could not parse conflict hunks in %s", path);
-       }
-       return hunk_no;
-}
-
-static int find_conflict(struct path_list *conflict)
-{
-       int i;
-       if (read_cache() < 0)
-               return error("Could not read index");
-       for (i = 0; i+1 < active_nr; i++) {
-               struct cache_entry *e2 = active_cache[i];
-               struct cache_entry *e3 = active_cache[i+1];
-               if (ce_stage(e2) == 2 &&
-                   ce_stage(e3) == 3 &&
-                   ce_same_name(e2, e3) &&
-                   S_ISREG(e2->ce_mode) &&
-                   S_ISREG(e3->ce_mode)) {
-                       path_list_insert((const char *)e2->name, conflict);
-                       i++; /* skip over both #2 and #3 */
-               }
-       }
-       return 0;
-}
-
-static int merge(const char *name, const char *path)
-{
-       int ret;
-       mmfile_t cur, base, other;
-       mmbuffer_t result = {NULL, 0};
-       xpparam_t xpp = {XDF_NEED_MINIMAL};
-
-       if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0)
-               return 1;
-
-       if (read_mmfile(&cur, rr_path(name, "thisimage")) ||
-                       read_mmfile(&base, rr_path(name, "preimage")) ||
-                       read_mmfile(&other, rr_path(name, "postimage")))
-               return 1;
-       ret = xdl_merge(&base, &cur, "", &other, "",
-                       &xpp, XDL_MERGE_ZEALOUS, &result);
-       if (!ret) {
-               FILE *f = fopen(path, "w");
-               if (!f)
-                       return error("Could not write to %s", path);
-               fwrite(result.ptr, result.size, 1, f);
-               fclose(f);
-       }
-
-       free(cur.ptr);
-       free(base.ptr);
-       free(other.ptr);
-       free(result.ptr);
-
-       return ret;
-}
-
 static void unlink_rr_item(const char *name)
 {
        unlink(rr_path(name, "thisimage"));
@@ -219,6 +37,17 @@ static void unlink_rr_item(const char *name)
        rmdir(git_path("rr-cache/%s", name));
 }
 
+static int git_rerere_gc_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "gc.rerereresolved"))
+               cutoff_resolve = git_config_int(var, value);
+       else if (!strcmp(var, "gc.rerereunresolved"))
+               cutoff_noresolve = git_config_int(var, value);
+       else
+               return git_default_config(var, value, cb);
+       return 0;
+}
+
 static void garbage_collect(struct path_list *rr)
 {
        struct path_list to_remove = { NULL, 0, 0, 1 };
@@ -227,6 +56,7 @@ static void garbage_collect(struct path_list *rr)
        int i, cutoff;
        time_t now = time(NULL), then;
 
+       git_config(git_rerere_gc_config, NULL);
        dir = opendir(git_path("rr-cache"));
        while ((e = readdir(dir))) {
                const char *name = e->d_name;
@@ -279,181 +109,25 @@ static int diff_two(const char *file1, const char *label1,
        return 0;
 }
 
-static struct lock_file index_lock;
-
-static int update_paths(struct path_list *update)
-{
-       int i;
-       int fd = hold_locked_index(&index_lock, 0);
-       int status = 0;
-
-       if (fd < 0)
-               return -1;
-
-       for (i = 0; i < update->nr; i++) {
-               struct path_list_item *item = &update->items[i];
-               if (add_file_to_cache(item->path, ADD_CACHE_IGNORE_ERRORS))
-                       status = -1;
-       }
-
-       if (!status && active_cache_changed) {
-               if (write_cache(fd, active_cache, active_nr) ||
-                   commit_locked_index(&index_lock))
-                       die("Unable to write new index file");
-       } else if (fd >= 0)
-               rollback_lock_file(&index_lock);
-       return status;
-}
-
-static int do_plain_rerere(struct path_list *rr, int fd)
-{
-       struct path_list conflict = { NULL, 0, 0, 1 };
-       struct path_list update = { NULL, 0, 0, 1 };
-       int i;
-
-       find_conflict(&conflict);
-
-       /*
-        * MERGE_RR records paths with conflicts immediately after merge
-        * failed.  Some of the conflicted paths might have been hand resolved
-        * in the working tree since then, but the initial run would catch all
-        * and register their preimages.
-        */
-
-       for (i = 0; i < conflict.nr; i++) {
-               const char *path = conflict.items[i].path;
-               if (!path_list_has_path(rr, path)) {
-                       unsigned char sha1[20];
-                       char *hex;
-                       int ret;
-                       ret = handle_file(path, sha1, NULL);
-                       if (ret < 1)
-                               continue;
-                       hex = xstrdup(sha1_to_hex(sha1));
-                       path_list_insert(path, rr)->util = hex;
-                       if (mkdir(git_path("rr-cache/%s", hex), 0755))
-                               continue;;
-                       handle_file(path, NULL, rr_path(hex, "preimage"));
-                       fprintf(stderr, "Recorded preimage for '%s'\n", path);
-               }
-       }
-
-       /*
-        * Now some of the paths that had conflicts earlier might have been
-        * hand resolved.  Others may be similar to a conflict already that
-        * was resolved before.
-        */
-
-       for (i = 0; i < rr->nr; i++) {
-               int ret;
-               const char *path = rr->items[i].path;
-               const char *name = (const char *)rr->items[i].util;
-
-               if (has_resolution(name)) {
-                       if (!merge(name, path)) {
-                               fprintf(stderr, "Resolved '%s' using "
-                                               "previous resolution.\n", path);
-                               if (rerere_autoupdate)
-                                       path_list_insert(path, &update);
-                               goto mark_resolved;
-                       }
-               }
-
-               /* Let's see if we have resolved it. */
-               ret = handle_file(path, NULL, NULL);
-               if (ret)
-                       continue;
-
-               fprintf(stderr, "Recorded resolution for '%s'.\n", path);
-               copy_file(rr_path(name, "postimage"), path, 0666);
-       mark_resolved:
-               rr->items[i].util = NULL;
-       }
-
-       if (update.nr)
-               update_paths(&update);
-
-       return write_rr(rr, fd);
-}
-
-static int git_rerere_config(const char *var, const char *value, void *cb)
-{
-       if (!strcmp(var, "gc.rerereresolved"))
-               cutoff_resolve = git_config_int(var, value);
-       else if (!strcmp(var, "gc.rerereunresolved"))
-               cutoff_noresolve = git_config_int(var, value);
-       else if (!strcmp(var, "rerere.enabled"))
-               rerere_enabled = git_config_bool(var, value);
-       else if (!strcmp(var, "rerere.autoupdate"))
-               rerere_autoupdate = git_config_bool(var, value);
-       else
-               return git_default_config(var, value, cb);
-       return 0;
-}
-
-static int is_rerere_enabled(void)
-{
-       struct stat st;
-       const char *rr_cache;
-       int rr_cache_exists;
-
-       if (!rerere_enabled)
-               return 0;
-
-       rr_cache = git_path("rr-cache");
-       rr_cache_exists = !stat(rr_cache, &st) && S_ISDIR(st.st_mode);
-       if (rerere_enabled < 0)
-               return rr_cache_exists;
-
-       if (!rr_cache_exists &&
-           (mkdir(rr_cache, 0777) || adjust_shared_perm(rr_cache)))
-               die("Could not create directory %s", rr_cache);
-       return 1;
-}
-
-static int setup_rerere(struct path_list *merge_rr)
-{
-       int fd;
-
-       git_config(git_rerere_config, NULL);
-       if (!is_rerere_enabled())
-               return -1;
-
-       merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR"));
-       fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
-       read_rr(merge_rr);
-       return fd;
-}
-
-int rerere(void)
-{
-       struct path_list merge_rr = { NULL, 0, 0, 1 };
-       int fd;
-
-       fd = setup_rerere(&merge_rr);
-       if (fd < 0)
-               return 0;
-       return do_plain_rerere(&merge_rr, fd);
-}
-
 int cmd_rerere(int argc, const char **argv, const char *prefix)
 {
        struct path_list merge_rr = { NULL, 0, 0, 1 };
        int i, fd;
 
+       if (argc < 2)
+               return rerere();
+
        fd = setup_rerere(&merge_rr);
        if (fd < 0)
                return 0;
 
-       if (argc < 2)
-               return do_plain_rerere(&merge_rr, fd);
-       else if (!strcmp(argv[1], "clear")) {
+       if (!strcmp(argv[1], "clear")) {
                for (i = 0; i < merge_rr.nr; i++) {
                        const char *name = (const char *)merge_rr.items[i].util;
                        if (!has_resolution(name))
                                unlink_rr_item(name);
                }
-               unlink(merge_rr_path);
+               unlink(git_path("rr-cache/MERGE_RR"));
        } else if (!strcmp(argv[1], "gc"))
                garbage_collect(&merge_rr);
        else if (!strcmp(argv[1], "status"))
index 01362022c0990cc785d7bcc77f21802c5e75b3e2..f8bcbfce4009337c4aea0b923a620fc4697e4df3 100644 (file)
@@ -154,6 +154,15 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
        if (!author)
                die("Missing author: %s",
                    sha1_to_hex(commit->object.sha1));
+       if (log->user_format) {
+               struct strbuf buf = STRBUF_INIT;
+
+               pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &buf,
+                       DEFAULT_ABBREV, "", "", DATE_NORMAL, 0);
+               insert_one_record(log, author, buf.buf);
+               strbuf_release(&buf);
+               return;
+       }
        if (*buffer)
                buffer++;
        insert_one_record(log, author, !*buffer ? "<none>" : buffer);
@@ -271,6 +280,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
                usage_with_options(shortlog_usage, options);
        }
 
+       log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT;
+
        /* assume HEAD if from a tty */
        if (!nongit && !rev.pending.nr && isatty(0))
                add_head_to_pending(&rev);
index 3c97c696a50cb55e78774c743436cf50cad35f96..a70922b21c4bc2ff552e00cd65ecba6c17a33000 100644 (file)
@@ -202,6 +202,7 @@ static int do_sign(struct strbuf *buffer)
        const char *args[4];
        char *bracket;
        int len;
+       int i, j;
 
        if (!*signingkey) {
                if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
@@ -241,6 +242,15 @@ static int do_sign(struct strbuf *buffer)
        if (finish_command(&gpg) || !len || len < 0)
                return error("gpg failed to sign the tag");
 
+       /* Strip CR from the line endings, in case we are on Windows. */
+       for (i = j = 0; i < buffer->len; i++)
+               if (buffer->buf[i] != '\r') {
+                       if (i != j)
+                               buffer->buf[j] = buffer->buf[i];
+                       j++;
+               }
+       strbuf_setlen(buffer, j);
+
        return 0;
 }
 
index b460b2da6f41797ca3190646eb3fe5cafd1abd84..0e605d4f4aa748c0b3c34aeb75a1b5a765114b12 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -2,6 +2,9 @@
 #define BUILTIN_H
 
 #include "git-compat-util.h"
+#include "strbuf.h"
+#include "cache.h"
+#include "commit.h"
 
 extern const char git_version_string[];
 extern const char git_usage_string[];
@@ -11,6 +14,10 @@ extern void list_common_cmds_help(void);
 extern void help_unknown_cmd(const char *cmd);
 extern void prune_packed_objects(int);
 extern int read_line_with_nul(char *buf, int size, FILE *file);
+extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
+       struct strbuf *out);
+extern int commit_tree(const char *msg, unsigned char *tree,
+               struct commit_list *parents, unsigned char *ret);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
 extern int cmd_annotate(int argc, const char **argv, const char *prefix);
@@ -57,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(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);
index 73cb3407077275f82677839d2c9e794c12833c95..5f8ee87bb1c446341b640c2f978a658d6bfcfcd0 100644 (file)
@@ -507,7 +507,7 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size)
        return read_one(&buffer, &size);
 }
 
-struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
+static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
 {
        while (*path) {
                const char *slash;
index 44aad426d319b83eda013e115f35e066cc590cb8..cf8b790874c4a4f5890b360c237ccdd4a5a03de4 100644 (file)
@@ -28,8 +28,6 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
 int cache_tree_fully_valid(struct cache_tree *);
 int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int);
 
-struct cache_tree *cache_tree_find(struct cache_tree *, const char *);
-
 #define WRITE_TREE_UNREADABLE_INDEX (-1)
 #define WRITE_TREE_UNMERGED_INDEX (-2)
 #define WRITE_TREE_PREFIX_ERROR (-3)
diff --git a/cache.h b/cache.h
index 0d8eddac778ace8e4b5f58459f508ac1192dbcff..a779d9207c8ea4d4e8455a89b17f312e0e71332d 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -254,6 +254,7 @@ static inline void remove_name_hash(struct cache_entry *ce)
 
 #define read_cache() read_index(&the_index)
 #define read_cache_from(path) read_index_from(&the_index, (path))
+#define read_cache_unmerged() read_index_unmerged(&the_index)
 #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)
@@ -356,6 +357,7 @@ extern int init_db(const char *template_dir, unsigned int flags);
 /* Initialize and use the cache information */
 extern int read_index(struct index_state *);
 extern int read_index_from(struct index_state *, const char *path);
+extern int read_index_unmerged(struct index_state *);
 extern int write_index(const struct index_state *, int newfd);
 extern int discard_index(struct index_state *);
 extern int unmerged_index(const struct index_state *);
@@ -538,6 +540,9 @@ extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsig
 extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
 extern int force_object_loose(const unsigned char *sha1, time_t mtime);
 
+/* just like read_sha1_file(), but non fatal in presence of bad objects */
+extern void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size);
+
 extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
 
 extern int move_temp_to_file(const char *tmpfile, const char *filename);
@@ -834,5 +839,6 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_
 void overlay_tree_on_cache(const char *tree_name, const char *prefix);
 
 char *alias_lookup(const char *alias);
+int split_cmdline(char *cmdline, const char ***argv);
 
 #endif /* CACHE_H */
index e2d8624d9c19adde87ae521361f4ccd8260c06a0..5148ec552708bb6c98cc64a84647ec7715ad1c29 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -325,6 +325,14 @@ struct commit_list *commit_list_insert(struct commit *item, struct commit_list *
        return new_list;
 }
 
+unsigned commit_list_count(const struct commit_list *l)
+{
+       unsigned c = 0;
+       for (; l; l = l->next )
+               c++;
+       return c;
+}
+
 void free_commit_list(struct commit_list *list)
 {
        while (list) {
@@ -525,26 +533,34 @@ static struct commit *interesting(struct commit_list *list)
        return NULL;
 }
 
-static struct commit_list *merge_bases(struct commit *one, struct commit *two)
+static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
 {
        struct commit_list *list = NULL;
        struct commit_list *result = NULL;
+       int i;
 
-       if (one == two)
-               /* We do not mark this even with RESULT so we do not
-                * have to clean it up.
-                */
-               return commit_list_insert(one, &result);
+       for (i = 0; i < n; i++) {
+               if (one == twos[i])
+                       /*
+                        * We do not mark this even with RESULT so we do not
+                        * have to clean it up.
+                        */
+                       return commit_list_insert(one, &result);
+       }
 
        if (parse_commit(one))
                return NULL;
-       if (parse_commit(two))
-               return NULL;
+       for (i = 0; i < n; i++) {
+               if (parse_commit(twos[i]))
+                       return NULL;
+       }
 
        one->object.flags |= PARENT1;
-       two->object.flags |= PARENT2;
        insert_by_date(one, &list);
-       insert_by_date(two, &list);
+       for (i = 0; i < n; i++) {
+               twos[i]->object.flags |= PARENT2;
+               insert_by_date(twos[i], &list);
+       }
 
        while (interesting(list)) {
                struct commit *commit;
@@ -592,21 +608,53 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two)
        return result;
 }
 
-struct commit_list *get_merge_bases(struct commit *one,
-                                       struct commit *two, int cleanup)
+struct commit_list *get_octopus_merge_bases(struct commit_list *in)
+{
+       struct commit_list *i, *j, *k, *ret = NULL;
+       struct commit_list **pptr = &ret;
+
+       for (i = in; i; i = i->next) {
+               if (!ret)
+                       pptr = &commit_list_insert(i->item, pptr)->next;
+               else {
+                       struct commit_list *new = NULL, *end = NULL;
+
+                       for (j = ret; j; j = j->next) {
+                               struct commit_list *bases;
+                               bases = get_merge_bases(i->item, j->item, 1);
+                               if (!new)
+                                       new = bases;
+                               else
+                                       end->next = bases;
+                               for (k = bases; k; k = k->next)
+                                       end = k;
+                       }
+                       ret = new;
+               }
+       }
+       return ret;
+}
+
+struct commit_list *get_merge_bases_many(struct commit *one,
+                                        int n,
+                                        struct commit **twos,
+                                        int cleanup)
 {
        struct commit_list *list;
        struct commit **rslt;
        struct commit_list *result;
        int cnt, i, j;
 
-       result = merge_bases(one, two);
-       if (one == two)
-               return result;
+       result = merge_bases_many(one, n, twos);
+       for (i = 0; i < n; i++) {
+               if (one == twos[i])
+                       return result;
+       }
        if (!result || !result->next) {
                if (cleanup) {
                        clear_commit_marks(one, all_flags);
-                       clear_commit_marks(two, all_flags);
+                       for (i = 0; i < n; i++)
+                               clear_commit_marks(twos[i], all_flags);
                }
                return result;
        }
@@ -624,12 +672,13 @@ struct commit_list *get_merge_bases(struct commit *one,
        free_commit_list(result);
 
        clear_commit_marks(one, all_flags);
-       clear_commit_marks(two, all_flags);
+       for (i = 0; i < n; i++)
+               clear_commit_marks(twos[i], all_flags);
        for (i = 0; i < cnt - 1; i++) {
                for (j = i+1; j < cnt; j++) {
                        if (!rslt[i] || !rslt[j])
                                continue;
-                       result = merge_bases(rslt[i], rslt[j]);
+                       result = merge_bases_many(rslt[i], 1, &rslt[j]);
                        clear_commit_marks(rslt[i], all_flags);
                        clear_commit_marks(rslt[j], all_flags);
                        for (list = result; list; list = list->next) {
@@ -651,6 +700,12 @@ struct commit_list *get_merge_bases(struct commit *one,
        return result;
 }
 
+struct commit_list *get_merge_bases(struct commit *one, struct commit *two,
+                                   int cleanup)
+{
+       return get_merge_bases_many(one, 1, &two, cleanup);
+}
+
 int in_merge_bases(struct commit *commit, struct commit **reference, int num)
 {
        struct commit_list *bases, *b;
@@ -670,3 +725,55 @@ int in_merge_bases(struct commit *commit, struct commit **reference, int num)
        free_commit_list(bases);
        return ret;
 }
+
+struct commit_list *reduce_heads(struct commit_list *heads)
+{
+       struct commit_list *p;
+       struct commit_list *result = NULL, **tail = &result;
+       struct commit **other;
+       size_t num_head, num_other;
+
+       if (!heads)
+               return NULL;
+
+       /* Avoid unnecessary reallocations */
+       for (p = heads, num_head = 0; p; p = p->next)
+               num_head++;
+       other = xcalloc(sizeof(*other), num_head);
+
+       /* For each commit, see if it can be reached by others */
+       for (p = heads; p; p = p->next) {
+               struct commit_list *q, *base;
+
+               /* Do we already have this in the result? */
+               for (q = result; q; q = q->next)
+                       if (p->item == q->item)
+                               break;
+               if (q)
+                       continue;
+
+               num_other = 0;
+               for (q = heads; q; q = q->next) {
+                       if (p->item == q->item)
+                               continue;
+                       other[num_other++] = q->item;
+               }
+               if (num_other)
+                       base = get_merge_bases_many(p->item, num_other, other, 1);
+               else
+                       base = NULL;
+               /*
+                * If p->item does not have anything common with other
+                * commits, there won't be any merge base.  If it is
+                * reachable from some of the others, p->item will be
+                * the merge base.  If its history is connected with
+                * others, but p->item is not reachable by others, we
+                * will get something other than p->item back.
+                */
+               if (!base || (base->item != p->item))
+                       tail = &(commit_list_insert(p->item, tail)->next);
+               free_commit_list(base);
+       }
+       free(other);
+       return result;
+}
index 2d94d4148ed4048469ba79cae4f6ff2a2e3f9bca..77de9621d9c926c6fb8a2bf9ca81c6c376a2ad41 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -41,6 +41,7 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size);
 int parse_commit(struct commit *item);
 
 struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p);
+unsigned commit_list_count(const struct commit_list *l);
 struct commit_list * insert_by_date(struct commit *item, struct commit_list **list);
 
 void free_commit_list(struct commit_list *list);
@@ -120,6 +121,7 @@ int read_graft_file(const char *graft_file);
 struct commit_graft *lookup_commit_graft(const unsigned char *sha1);
 
 extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
+extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
 
 extern int register_shallow(const unsigned char *sha1);
 extern int unregister_shallow(const unsigned char *sha1);
@@ -131,11 +133,12 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
 int in_merge_bases(struct commit *, struct commit **, int);
 
 extern int interactive_add(int argc, const char **argv, const char *prefix);
-extern int rerere(void);
 
 static inline int single_parent(struct commit *commit)
 {
        return commit->parents && !commit->parents->next;
 }
 
+struct commit_list *reduce_heads(struct commit_list *heads);
+
 #endif /* COMMIT_H */
index 3a05fe7da656ebb5d1699937013a1ab517bbee3c..c0bc849e45dc511202d1e7602f570de47630e6aa 100644 (file)
@@ -1017,3 +1017,25 @@ sig_handler_t mingw_signal(int sig, sig_handler_t handler)
        timer_fn = handler;
        return old;
 }
+
+static const char *make_backslash_path(const char *path)
+{
+       static char buf[PATH_MAX + 1];
+       char *c;
+
+       if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
+               die("Too long path: %.*s", 60, path);
+
+       for (c = buf; *c; c++) {
+               if (*c == '/')
+                       *c = '\\';
+       }
+       return buf;
+}
+
+void mingw_open_html(const char *unixpath)
+{
+       const char *htmlpath = make_backslash_path(unixpath);
+       printf("Launching default browser to display HTML ...\n");
+       ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0);
+}
index 6bc049ad993cb95885fbb936c67a3b7c986e56eb..5a3bcee29b7e39f52dcae40aeed611c01cec920e 100644 (file)
@@ -202,6 +202,9 @@ sig_handler_t mingw_signal(int sig, sig_handler_t handler);
 #define PATH_SEP ';'
 #define PRIuMAX "I64u"
 
+void mingw_open_html(const char *path);
+#define open_html mingw_open_html
+
 /*
  * helpers
  */
index 2862cc45cbb1752153bb6050b4e6bd53f6660724..1e066c71e0fbc4b172ef548663e660e636d6a916 100644 (file)
--- a/config.c
+++ b/config.c
@@ -581,15 +581,8 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
 const char *git_etc_gitconfig(void)
 {
        static const char *system_wide;
-       if (!system_wide) {
-               system_wide = ETC_GITCONFIG;
-               if (!is_absolute_path(system_wide)) {
-                       /* interpret path relative to exec-dir */
-                       struct strbuf d = STRBUF_INIT;
-                       strbuf_addf(&d, "%s/%s", git_exec_path(), system_wide);
-                       system_wide = strbuf_detach(&d, NULL);
-               }
-       }
+       if (!system_wide)
+               system_wide = system_path(ETC_GITCONFIG);
        return system_wide;
 }
 
index d268e6f0b3f90d50367bfcd8907d29fdb70a8cc5..29f6cd4e9eb07f39cc08838ed52be6cf2c438c2e 100755 (executable)
 #       git@vger.kernel.org
 #
 
+case "$COMP_WORDBREAKS" in
+*:*) : great ;;
+*)   COMP_WORDBREAKS="$COMP_WORDBREAKS:"
+esac
+
 __gitdir ()
 {
        if [ -z "$1" ]; then
@@ -68,26 +73,26 @@ __git_ps1 ()
        if [ -n "$g" ]; then
                local r
                local b
-               if [ -d "$g/../.dotest" ]
+               if [ -d "$g/rebase" ]
                then
-                       if test -f "$g/../.dotest/rebasing"
+                       if test -f "$g/rebase/rebasing"
                        then
                                r="|REBASE"
-                       elif test -f "$g/../.dotest/applying"
+                       elif test -f "$g/rebase/applying"
                        then
                                r="|AM"
                        else
                                r="|AM/REBASE"
                        fi
                        b="$(git symbolic-ref HEAD 2>/dev/null)"
-               elif [ -f "$g/.dotest-merge/interactive" ]
+               elif [ -f "$g/rebase-merge/interactive" ]
                then
                        r="|REBASE-i"
-                       b="$(cat "$g/.dotest-merge/head-name")"
-               elif [ -d "$g/.dotest-merge" ]
+                       b="$(cat "$g/rebase-merge/head-name")"
+               elif [ -d "$g/rebase-merge" ]
                then
                        r="|REBASE-m"
-                       b="$(cat "$g/.dotest-merge/head-name")"
+                       b="$(cat "$g/rebase-merge/head-name")"
                elif [ -f "$g/MERGE_HEAD" ]
                then
                        r="|MERGING"
@@ -294,9 +299,23 @@ __git_complete_file ()
                        ls="$ref"
                        ;;
            esac
+
+               case "$COMP_WORDBREAKS" in
+               *:*) : great ;;
+               *)   pfx="$ref:$pfx" ;;
+               esac
+
+               local IFS=$'\n'
                COMPREPLY=($(compgen -P "$pfx" \
                        -W "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \
-                               | sed '/^100... blob /s,^.*     ,,
+                               | sed '/^100... blob /{
+                                          s,^.*        ,,
+                                          s,$, ,
+                                      }
+                                      /^120000 blob /{
+                                          s,^.*        ,,
+                                          s,$, ,
+                                      }
                                       /^040000 tree /{
                                           s,^.*        ,,
                                           s,$,/,
@@ -468,8 +487,8 @@ __git_whitespacelist="nowarn warn error error-all strip"
 
 _git_am ()
 {
-       local cur="${COMP_WORDS[COMP_CWORD]}"
-       if [ -d .dotest ]; then
+       local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
+       if [ -d "$dir"/rebase ]; then
                __gitcomp "--skip --resolved"
                return
        fi
@@ -692,7 +711,12 @@ _git_fetch ()
        *)
                case "$cur" in
                *:*)
-                       __gitcomp "$(__git_refs)" "" "${cur#*:}"
+                       local pfx=""
+                       case "$COMP_WORDBREAKS" in
+                       *:*) : great ;;
+                       *)   pfx="${cur%%:*}:" ;;
+                       esac
+                       __gitcomp "$(__git_refs)" "$pfx" "${cur#*:}"
                        ;;
                *)
                        local remote
@@ -868,7 +892,14 @@ _git_push ()
                        git-push)  remote="${COMP_WORDS[1]}" ;;
                        git)       remote="${COMP_WORDS[2]}" ;;
                        esac
-                       __gitcomp "$(__git_refs "$remote")" "" "${cur#*:}"
+
+                       local pfx=""
+                       case "$COMP_WORDBREAKS" in
+                       *:*) : great ;;
+                       *)   pfx="${cur%%:*}:" ;;
+                       esac
+
+                       __gitcomp "$(__git_refs "$remote")" "$pfx" "${cur#*:}"
                        ;;
                +*)
                        __gitcomp "$(__git_refs)" + "${cur#+}"
@@ -884,7 +915,7 @@ _git_push ()
 _git_rebase ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
-       if [ -d .dotest ] || [ -d "$dir"/.dotest-merge ]; then
+       if [ -d "$dir"/rebase ] || [ -d "$dir"/rebase-merge ]; then
                __gitcomp "--continue --skip --abort"
                return
        fi
@@ -905,6 +936,24 @@ _git_rebase ()
        __gitcomp "$(__git_refs)"
 }
 
+_git_send_email ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --*)
+               __gitcomp "--bcc --cc --cc-cmd --chain-reply-to --compose
+                       --dry-run --envelope-sender --from --identity
+                       --in-reply-to --no-chain-reply-to --no-signed-off-by-cc
+                       --no-suppress-from --no-thread --quiet
+                       --signed-off-by-cc --smtp-pass --smtp-server
+                       --smtp-server-port --smtp-ssl --smtp-user --subject
+                       --suppress-cc --suppress-from --thread --to"
+               return
+               ;;
+       esac
+       COMPREPLY=()
+}
+
 _git_config ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -1376,6 +1425,7 @@ _git ()
        rebase)      _git_rebase ;;
        remote)      _git_remote ;;
        reset)       _git_reset ;;
+       send-email)  _git_send_email ;;
        shortlog)    _git_shortlog ;;
        show)        _git_show ;;
        show-branch) _git_log ;;
@@ -1409,64 +1459,11 @@ _gitk ()
 
 complete -o default -o nospace -F _git git
 complete -o default -o nospace -F _gitk gitk
-complete -o default -o nospace -F _git_am git-am
-complete -o default -o nospace -F _git_apply git-apply
-complete -o default -o nospace -F _git_bisect git-bisect
-complete -o default -o nospace -F _git_branch git-branch
-complete -o default -o nospace -F _git_bundle git-bundle
-complete -o default -o nospace -F _git_checkout git-checkout
-complete -o default -o nospace -F _git_cherry git-cherry
-complete -o default -o nospace -F _git_cherry_pick git-cherry-pick
-complete -o default -o nospace -F _git_commit git-commit
-complete -o default -o nospace -F _git_describe git-describe
-complete -o default -o nospace -F _git_diff git-diff
-complete -o default -o nospace -F _git_fetch git-fetch
-complete -o default -o nospace -F _git_format_patch git-format-patch
-complete -o default -o nospace -F _git_gc git-gc
-complete -o default -o nospace -F _git_log git-log
-complete -o default -o nospace -F _git_ls_remote git-ls-remote
-complete -o default -o nospace -F _git_ls_tree git-ls-tree
-complete -o default -o nospace -F _git_merge git-merge
-complete -o default -o nospace -F _git_merge_base git-merge-base
-complete -o default -o nospace -F _git_name_rev git-name-rev
-complete -o default -o nospace -F _git_pull git-pull
-complete -o default -o nospace -F _git_push git-push
-complete -o default -o nospace -F _git_rebase git-rebase
-complete -o default -o nospace -F _git_config git-config
-complete -o default -o nospace -F _git_remote git-remote
-complete -o default -o nospace -F _git_reset git-reset
-complete -o default -o nospace -F _git_shortlog git-shortlog
-complete -o default -o nospace -F _git_show git-show
-complete -o default -o nospace -F _git_stash git-stash
-complete -o default -o nospace -F _git_submodule git-submodule
-complete -o default -o nospace -F _git_svn git-svn
-complete -o default -o nospace -F _git_log git-show-branch
-complete -o default -o nospace -F _git_tag git-tag
-complete -o default -o nospace -F _git_log git-whatchanged
 
 # The following are necessary only for Cygwin, and only are needed
 # when the user has tab-completed the executable name and consequently
 # included the '.exe' suffix.
 #
 if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
-complete -o default -o nospace -F _git_add git-add.exe
-complete -o default -o nospace -F _git_apply git-apply.exe
 complete -o default -o nospace -F _git git.exe
-complete -o default -o nospace -F _git_branch git-branch.exe
-complete -o default -o nospace -F _git_bundle git-bundle.exe
-complete -o default -o nospace -F _git_cherry git-cherry.exe
-complete -o default -o nospace -F _git_describe git-describe.exe
-complete -o default -o nospace -F _git_diff git-diff.exe
-complete -o default -o nospace -F _git_format_patch git-format-patch.exe
-complete -o default -o nospace -F _git_log git-log.exe
-complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe
-complete -o default -o nospace -F _git_merge_base git-merge-base.exe
-complete -o default -o nospace -F _git_name_rev git-name-rev.exe
-complete -o default -o nospace -F _git_push git-push.exe
-complete -o default -o nospace -F _git_config git-config
-complete -o default -o nospace -F _git_shortlog git-shortlog.exe
-complete -o default -o nospace -F _git_show git-show.exe
-complete -o default -o nospace -F _git_log git-show-branch.exe
-complete -o default -o nospace -F _git_tag git-tag.exe
-complete -o default -o nospace -F _git_log git-whatchanged.exe
 fi
index 4fa853fae76dc2ac132e489f5f1b630b3fc0f1de..43b059bbaa193a07c5a020286a3fbba343ccfbc1 100644 (file)
@@ -1252,8 +1252,8 @@ Return the list of files that haven't been handled."
        "\n")
       (when subject (insert subject "\n\n"))
       (cond (msg (insert msg "\n"))
-            ((file-readable-p ".dotest/msg")
-             (insert-file-contents ".dotest/msg"))
+            ((file-readable-p ".git/rebase/msg")
+             (insert-file-contents ".git/rebase/msg"))
             ((file-readable-p ".git/MERGE_MSG")
              (insert-file-contents ".git/MERGE_MSG")))
       ; delete empty lines at end
@@ -1272,9 +1272,9 @@ Return the list of files that haven't been handled."
           (coding-system (git-get-commits-coding-system))
           author-name author-email subject date)
       (when (eq 0 (buffer-size buffer))
-        (when (file-readable-p ".dotest/info")
+        (when (file-readable-p ".git/rebase/info")
           (with-temp-buffer
-            (insert-file-contents ".dotest/info")
+            (insert-file-contents ".git/rebase/info")
             (goto-char (point-min))
             (when (re-search-forward "^Author: \\(.*\\)\nEmail: \\(.*\\)$" nil t)
               (setq author-name (match-string 1))
diff --git a/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh
new file mode 100755 (executable)
index 0000000..8026ccf
--- /dev/null
@@ -0,0 +1,554 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git-merge [options] <remote>...
+git-merge [options] <msg> HEAD <remote>
+--
+stat                 show a diffstat at the end of the merge
+n                    don't show a diffstat at the end of the merge
+summary              (synonym to --stat)
+log                  add list of one-line log to merge commit message
+squash               create a single commit instead of doing a merge
+commit               perform a commit if the merge succeeds (default)
+ff                   allow fast forward (default)
+s,strategy=          merge strategy to use
+m,message=           message to be used for the merge commit (if any)
+"
+
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+require_work_tree
+cd_to_toplevel
+
+test -z "$(git ls-files -u)" ||
+       die "You are in the middle of a conflicted merge."
+
+LF='
+'
+
+all_strategies='recur recursive octopus resolve stupid ours subtree'
+default_twohead_strategies='recursive'
+default_octopus_strategies='octopus'
+no_fast_forward_strategies='subtree ours'
+no_trivial_strategies='recursive recur subtree ours'
+use_strategies=
+
+allow_fast_forward=t
+allow_trivial_merge=t
+squash= no_commit= log_arg=
+
+dropsave() {
+       rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
+                "$GIT_DIR/MERGE_STASH" || exit 1
+}
+
+savestate() {
+       # Stash away any local modifications.
+       git stash create >"$GIT_DIR/MERGE_STASH"
+}
+
+restorestate() {
+        if test -f "$GIT_DIR/MERGE_STASH"
+       then
+               git reset --hard $head >/dev/null
+               git stash apply $(cat "$GIT_DIR/MERGE_STASH")
+               git update-index --refresh >/dev/null
+       fi
+}
+
+finish_up_to_date () {
+       case "$squash" in
+       t)
+               echo "$1 (nothing to squash)" ;;
+       '')
+               echo "$1" ;;
+       esac
+       dropsave
+}
+
+squash_message () {
+       echo Squashed commit of the following:
+       echo
+       git log --no-merges --pretty=medium ^"$head" $remoteheads
+}
+
+finish () {
+       if test '' = "$2"
+       then
+               rlogm="$GIT_REFLOG_ACTION"
+       else
+               echo "$2"
+               rlogm="$GIT_REFLOG_ACTION: $2"
+       fi
+       case "$squash" in
+       t)
+               echo "Squash commit -- not updating HEAD"
+               squash_message >"$GIT_DIR/SQUASH_MSG"
+               ;;
+       '')
+               case "$merge_msg" in
+               '')
+                       echo "No merge message -- not updating HEAD"
+                       ;;
+               *)
+                       git update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1
+                       git gc --auto
+                       ;;
+               esac
+               ;;
+       esac
+       case "$1" in
+       '')
+               ;;
+       ?*)
+               if test "$show_diffstat" = t
+               then
+                       # We want color (if set), but no pager
+                       GIT_PAGER='' git diff --stat --summary -M "$head" "$1"
+               fi
+               ;;
+       esac
+
+       # Run a post-merge hook
+        if test -x "$GIT_DIR"/hooks/post-merge
+        then
+           case "$squash" in
+           t)
+                "$GIT_DIR"/hooks/post-merge 1
+               ;;
+           '')
+                "$GIT_DIR"/hooks/post-merge 0
+               ;;
+           esac
+        fi
+}
+
+merge_name () {
+       remote="$1"
+       rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return
+       bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
+       if test "$rh" = "$bh"
+       then
+               echo "$rh               branch '$remote' of ."
+       elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') &&
+               git show-ref -q --verify "refs/heads/$truname" 2>/dev/null
+       then
+               echo "$rh               branch '$truname' (early part) of ."
+       elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
+       then
+               sed -e 's/      not-for-merge   /               /' -e 1q \
+                       "$GIT_DIR/FETCH_HEAD"
+       else
+               echo "$rh               commit '$remote'"
+       fi
+}
+
+parse_config () {
+       while test $# != 0; do
+               case "$1" in
+               -n|--no-stat|--no-summary)
+                       show_diffstat=false ;;
+               --stat|--summary)
+                       show_diffstat=t ;;
+               --log|--no-log)
+                       log_arg=$1 ;;
+               --squash)
+                       test "$allow_fast_forward" = t ||
+                               die "You cannot combine --squash with --no-ff."
+                       squash=t no_commit=t ;;
+               --no-squash)
+                       squash= no_commit= ;;
+               --commit)
+                       no_commit= ;;
+               --no-commit)
+                       no_commit=t ;;
+               --ff)
+                       allow_fast_forward=t ;;
+               --no-ff)
+                       test "$squash" != t ||
+                               die "You cannot combine --squash with --no-ff."
+                       allow_fast_forward=f ;;
+               -s|--strategy)
+                       shift
+                       case " $all_strategies " in
+                       *" $1 "*)
+                               use_strategies="$use_strategies$1 " ;;
+                       *)
+                               die "available strategies are: $all_strategies" ;;
+                       esac
+                       ;;
+               -m|--message)
+                       shift
+                       merge_msg="$1"
+                       have_message=t
+                       ;;
+               --)
+                       shift
+                       break ;;
+               *)      usage ;;
+               esac
+               shift
+       done
+       args_left=$#
+}
+
+test $# != 0 || usage
+
+have_message=
+
+if branch=$(git-symbolic-ref -q HEAD)
+then
+       mergeopts=$(git config "branch.${branch#refs/heads/}.mergeoptions")
+       if test -n "$mergeopts"
+       then
+               parse_config $mergeopts --
+       fi
+fi
+
+parse_config "$@"
+while test $args_left -lt $#; do shift; done
+
+if test -z "$show_diffstat"; then
+    test "$(git config --bool merge.diffstat)" = false && show_diffstat=false
+    test "$(git config --bool merge.stat)" = false && show_diffstat=false
+    test -z "$show_diffstat" && show_diffstat=t
+fi
+
+# This could be traditional "merge <msg> HEAD <commit>..."  and the
+# way we can tell it is to see if the second token is HEAD, but some
+# people might have misused the interface and used a committish that
+# is the same as HEAD there instead.  Traditional format never would
+# have "-m" so it is an additional safety measure to check for it.
+
+if test -z "$have_message" &&
+       second_token=$(git rev-parse --verify "$2^0" 2>/dev/null) &&
+       head_commit=$(git rev-parse --verify "HEAD" 2>/dev/null) &&
+       test "$second_token" = "$head_commit"
+then
+       merge_msg="$1"
+       shift
+       head_arg="$1"
+       shift
+elif ! git rev-parse --verify HEAD >/dev/null 2>&1
+then
+       # If the merged head is a valid one there is no reason to
+       # forbid "git merge" into a branch yet to be born.  We do
+       # the same for "git pull".
+       if test 1 -ne $#
+       then
+               echo >&2 "Can merge only exactly one commit into empty head"
+               exit 1
+       fi
+
+       rh=$(git rev-parse --verify "$1^0") ||
+               die "$1 - not something we can merge"
+
+       git update-ref -m "initial pull" HEAD "$rh" "" &&
+       git read-tree --reset -u HEAD
+       exit
+
+else
+       # We are invoked directly as the first-class UI.
+       head_arg=HEAD
+
+       # All the rest are the commits being merged; prepare
+       # the standard merge summary message to be appended to
+       # the given message.  If remote is invalid we will die
+       # later in the common codepath so we discard the error
+       # in this loop.
+       merge_name=$(for remote
+               do
+                       merge_name "$remote"
+               done | git fmt-merge-msg $log_arg
+       )
+       merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
+fi
+head=$(git rev-parse --verify "$head_arg"^0) || usage
+
+# All the rest are remote heads
+test "$#" = 0 && usage ;# we need at least one remote head.
+set_reflog_action "merge $*"
+
+remoteheads=
+for remote
+do
+       remotehead=$(git rev-parse --verify "$remote"^0 2>/dev/null) ||
+           die "$remote - not something we can merge"
+       remoteheads="${remoteheads}$remotehead "
+       eval GITHEAD_$remotehead='"$remote"'
+       export GITHEAD_$remotehead
+done
+set x $remoteheads ; shift
+
+case "$use_strategies" in
+'')
+       case "$#" in
+       1)
+               var="`git config --get pull.twohead`"
+               if test -n "$var"
+               then
+                       use_strategies="$var"
+               else
+                       use_strategies="$default_twohead_strategies"
+               fi ;;
+       *)
+               var="`git config --get pull.octopus`"
+               if test -n "$var"
+               then
+                       use_strategies="$var"
+               else
+                       use_strategies="$default_octopus_strategies"
+               fi ;;
+       esac
+       ;;
+esac
+
+for s in $use_strategies
+do
+       for ss in $no_fast_forward_strategies
+       do
+               case " $s " in
+               *" $ss "*)
+                       allow_fast_forward=f
+                       break
+                       ;;
+               esac
+       done
+       for ss in $no_trivial_strategies
+       do
+               case " $s " in
+               *" $ss "*)
+                       allow_trivial_merge=f
+                       break
+                       ;;
+               esac
+       done
+done
+
+case "$#" in
+1)
+       common=$(git merge-base --all $head "$@")
+       ;;
+*)
+       common=$(git show-branch --merge-base $head "$@")
+       ;;
+esac
+echo "$head" >"$GIT_DIR/ORIG_HEAD"
+
+case "$allow_fast_forward,$#,$common,$no_commit" in
+?,*,'',*)
+       # No common ancestors found. We need a real merge.
+       ;;
+?,1,"$1",*)
+       # If head can reach all the merge then we are up to date.
+       # but first the most common case of merging one remote.
+       finish_up_to_date "Already up-to-date."
+       exit 0
+       ;;
+t,1,"$head",*)
+       # Again the most common case of merging one remote.
+       echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)"
+       git update-index --refresh 2>/dev/null
+       msg="Fast forward"
+       if test -n "$have_message"
+       then
+               msg="$msg (no commit created; -m option ignored)"
+       fi
+       new_head=$(git rev-parse --verify "$1^0") &&
+       git read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
+       finish "$new_head" "$msg" || exit
+       dropsave
+       exit 0
+       ;;
+?,1,?*"$LF"?*,*)
+       # We are not doing octopus and not fast forward.  Need a
+       # real merge.
+       ;;
+?,1,*,)
+       # We are not doing octopus, not fast forward, and have only
+       # one common.
+       git update-index --refresh 2>/dev/null
+       case "$allow_trivial_merge" in
+       t)
+               # See if it is really trivial.
+               git var GIT_COMMITTER_IDENT >/dev/null || exit
+               echo "Trying really trivial in-index merge..."
+               if git read-tree --trivial -m -u -v $common $head "$1" &&
+                  result_tree=$(git write-tree)
+               then
+                       echo "Wonderful."
+                       result_commit=$(
+                               printf '%s\n' "$merge_msg" |
+                               git commit-tree $result_tree -p HEAD -p "$1"
+                       ) || exit
+                       finish "$result_commit" "In-index merge"
+                       dropsave
+                       exit 0
+               fi
+               echo "Nope."
+       esac
+       ;;
+*)
+       # An octopus.  If we can reach all the remote we are up to date.
+       up_to_date=t
+       for remote
+       do
+               common_one=$(git merge-base --all $head $remote)
+               if test "$common_one" != "$remote"
+               then
+                       up_to_date=f
+                       break
+               fi
+       done
+       if test "$up_to_date" = t
+       then
+               finish_up_to_date "Already up-to-date. Yeeah!"
+               exit 0
+       fi
+       ;;
+esac
+
+# We are going to make a new commit.
+git var GIT_COMMITTER_IDENT >/dev/null || exit
+
+# At this point, we need a real merge.  No matter what strategy
+# we use, it would operate on the index, possibly affecting the
+# working tree, and when resolved cleanly, have the desired tree
+# in the index -- this means that the index must be in sync with
+# the $head commit.  The strategies are responsible to ensure this.
+
+case "$use_strategies" in
+?*' '?*)
+    # Stash away the local changes so that we can try more than one.
+    savestate
+    single_strategy=no
+    ;;
+*)
+    rm -f "$GIT_DIR/MERGE_STASH"
+    single_strategy=yes
+    ;;
+esac
+
+result_tree= best_cnt=-1 best_strategy= wt_strategy=
+merge_was_ok=
+for strategy in $use_strategies
+do
+    test "$wt_strategy" = '' || {
+       echo "Rewinding the tree to pristine..."
+       restorestate
+    }
+    case "$single_strategy" in
+    no)
+       echo "Trying merge strategy $strategy..."
+       ;;
+    esac
+
+    # Remember which strategy left the state in the working tree
+    wt_strategy=$strategy
+
+    git-merge-$strategy $common -- "$head_arg" "$@"
+    exit=$?
+    if test "$no_commit" = t && test "$exit" = 0
+    then
+        merge_was_ok=t
+       exit=1 ;# pretend it left conflicts.
+    fi
+
+    test "$exit" = 0 || {
+
+       # The backend exits with 1 when conflicts are left to be resolved,
+       # with 2 when it does not handle the given merge at all.
+
+       if test "$exit" -eq 1
+       then
+           cnt=`{
+               git diff-files --name-only
+               git ls-files --unmerged
+           } | wc -l`
+           if test $best_cnt -le 0 -o $cnt -le $best_cnt
+           then
+               best_strategy=$strategy
+               best_cnt=$cnt
+           fi
+       fi
+       continue
+    }
+
+    # Automerge succeeded.
+    result_tree=$(git write-tree) && break
+done
+
+# If we have a resulting tree, that means the strategy module
+# auto resolved the merge cleanly.
+if test '' != "$result_tree"
+then
+    if test "$allow_fast_forward" = "t"
+    then
+        parents=$(git show-branch --independent "$head" "$@")
+    else
+        parents=$(git rev-parse "$head" "$@")
+    fi
+    parents=$(echo "$parents" | sed -e 's/^/-p /')
+    result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit
+    finish "$result_commit" "Merge made by $wt_strategy."
+    dropsave
+    exit 0
+fi
+
+# Pick the result from the best strategy and have the user fix it up.
+case "$best_strategy" in
+'')
+       restorestate
+       case "$use_strategies" in
+       ?*' '?*)
+               echo >&2 "No merge strategy handled the merge."
+               ;;
+       *)
+               echo >&2 "Merge with strategy $use_strategies failed."
+               ;;
+       esac
+       exit 2
+       ;;
+"$wt_strategy")
+       # We already have its result in the working tree.
+       ;;
+*)
+       echo "Rewinding the tree to pristine..."
+       restorestate
+       echo "Using the $best_strategy to prepare resolving by hand."
+       git-merge-$best_strategy $common -- "$head_arg" "$@"
+       ;;
+esac
+
+if test "$squash" = t
+then
+       finish
+else
+       for remote
+       do
+               echo $remote
+       done >"$GIT_DIR/MERGE_HEAD"
+       printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG"
+fi
+
+if test "$merge_was_ok" = t
+then
+       echo >&2 \
+       "Automatic merge went well; stopped before committing as requested"
+       exit 0
+else
+       {
+           echo '
+Conflicts:
+'
+               git ls-files --unmerged |
+               sed -e 's/^[^   ]*      /       /' |
+               uniq
+       } >>"$GIT_DIR/MERGE_MSG"
+       git rerere
+       die "Automatic merge failed; fix conflicts and then commit the result."
+fi
index 352b69d4ceeec411a81469a6146e023aa1d6b338..78efed800d4d64898d438d9590b01be008cfcd36 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -61,6 +61,10 @@ static void gather_stats(const char *buf, unsigned long size, struct text_stat *
                else
                        stats->printable++;
        }
+
+       /* If file ends with EOF then don't count this EOF as non-printable. */
+       if (size >= 1 && buf[size-1] == '\032')
+               stats->nonprintable--;
 }
 
 /*
index b17722d66a7008b822125bb96ab1b7921e8b627b..e7eaff9a68ccbcc692522c9956f0dae9af45f3f1 100644 (file)
@@ -171,7 +171,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                        if (silent_on_removed)
                                continue;
                        diff_addremove(&revs->diffopt, '-', ce->ce_mode,
-                                      ce->sha1, ce->name, NULL);
+                                      ce->sha1, ce->name);
                        continue;
                }
                changed = ce_match_stat(ce, &st, ce_option);
@@ -184,7 +184,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                newmode = ce_mode_from_stat(ce, st.st_mode);
                diff_change(&revs->diffopt, oldmode, newmode,
                            ce->sha1, (changed ? null_sha1 : ce->sha1),
-                           ce->name, NULL);
+                           ce->name);
 
        }
        diffcore_std(&revs->diffopt);
@@ -208,7 +208,7 @@ static void diff_index_show_file(struct rev_info *revs,
                                 const unsigned char *sha1, unsigned int mode)
 {
        diff_addremove(&revs->diffopt, prefix[0], mode,
-                      sha1, ce->name, NULL);
+                      sha1, ce->name);
 }
 
 static int get_stat_data(struct cache_entry *ce,
@@ -312,7 +312,7 @@ static int show_modified(struct oneway_unpack_data *cbdata,
                return 0;
 
        diff_change(&revs->diffopt, oldmode, mode,
-                   old->sha1, sha1, old->name, NULL);
+                   old->sha1, sha1, old->name);
        return 0;
 }
 
diff --git a/diff.c b/diff.c
index 6a39b393f3e276dd0e0352b5c05eec6989940854..a07812c5c75f1f579295df392efed27f6a5f6fc1 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -3412,9 +3412,8 @@ int diff_result_code(struct diff_options *opt, int status)
 void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
-                   const char *base, const char *path)
+                   const char *concatpath)
 {
-       char concatpath[PATH_MAX];
        struct diff_filespec *one, *two;
 
        if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(mode))
@@ -3436,9 +3435,6 @@ void diff_addremove(struct diff_options *options,
                addremove = (addremove == '+' ? '-' :
                             addremove == '-' ? '+' : addremove);
 
-       if (!path) path = "";
-       sprintf(concatpath, "%s%s", base, path);
-
        if (options->prefix &&
            strncmp(concatpath, options->prefix, options->prefix_length))
                return;
@@ -3459,9 +3455,8 @@ void diff_change(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
-                const char *base, const char *path)
+                const char *concatpath)
 {
-       char concatpath[PATH_MAX];
        struct diff_filespec *one, *two;
 
        if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(old_mode)
@@ -3474,8 +3469,6 @@ void diff_change(struct diff_options *options,
                tmp = old_mode; old_mode = new_mode; new_mode = tmp;
                tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
        }
-       if (!path) path = "";
-       sprintf(concatpath, "%s%s", base, path);
 
        if (options->prefix &&
            strncmp(concatpath, options->prefix, options->prefix_length))
diff --git a/diff.h b/diff.h
index 5dc0cb595b64868ca8b8e26e5311d2df102c89e0..50fb5ddb0bec02b0cd5498d6ecc37d44bf874476 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -14,12 +14,12 @@ typedef void (*change_fn_t)(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
-                const char *base, const char *path);
+                const char *fullpath);
 
 typedef void (*add_remove_fn_t)(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
-                   const char *base, const char *path);
+                   const char *fullpath);
 
 typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
                struct diff_options *options, void *data);
@@ -164,14 +164,13 @@ extern void diff_addremove(struct diff_options *,
                           int addremove,
                           unsigned mode,
                           const unsigned char *sha1,
-                          const char *base,
-                          const char *path);
+                          const char *fullpath);
 
 extern void diff_change(struct diff_options *,
                        unsigned mode1, unsigned mode2,
                        const unsigned char *sha1,
                        const unsigned char *sha2,
-                       const char *base, const char *path);
+                       const char *fullpath);
 
 extern void diff_unmerge(struct diff_options *,
                         const char *path,
index da04efe951e5b8dc4e7b72246212d8ebabc09ec6..8899e31b3b9edb528a339df394f891f76b8de594 100644 (file)
@@ -40,6 +40,16 @@ static const char *builtin_exec_path(void)
 #endif
 }
 
+const char *system_path(const char *path)
+{
+       if (!is_absolute_path(path)) {
+               struct strbuf d = STRBUF_INIT;
+               strbuf_addf(&d, "%s/%s", git_exec_path(), path);
+               path = strbuf_detach(&d, NULL);
+       }
+       return path;
+}
+
 void git_set_argv_exec_path(const char *exec_path)
 {
        argv_exec_path = exec_path;
index a892355c8212298130fb3925c6cba352ed6999b6..7eb94e5e11c07dd269cf59fa5c85845c787d86dd 100644 (file)
@@ -6,6 +6,6 @@ extern const char* git_exec_path(void);
 extern void setup_path(const char *);
 extern int execv_git_cmd(const char **argv); /* NULL terminated */
 extern int execl_git_cmd(const char *cmd, ...);
-
+extern const char *system_path(const char *path);
 
 #endif /* GIT_EXEC_CMD_H */
index 801d7c02513be636ac682b503de7b9b4121acee9..da768ee7acc22e6480f0a067e109239561d43927 100755 (executable)
@@ -54,7 +54,7 @@ sub colored {
 my $patch_mode;
 
 sub run_cmd_pipe {
-       if ($^O eq 'MSWin32') {
+       if ($^O eq 'MSWin32' || $^O eq 'msys') {
                my @invalid = grep {m/[":*]/} @_;
                die "$^O does not support: @invalid\n" if @invalid;
                my @args = map { m/ /o ? "\"$_\"": $_ } @_;
@@ -406,9 +406,9 @@ sub list_and_choose {
                        if ($choice =~ s/^-//) {
                                $choose = 0;
                        }
-                       # A range can be specified like 5-7
-                       if ($choice =~ /^(\d+)-(\d+)$/) {
-                               ($bottom, $top) = ($1, $2);
+                       # A range can be specified like 5-7 or 5-.
+                       if ($choice =~ /^(\d+)-(\d*)$/) {
+                               ($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff);
                        }
                        elsif ($choice =~ /^\d+$/) {
                                $bottom = $top = $choice;
index 4e73f93a1f1906db8212a1aa9236bdfa03ae1a00..cc8787b460e601889fd90c5dba2055e9eb85bc22 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -119,7 +119,7 @@ It does not apply to blobs recorded in its index."
 }
 
 prec=4
-dotest=".dotest"
+dotest="$GIT_DIR/rebase"
 sign= utf8=t keep= skip= interactive= resolved= binary= rebasing=
 resolvemsg= resume=
 git_apply_opt=
@@ -195,7 +195,7 @@ then
                false
                ;;
        esac ||
-       die "previous dotest directory $dotest still exists but mbox given."
+       die "previous rebase directory $dotest still exists but mbox given."
        resume=yes
 else
        # Make sure we are not given --skip nor --resolved
@@ -242,6 +242,7 @@ else
                : >"$dotest/rebasing"
        else
                : >"$dotest/applying"
+               git update-ref ORIG_HEAD HEAD
        fi
 fi
 
@@ -324,7 +325,7 @@ do
                        <"$dotest"/info >/dev/null &&
                        go_next && continue
 
-               test -s $dotest/patch || {
+               test -s "$dotest/patch" || {
                        echo "Patch is empty.  Was it split wrong?"
                        stop_here $this
                }
index 8c7e11473390c14956e43843c17a2d85a336a087..cf89cdf4598b3796724a85aa707f740245155cdc 100644 (file)
@@ -157,6 +157,12 @@ extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
 extern int prefixcmp(const char *str, const char *prefix);
 extern time_t tm_to_time_t(const struct tm *tm);
 
+static inline const char *skip_prefix(const char *str, const char *prefix)
+{
+       size_t len = strlen(prefix);
+       return strncmp(str, prefix, len) ? NULL : str + len;
+}
+
 #ifdef NO_MMAP
 
 #ifndef PROT_READ
index 920bbe15a3bbc7768d47a2adbc84aa81264e2226..b00d1c29cd3a7d2cd8bbcd7ea4f531e050fcfcb9 100755 (executable)
@@ -1884,7 +1884,7 @@ sub req_annotate
     }
 
     # done; get out of the tempdir
-    cleanupWorkDir();
+    cleanupWorkTree();
 
     print "ok\n";
 
index d89f156fd54fe5e51e00793b908ce220e6aa91c8..940677cbd8558574fd9f9fb5812a0bbf46198588 100755 (executable)
@@ -1995,9 +1995,13 @@ if {[is_enabled multicommit]} {
        }
 }
 
-.mbar.repository add command -label [mc Quit] \
-       -command do_quit \
-       -accelerator $M1T-Q
+if {[is_MacOSX]} {
+       proc ::tk::mac::Quit {args} { do_quit }
+} else {
+       .mbar.repository add command -label [mc Quit] \
+               -command do_quit \
+               -accelerator $M1T-Q
+}
 
 # -- Edit Menu
 #
index cc26b0780882b6e8bde122c2a724d110f439d84f..5c01875b051305b5b40a17ac7a2245f69081f41b 100644 (file)
@@ -257,6 +257,7 @@ proc _reset_wait {fd} {
 
                catch {file delete [gitdir MERGE_HEAD]}
                catch {file delete [gitdir rr-cache MERGE_RR]}
+               catch {file delete [gitdir MERGE_RR]}
                catch {file delete [gitdir SQUASH_MSG]}
                catch {file delete [gitdir MERGE_MSG]}
                catch {file delete [gitdir GITGUI_MSG]}
diff --git a/git-merge.sh b/git-merge.sh
deleted file mode 100755 (executable)
index 8026ccf..0000000
+++ /dev/null
@@ -1,554 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-
-OPTIONS_KEEPDASHDASH=
-OPTIONS_SPEC="\
-git-merge [options] <remote>...
-git-merge [options] <msg> HEAD <remote>
---
-stat                 show a diffstat at the end of the merge
-n                    don't show a diffstat at the end of the merge
-summary              (synonym to --stat)
-log                  add list of one-line log to merge commit message
-squash               create a single commit instead of doing a merge
-commit               perform a commit if the merge succeeds (default)
-ff                   allow fast forward (default)
-s,strategy=          merge strategy to use
-m,message=           message to be used for the merge commit (if any)
-"
-
-SUBDIRECTORY_OK=Yes
-. git-sh-setup
-require_work_tree
-cd_to_toplevel
-
-test -z "$(git ls-files -u)" ||
-       die "You are in the middle of a conflicted merge."
-
-LF='
-'
-
-all_strategies='recur recursive octopus resolve stupid ours subtree'
-default_twohead_strategies='recursive'
-default_octopus_strategies='octopus'
-no_fast_forward_strategies='subtree ours'
-no_trivial_strategies='recursive recur subtree ours'
-use_strategies=
-
-allow_fast_forward=t
-allow_trivial_merge=t
-squash= no_commit= log_arg=
-
-dropsave() {
-       rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
-                "$GIT_DIR/MERGE_STASH" || exit 1
-}
-
-savestate() {
-       # Stash away any local modifications.
-       git stash create >"$GIT_DIR/MERGE_STASH"
-}
-
-restorestate() {
-        if test -f "$GIT_DIR/MERGE_STASH"
-       then
-               git reset --hard $head >/dev/null
-               git stash apply $(cat "$GIT_DIR/MERGE_STASH")
-               git update-index --refresh >/dev/null
-       fi
-}
-
-finish_up_to_date () {
-       case "$squash" in
-       t)
-               echo "$1 (nothing to squash)" ;;
-       '')
-               echo "$1" ;;
-       esac
-       dropsave
-}
-
-squash_message () {
-       echo Squashed commit of the following:
-       echo
-       git log --no-merges --pretty=medium ^"$head" $remoteheads
-}
-
-finish () {
-       if test '' = "$2"
-       then
-               rlogm="$GIT_REFLOG_ACTION"
-       else
-               echo "$2"
-               rlogm="$GIT_REFLOG_ACTION: $2"
-       fi
-       case "$squash" in
-       t)
-               echo "Squash commit -- not updating HEAD"
-               squash_message >"$GIT_DIR/SQUASH_MSG"
-               ;;
-       '')
-               case "$merge_msg" in
-               '')
-                       echo "No merge message -- not updating HEAD"
-                       ;;
-               *)
-                       git update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1
-                       git gc --auto
-                       ;;
-               esac
-               ;;
-       esac
-       case "$1" in
-       '')
-               ;;
-       ?*)
-               if test "$show_diffstat" = t
-               then
-                       # We want color (if set), but no pager
-                       GIT_PAGER='' git diff --stat --summary -M "$head" "$1"
-               fi
-               ;;
-       esac
-
-       # Run a post-merge hook
-        if test -x "$GIT_DIR"/hooks/post-merge
-        then
-           case "$squash" in
-           t)
-                "$GIT_DIR"/hooks/post-merge 1
-               ;;
-           '')
-                "$GIT_DIR"/hooks/post-merge 0
-               ;;
-           esac
-        fi
-}
-
-merge_name () {
-       remote="$1"
-       rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return
-       bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
-       if test "$rh" = "$bh"
-       then
-               echo "$rh               branch '$remote' of ."
-       elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') &&
-               git show-ref -q --verify "refs/heads/$truname" 2>/dev/null
-       then
-               echo "$rh               branch '$truname' (early part) of ."
-       elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
-       then
-               sed -e 's/      not-for-merge   /               /' -e 1q \
-                       "$GIT_DIR/FETCH_HEAD"
-       else
-               echo "$rh               commit '$remote'"
-       fi
-}
-
-parse_config () {
-       while test $# != 0; do
-               case "$1" in
-               -n|--no-stat|--no-summary)
-                       show_diffstat=false ;;
-               --stat|--summary)
-                       show_diffstat=t ;;
-               --log|--no-log)
-                       log_arg=$1 ;;
-               --squash)
-                       test "$allow_fast_forward" = t ||
-                               die "You cannot combine --squash with --no-ff."
-                       squash=t no_commit=t ;;
-               --no-squash)
-                       squash= no_commit= ;;
-               --commit)
-                       no_commit= ;;
-               --no-commit)
-                       no_commit=t ;;
-               --ff)
-                       allow_fast_forward=t ;;
-               --no-ff)
-                       test "$squash" != t ||
-                               die "You cannot combine --squash with --no-ff."
-                       allow_fast_forward=f ;;
-               -s|--strategy)
-                       shift
-                       case " $all_strategies " in
-                       *" $1 "*)
-                               use_strategies="$use_strategies$1 " ;;
-                       *)
-                               die "available strategies are: $all_strategies" ;;
-                       esac
-                       ;;
-               -m|--message)
-                       shift
-                       merge_msg="$1"
-                       have_message=t
-                       ;;
-               --)
-                       shift
-                       break ;;
-               *)      usage ;;
-               esac
-               shift
-       done
-       args_left=$#
-}
-
-test $# != 0 || usage
-
-have_message=
-
-if branch=$(git-symbolic-ref -q HEAD)
-then
-       mergeopts=$(git config "branch.${branch#refs/heads/}.mergeoptions")
-       if test -n "$mergeopts"
-       then
-               parse_config $mergeopts --
-       fi
-fi
-
-parse_config "$@"
-while test $args_left -lt $#; do shift; done
-
-if test -z "$show_diffstat"; then
-    test "$(git config --bool merge.diffstat)" = false && show_diffstat=false
-    test "$(git config --bool merge.stat)" = false && show_diffstat=false
-    test -z "$show_diffstat" && show_diffstat=t
-fi
-
-# This could be traditional "merge <msg> HEAD <commit>..."  and the
-# way we can tell it is to see if the second token is HEAD, but some
-# people might have misused the interface and used a committish that
-# is the same as HEAD there instead.  Traditional format never would
-# have "-m" so it is an additional safety measure to check for it.
-
-if test -z "$have_message" &&
-       second_token=$(git rev-parse --verify "$2^0" 2>/dev/null) &&
-       head_commit=$(git rev-parse --verify "HEAD" 2>/dev/null) &&
-       test "$second_token" = "$head_commit"
-then
-       merge_msg="$1"
-       shift
-       head_arg="$1"
-       shift
-elif ! git rev-parse --verify HEAD >/dev/null 2>&1
-then
-       # If the merged head is a valid one there is no reason to
-       # forbid "git merge" into a branch yet to be born.  We do
-       # the same for "git pull".
-       if test 1 -ne $#
-       then
-               echo >&2 "Can merge only exactly one commit into empty head"
-               exit 1
-       fi
-
-       rh=$(git rev-parse --verify "$1^0") ||
-               die "$1 - not something we can merge"
-
-       git update-ref -m "initial pull" HEAD "$rh" "" &&
-       git read-tree --reset -u HEAD
-       exit
-
-else
-       # We are invoked directly as the first-class UI.
-       head_arg=HEAD
-
-       # All the rest are the commits being merged; prepare
-       # the standard merge summary message to be appended to
-       # the given message.  If remote is invalid we will die
-       # later in the common codepath so we discard the error
-       # in this loop.
-       merge_name=$(for remote
-               do
-                       merge_name "$remote"
-               done | git fmt-merge-msg $log_arg
-       )
-       merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
-fi
-head=$(git rev-parse --verify "$head_arg"^0) || usage
-
-# All the rest are remote heads
-test "$#" = 0 && usage ;# we need at least one remote head.
-set_reflog_action "merge $*"
-
-remoteheads=
-for remote
-do
-       remotehead=$(git rev-parse --verify "$remote"^0 2>/dev/null) ||
-           die "$remote - not something we can merge"
-       remoteheads="${remoteheads}$remotehead "
-       eval GITHEAD_$remotehead='"$remote"'
-       export GITHEAD_$remotehead
-done
-set x $remoteheads ; shift
-
-case "$use_strategies" in
-'')
-       case "$#" in
-       1)
-               var="`git config --get pull.twohead`"
-               if test -n "$var"
-               then
-                       use_strategies="$var"
-               else
-                       use_strategies="$default_twohead_strategies"
-               fi ;;
-       *)
-               var="`git config --get pull.octopus`"
-               if test -n "$var"
-               then
-                       use_strategies="$var"
-               else
-                       use_strategies="$default_octopus_strategies"
-               fi ;;
-       esac
-       ;;
-esac
-
-for s in $use_strategies
-do
-       for ss in $no_fast_forward_strategies
-       do
-               case " $s " in
-               *" $ss "*)
-                       allow_fast_forward=f
-                       break
-                       ;;
-               esac
-       done
-       for ss in $no_trivial_strategies
-       do
-               case " $s " in
-               *" $ss "*)
-                       allow_trivial_merge=f
-                       break
-                       ;;
-               esac
-       done
-done
-
-case "$#" in
-1)
-       common=$(git merge-base --all $head "$@")
-       ;;
-*)
-       common=$(git show-branch --merge-base $head "$@")
-       ;;
-esac
-echo "$head" >"$GIT_DIR/ORIG_HEAD"
-
-case "$allow_fast_forward,$#,$common,$no_commit" in
-?,*,'',*)
-       # No common ancestors found. We need a real merge.
-       ;;
-?,1,"$1",*)
-       # If head can reach all the merge then we are up to date.
-       # but first the most common case of merging one remote.
-       finish_up_to_date "Already up-to-date."
-       exit 0
-       ;;
-t,1,"$head",*)
-       # Again the most common case of merging one remote.
-       echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)"
-       git update-index --refresh 2>/dev/null
-       msg="Fast forward"
-       if test -n "$have_message"
-       then
-               msg="$msg (no commit created; -m option ignored)"
-       fi
-       new_head=$(git rev-parse --verify "$1^0") &&
-       git read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
-       finish "$new_head" "$msg" || exit
-       dropsave
-       exit 0
-       ;;
-?,1,?*"$LF"?*,*)
-       # We are not doing octopus and not fast forward.  Need a
-       # real merge.
-       ;;
-?,1,*,)
-       # We are not doing octopus, not fast forward, and have only
-       # one common.
-       git update-index --refresh 2>/dev/null
-       case "$allow_trivial_merge" in
-       t)
-               # See if it is really trivial.
-               git var GIT_COMMITTER_IDENT >/dev/null || exit
-               echo "Trying really trivial in-index merge..."
-               if git read-tree --trivial -m -u -v $common $head "$1" &&
-                  result_tree=$(git write-tree)
-               then
-                       echo "Wonderful."
-                       result_commit=$(
-                               printf '%s\n' "$merge_msg" |
-                               git commit-tree $result_tree -p HEAD -p "$1"
-                       ) || exit
-                       finish "$result_commit" "In-index merge"
-                       dropsave
-                       exit 0
-               fi
-               echo "Nope."
-       esac
-       ;;
-*)
-       # An octopus.  If we can reach all the remote we are up to date.
-       up_to_date=t
-       for remote
-       do
-               common_one=$(git merge-base --all $head $remote)
-               if test "$common_one" != "$remote"
-               then
-                       up_to_date=f
-                       break
-               fi
-       done
-       if test "$up_to_date" = t
-       then
-               finish_up_to_date "Already up-to-date. Yeeah!"
-               exit 0
-       fi
-       ;;
-esac
-
-# We are going to make a new commit.
-git var GIT_COMMITTER_IDENT >/dev/null || exit
-
-# At this point, we need a real merge.  No matter what strategy
-# we use, it would operate on the index, possibly affecting the
-# working tree, and when resolved cleanly, have the desired tree
-# in the index -- this means that the index must be in sync with
-# the $head commit.  The strategies are responsible to ensure this.
-
-case "$use_strategies" in
-?*' '?*)
-    # Stash away the local changes so that we can try more than one.
-    savestate
-    single_strategy=no
-    ;;
-*)
-    rm -f "$GIT_DIR/MERGE_STASH"
-    single_strategy=yes
-    ;;
-esac
-
-result_tree= best_cnt=-1 best_strategy= wt_strategy=
-merge_was_ok=
-for strategy in $use_strategies
-do
-    test "$wt_strategy" = '' || {
-       echo "Rewinding the tree to pristine..."
-       restorestate
-    }
-    case "$single_strategy" in
-    no)
-       echo "Trying merge strategy $strategy..."
-       ;;
-    esac
-
-    # Remember which strategy left the state in the working tree
-    wt_strategy=$strategy
-
-    git-merge-$strategy $common -- "$head_arg" "$@"
-    exit=$?
-    if test "$no_commit" = t && test "$exit" = 0
-    then
-        merge_was_ok=t
-       exit=1 ;# pretend it left conflicts.
-    fi
-
-    test "$exit" = 0 || {
-
-       # The backend exits with 1 when conflicts are left to be resolved,
-       # with 2 when it does not handle the given merge at all.
-
-       if test "$exit" -eq 1
-       then
-           cnt=`{
-               git diff-files --name-only
-               git ls-files --unmerged
-           } | wc -l`
-           if test $best_cnt -le 0 -o $cnt -le $best_cnt
-           then
-               best_strategy=$strategy
-               best_cnt=$cnt
-           fi
-       fi
-       continue
-    }
-
-    # Automerge succeeded.
-    result_tree=$(git write-tree) && break
-done
-
-# If we have a resulting tree, that means the strategy module
-# auto resolved the merge cleanly.
-if test '' != "$result_tree"
-then
-    if test "$allow_fast_forward" = "t"
-    then
-        parents=$(git show-branch --independent "$head" "$@")
-    else
-        parents=$(git rev-parse "$head" "$@")
-    fi
-    parents=$(echo "$parents" | sed -e 's/^/-p /')
-    result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit
-    finish "$result_commit" "Merge made by $wt_strategy."
-    dropsave
-    exit 0
-fi
-
-# Pick the result from the best strategy and have the user fix it up.
-case "$best_strategy" in
-'')
-       restorestate
-       case "$use_strategies" in
-       ?*' '?*)
-               echo >&2 "No merge strategy handled the merge."
-               ;;
-       *)
-               echo >&2 "Merge with strategy $use_strategies failed."
-               ;;
-       esac
-       exit 2
-       ;;
-"$wt_strategy")
-       # We already have its result in the working tree.
-       ;;
-*)
-       echo "Rewinding the tree to pristine..."
-       restorestate
-       echo "Using the $best_strategy to prepare resolving by hand."
-       git-merge-$best_strategy $common -- "$head_arg" "$@"
-       ;;
-esac
-
-if test "$squash" = t
-then
-       finish
-else
-       for remote
-       do
-               echo $remote
-       done >"$GIT_DIR/MERGE_HEAD"
-       printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG"
-fi
-
-if test "$merge_was_ok" = t
-then
-       echo >&2 \
-       "Automatic merge went well; stopped before committing as requested"
-       exit 0
-else
-       {
-           echo '
-Conflicts:
-'
-               git ls-files --unmerged |
-               sed -e 's/^[^   ]*      /       /' |
-               uniq
-       } >>"$GIT_DIR/MERGE_MSG"
-       git rerere
-       die "Automatic merge failed; fix conflicts and then commit the result."
-fi
index 7cd8f7134e696312d243d73acebb6ecfe07d1e13..d1efa1d7419c23b65af1dccf4b8a00ed0e94c79c 100755 (executable)
@@ -53,7 +53,7 @@ if ! [ -d "$QUILT_PATCHES" ] ; then
 fi
 
 # Temporary directories
-tmp_dir=.dotest
+tmp_dir="$GIT_DIR"/rebase
 tmp_msg="$tmp_dir/msg"
 tmp_patch="$tmp_dir/patch"
 tmp_info="$tmp_dir/info"
index 3d85486c88069b98cff72f84001bc1897ef85941..e63a864c7b1b5057737fda1e52c88d1d5e40322f 100755 (executable)
@@ -31,7 +31,7 @@ skip               skip current patch and continue rebasing process
 . git-sh-setup
 require_work_tree
 
-DOTEST="$GIT_DIR/.dotest-merge"
+DOTEST="$GIT_DIR/rebase-merge"
 TODO="$DOTEST"/git-rebase-todo
 DONE="$DOTEST"/done
 MSG="$DOTEST"/message
@@ -174,6 +174,8 @@ pick_one_preserving_merges () {
                                new_parents="$new_parents $new_p"
                                ;;
                        esac
+               else
+                       new_parents="$new_parents $p"
                fi
        done
        case $fast_forward in
@@ -574,6 +576,7 @@ EOF
                has_action "$TODO" ||
                        die_abort "Nothing to do"
 
+               git update-ref ORIG_HEAD $HEAD
                output git checkout $ONTO && do_rest
                ;;
        esac
index e2d85eeeab79e60ef46a65cfee1fb4682dd4ba68..6ef5754814babdb908d794ea030aceca6597b190 100755 (executable)
@@ -14,7 +14,7 @@ It is possible that a merge failure will prevent this process from being
 completely automatic.  You will have to resolve any such merge failure
 and run git rebase --continue.  Another option is to bypass the commit
 that caused the merge failure with git rebase --skip.  To restore the
-original <branch> and remove the .dotest working files, use the command
+original <branch> and remove the .git/rebase working files, use the command
 git rebase --abort instead.
 
 Note that if <branch> is not specified on the command line, the
@@ -42,7 +42,7 @@ To restore the original branch and stop rebasing run \"git rebase --abort\".
 unset newbase
 strategy=recursive
 do_merge=
-dotest=$GIT_DIR/.dotest-merge
+dotest="$GIT_DIR"/rebase-merge
 prec=4
 verbose=
 git_am_opt=
@@ -150,7 +150,7 @@ while test $# != 0
 do
        case "$1" in
        --continue)
-               test -d "$dotest" -o -d .dotest ||
+               test -d "$dotest" -o -d "$GIT_DIR"/rebase ||
                        die "No rebase in progress?"
 
                git diff-files --quiet --ignore-submodules || {
@@ -173,15 +173,15 @@ do
                        finish_rb_merge
                        exit
                fi
-               head_name=$(cat .dotest/head-name) &&
-               onto=$(cat .dotest/onto) &&
-               orig_head=$(cat .dotest/orig-head) &&
+               head_name=$(cat "$GIT_DIR"/rebase/head-name) &&
+               onto=$(cat "$GIT_DIR"/rebase/onto) &&
+               orig_head=$(cat "$GIT_DIR"/rebase/orig-head) &&
                git am --resolved --3way --resolvemsg="$RESOLVEMSG" &&
                move_to_original_branch
                exit
                ;;
        --skip)
-               test -d "$dotest" -o -d .dotest ||
+               test -d "$dotest" -o -d "$GIT_DIR"/rebase ||
                        die "No rebase in progress?"
 
                git reset --hard HEAD || exit $?
@@ -201,15 +201,15 @@ do
                        finish_rb_merge
                        exit
                fi
-               head_name=$(cat .dotest/head-name) &&
-               onto=$(cat .dotest/onto) &&
-               orig_head=$(cat .dotest/orig-head) &&
+               head_name=$(cat "$GIT_DIR"/rebase/head-name) &&
+               onto=$(cat "$GIT_DIR"/rebase/onto) &&
+               orig_head=$(cat "$GIT_DIR"/rebase/orig-head) &&
                git am -3 --skip --resolvemsg="$RESOLVEMSG" &&
                move_to_original_branch
                exit
                ;;
        --abort)
-               test -d "$dotest" -o -d .dotest ||
+               test -d "$dotest" -o -d "$GIT_DIR"/rebase ||
                        die "No rebase in progress?"
 
                git rerere clear
@@ -217,7 +217,7 @@ do
                then
                        move_to_original_branch
                else
-                       dotest=.dotest
+                       dotest="$GIT_DIR"/rebase
                        move_to_original_branch
                fi
                git reset --hard $(cat "$dotest/orig-head")
@@ -265,24 +265,24 @@ do
        shift
 done
 
-# Make sure we do not have .dotest
+# Make sure we do not have $GIT_DIR/rebase
 if test -z "$do_merge"
 then
-       if mkdir .dotest
+       if mkdir "$GIT_DIR"/rebase
        then
-               rmdir .dotest
+               rmdir "$GIT_DIR"/rebase
        else
                echo >&2 '
-It seems that I cannot create a .dotest directory, and I wonder if you
+It seems that I cannot create a '"$GIT_DIR"'/rebase directory, and I wonder if you
 are in the middle of patch application or another rebase.  If that is not
-the case, please rm -fr .dotest and run me again.  I am stopping in case
+the case, please rm -fr '"$GIT_DIR"'/rebase and run me again.  I am stopping in case
 you still have something valuable there.'
                exit 1
        fi
 else
        if test -d "$dotest"
        then
-               die "previous dotest directory $dotest still exists." \
+               die "previous rebase directory $dotest still exists." \
                        'try git-rebase < --continue | --abort >'
        fi
 fi
@@ -376,9 +376,8 @@ fi
 
 # Detach HEAD and reset the tree
 echo "First, rewinding head to replay your work on top of it..."
-git checkout "$onto^0" >/dev/null 2>&1 ||
-       die "could not detach HEAD"
-# git reset --hard "$onto^0"
+git checkout -q "$onto^0" || die "could not detach HEAD"
+git update-ref ORIG_HEAD $branch
 
 # If the $onto is a proper descendant of the tip of the branch, then
 # we just fast forwarded.
@@ -396,10 +395,10 @@ then
        git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
        move_to_original_branch
        ret=$?
-       test 0 != $ret -a -d .dotest &&
-               echo $head_name > .dotest/head-name &&
-               echo $onto > .dotest/onto &&
-               echo $orig_head > .dotest/orig-head
+       test 0 != $ret -a -d "$GIT_DIR"/rebase &&
+               echo $head_name > "$GIT_DIR"/rebase/head-name &&
+               echo $onto > "$GIT_DIR"/rebase/onto &&
+               echo $orig_head > "$GIT_DIR"/rebase/orig-head
        exit $ret
 fi
 
index 099a7d7560879c284c9fb2fff68f4588baaaf49b..9228f56bee7fd82d530a6865f1f8669fd9b99d8f 100755 (executable)
@@ -5,7 +5,7 @@
 # Copyright (c) 2007 Lars Hjemli
 
 USAGE="[--quiet] [--cached] \
-[add <repo> [-b branch]|status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
+[add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
 [--] [<path>...]"
 OPTIONS_SPEC=
 . git-sh-setup
@@ -27,18 +27,6 @@ say()
        fi
 }
 
-# NEEDSWORK: identical function exists in get_repo_base in clone.sh
-get_repo_base() {
-       (
-               cd "`/bin/pwd`" &&
-               cd "$1" || cd "$1.git" &&
-               {
-                       cd .git
-                       pwd
-               }
-       ) 2>/dev/null
-}
-
 # Resolve relative url by appending to parent's url
 resolve_relative_url ()
 {
@@ -115,7 +103,7 @@ module_clone()
 #
 # Add a new submodule to the working tree, .gitmodules and the index
 #
-# $@ = repo [path]
+# $@ = repo path
 #
 # optional branch is stored in global branch variable
 #
@@ -150,16 +138,27 @@ cmd_add()
        repo=$1
        path=$2
 
-       if test -z "$repo"; then
+       if test -z "$repo" -o -z "$path"; then
                usage
        fi
 
-       # Guess path from repo if not specified or strip trailing slashes
-       if test -z "$path"; then
-               path=$(echo "$repo" | sed -e 's|/*$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
-       else
-               path=$(echo "$path" | sed -e 's|/*$||')
-       fi
+       # assure repo is absolute or relative to parent
+       case "$repo" in
+       ./*|../*)
+               # dereference source url relative to parent's url
+               realrepo=$(resolve_relative_url "$repo") || exit
+               ;;
+       *:*|/*)
+               # absolute url
+               realrepo=$repo
+               ;;
+       *)
+               die "repo URL: '$repo' must be absolute or begin with ./|../"
+       ;;
+       esac
+
+       # strip trailing slashes from path
+       path=$(echo "$path" | sed -e 's|/*$||')
 
        git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
        die "'$path' already exists in the index"
@@ -173,21 +172,17 @@ cmd_add()
                else
                        die "'$path' already exists and is not a valid git repo"
                fi
-       else
+
                case "$repo" in
                ./*|../*)
-                       # dereference source url relative to parent's url
-                       realrepo=$(resolve_relative_url "$repo") || exit
-                       ;;
+                       url=$(resolve_relative_url "$repo") || exit
+                   ;;
                *)
-                       # Turn the source into an absolute path if
-                       # it is local
-                       if base=$(get_repo_base "$repo"); then
-                               repo="$base"
-                       fi
-                       realrepo=$repo
+                       url="$repo"
                        ;;
                esac
+               git config submodule."$path".url "$url"
+       else
 
                module_clone "$path" "$realrepo" || exit
                (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||
index a366c891dc9892550093f0bb760d5d2580eb11cd..3750e47c202ebdcb25e657eecd69ef5252d45221 100755 (executable)
@@ -537,13 +537,13 @@ sub cmd_find_rev {
                my $head = shift;
                $head ||= 'HEAD';
                my @refs;
-               my (undef, undef, undef, $gs) = working_head_info($head, \@refs);
+               my (undef, undef, $uuid, $gs) = working_head_info($head, \@refs);
                unless ($gs) {
                        die "Unable to determine upstream SVN information from ",
                            "$head history\n";
                }
                my $desired_revision = substr($revision_or_hash, 1);
-               $result = $gs->rev_map_get($desired_revision);
+               $result = $gs->rev_map_get($desired_revision, $uuid);
        } else {
                my (undef, $rev, undef) = cmt_metadata($revision_or_hash);
                $result = $rev;
@@ -1162,7 +1162,7 @@ sub working_head_info {
                if (defined $url && defined $rev) {
                        next if $max{$url} and $max{$url} < $rev;
                        if (my $gs = Git::SVN->find_by_url($url)) {
-                               my $c = $gs->rev_map_get($rev);
+                               my $c = $gs->rev_map_get($rev, $uuid);
                                if ($c && $c eq $hash) {
                                        close $fh; # break the pipe
                                        return ($url, $rev, $uuid, $gs);
@@ -1416,11 +1416,17 @@ sub fetch_all {
 
 sub read_all_remotes {
        my $r = {};
+       my $use_svm_props = eval { command_oneline(qw/config --bool
+           svn.useSvmProps/) };
+       $use_svm_props = $use_svm_props eq 'true' if $use_svm_props;
        foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) {
                if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) {
                        my ($remote, $local_ref, $remote_ref) = ($1, $2, $3);
                        $local_ref =~ s{^/}{};
                        $r->{$remote}->{fetch}->{$local_ref} = $remote_ref;
+                       $r->{$remote}->{svm} = {} if $use_svm_props;
+               } elsif (m!^(.+)\.usesvmprops=\s*(.*)\s*$!) {
+                       $r->{$1}->{svm} = {};
                } elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
                        $r->{$1}->{url} = $2;
                } elsif (m!^(.+)\.(branches|tags)=
@@ -1437,6 +1443,23 @@ sub read_all_remotes {
                        }
                }
        }
+
+       map {
+               if (defined $r->{$_}->{svm}) {
+                       my $svm;
+                       eval {
+                               my $section = "svn-remote.$_";
+                               $svm = {
+                                       source => tmp_config('--get',
+                                           "$section.svm-source"),
+                                       replace => tmp_config('--get',
+                                           "$section.svm-replace"),
+                               }
+                       };
+                       $r->{$_}->{svm} = $svm;
+               }
+       } keys %$r;
+
        $r;
 }
 
@@ -1563,13 +1586,21 @@ sub find_by_url { # repos_root and, path are optional
                }
                my $p = $path;
                my $rwr = rewrite_root({repo_id => $repo_id});
+               my $svm = $remotes->{$repo_id}->{svm}
+                       if defined $remotes->{$repo_id}->{svm};
                unless (defined $p) {
                        $p = $full_url;
                        my $z = $u;
+                       my $prefix = '';
                        if ($rwr) {
                                $z = $rwr;
+                       } elsif (defined $svm) {
+                               $z = $svm->{source};
+                               $prefix = $svm->{replace};
+                               $prefix =~ s#^\Q$u\E(?:/|$)##;
+                               $prefix =~ s#/$##;
                        }
-                       $p =~ s#^\Q$z\E(?:/|$)## or next;
+                       $p =~ s#^\Q$z\E(?:/|$)#$prefix# or next;
                }
                foreach my $f (keys %$fetch) {
                        next if $f ne $p;
@@ -4619,7 +4650,7 @@ sub migrate_from_v1 {
        mkpath([$svn_dir]);
        print STDERR "Data from a previous version of git-svn exists, but\n\t",
                     "$svn_dir\n\t(required for this version ",
-                    "($::VERSION) of git-svn) does not. exist\n";
+                    "($::VERSION) of git-svn) does not exist.\n";
        my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
        while (<$fh>) {
                my $x = $_;
diff --git a/git.c b/git.c
index 7075533aa7243abacf100cb8a8987284d5d144de..74ea0e6cc52f627df1bf018770aec67584325ab5 100644 (file)
--- a/git.c
+++ b/git.c
@@ -127,59 +127,6 @@ static int handle_options(const char*** argv, int* argc, int* envchanged)
        return handled;
 }
 
-static int split_cmdline(char *cmdline, const char ***argv)
-{
-       int src, dst, count = 0, size = 16;
-       char quoted = 0;
-
-       *argv = xmalloc(sizeof(char*) * size);
-
-       /* split alias_string */
-       (*argv)[count++] = cmdline;
-       for (src = dst = 0; cmdline[src];) {
-               char c = cmdline[src];
-               if (!quoted && isspace(c)) {
-                       cmdline[dst++] = 0;
-                       while (cmdline[++src]
-                                       && isspace(cmdline[src]))
-                               ; /* skip */
-                       if (count >= size) {
-                               size += 16;
-                               *argv = xrealloc(*argv, sizeof(char*) * size);
-                       }
-                       (*argv)[count++] = cmdline + dst;
-               } else if(!quoted && (c == '\'' || c == '"')) {
-                       quoted = c;
-                       src++;
-               } else if (c == quoted) {
-                       quoted = 0;
-                       src++;
-               } else {
-                       if (c == '\\' && quoted != '\'') {
-                               src++;
-                               c = cmdline[src];
-                               if (!c) {
-                                       free(*argv);
-                                       *argv = NULL;
-                                       return error("cmdline ends with \\");
-                               }
-                       }
-                       cmdline[dst++] = c;
-                       src++;
-               }
-       }
-
-       cmdline[dst] = 0;
-
-       if (quoted) {
-               free(*argv);
-               *argv = NULL;
-               return error("unclosed quote");
-       }
-
-       return count;
-}
-
 static int handle_alias(int *argcp, const char ***argv)
 {
        int envchanged = 0, ret = 0, saved_errno = errno;
@@ -366,6 +313,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "ls-remote", cmd_ls_remote },
                { "mailinfo", cmd_mailinfo },
                { "mailsplit", cmd_mailsplit },
+               { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
                { "merge-base", cmd_merge_base, RUN_SETUP },
                { "merge-file", cmd_merge_file },
                { "merge-ours", cmd_merge_ours, RUN_SETUP },
diff --git a/help.c b/help.c
index ca9632b6c58267776086f48e87e2236ff1f6053f..52d39b88a36e92b00828fdb8c8d056f9b79a37e0 100644 (file)
--- a/help.c
+++ b/help.c
@@ -633,15 +633,29 @@ static void show_info_page(const char *git_cmd)
 static void get_html_page_path(struct strbuf *page_path, const char *page)
 {
        struct stat st;
+       const char *html_path = system_path(GIT_HTML_PATH);
 
        /* Check that we have a git documentation directory. */
-       if (stat(GIT_HTML_PATH "/git.html", &st) || !S_ISREG(st.st_mode))
-               die("'%s': not a documentation directory.", GIT_HTML_PATH);
+       if (stat(mkpath("%s/git.html", html_path), &st)
+           || !S_ISREG(st.st_mode))
+               die("'%s': not a documentation directory.", html_path);
 
        strbuf_init(page_path, 0);
-       strbuf_addf(page_path, GIT_HTML_PATH "/%s.html", page);
+       strbuf_addf(page_path, "%s/%s.html", html_path, page);
 }
 
+/*
+ * If open_html is not defined in a platform-specific way (see for
+ * example compat/mingw.h), we use the script web--browse to display
+ * HTML.
+ */
+#ifndef open_html
+void open_html(const char *path)
+{
+       execl_git_cmd("web--browse", "-c", "help.browser", path, NULL);
+}
+#endif
+
 static void show_html_page(const char *git_cmd)
 {
        const char *page = cmd_to_page(git_cmd);
@@ -649,7 +663,7 @@ static void show_html_page(const char *git_cmd)
 
        get_html_page_path(&page_path, page);
 
-       execl_git_cmd("web--browse", "-c", "help.browser", page_path.buf, NULL);
+       open_html(page_path.buf);
 }
 
 void help_unknown_cmd(const char *cmd)
index 25db5db24b239409657510e8c2eeb3845b374bbf..b4ec7361748640c7ced04c10e9087e12c553d928 100644 (file)
@@ -26,6 +26,14 @@ union delta_base {
        off_t offset;
 };
 
+struct base_data {
+       struct base_data *base;
+       struct base_data *child;
+       struct object_entry *obj;
+       void *data;
+       unsigned long size;
+};
+
 /*
  * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
  * to memcmp() only the first 20 bytes.
@@ -43,6 +51,8 @@ struct delta_entry
 
 static struct object_entry *objects;
 static struct delta_entry *deltas;
+static struct base_data *base_cache;
+static size_t base_cache_used;
 static int nr_objects;
 static int nr_deltas;
 static int nr_resolved_deltas;
@@ -211,6 +221,46 @@ static void bad_object(unsigned long offset, const char *format, ...)
        die("pack has bad object at offset %lu: %s", offset, buf);
 }
 
+static void prune_base_data(struct base_data *retain)
+{
+       struct base_data *b = base_cache;
+       for (b = base_cache;
+            base_cache_used > delta_base_cache_limit && b;
+            b = b->child) {
+               if (b->data && b != retain) {
+                       free(b->data);
+                       b->data = NULL;
+                       base_cache_used -= b->size;
+               }
+       }
+}
+
+static void link_base_data(struct base_data *base, struct base_data *c)
+{
+       if (base)
+               base->child = c;
+       else
+               base_cache = c;
+
+       c->base = base;
+       c->child = NULL;
+       base_cache_used += c->size;
+       prune_base_data(c);
+}
+
+static void unlink_base_data(struct base_data *c)
+{
+       struct base_data *base = c->base;
+       if (base)
+               base->child = NULL;
+       else
+               base_cache = NULL;
+       if (c->data) {
+               free(c->data);
+               base_cache_used -= c->size;
+       }
+}
+
 static void *unpack_entry_data(unsigned long offset, unsigned long size)
 {
        z_stream stream;
@@ -426,33 +476,60 @@ static void sha1_object(const void *data, unsigned long size,
        }
 }
 
-static void resolve_delta(struct object_entry *delta_obj, void *base_data,
-                         unsigned long base_size, enum object_type type)
+static void *get_base_data(struct base_data *c)
+{
+       if (!c->data) {
+               struct object_entry *obj = c->obj;
+
+               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+                       void *base = get_base_data(c->base);
+                       void *raw = get_data_from_pack(obj);
+                       c->data = patch_delta(
+                               base, c->base->size,
+                               raw, obj->size,
+                               &c->size);
+                       free(raw);
+                       if (!c->data)
+                               bad_object(obj->idx.offset, "failed to apply delta");
+               } else
+                       c->data = get_data_from_pack(obj);
+
+               base_cache_used += c->size;
+               prune_base_data(c);
+       }
+       return c->data;
+}
+
+static void resolve_delta(struct object_entry *delta_obj,
+                         struct base_data *base_obj, enum object_type type)
 {
        void *delta_data;
        unsigned long delta_size;
-       void *result;
-       unsigned long result_size;
        union delta_base delta_base;
        int j, first, last;
+       struct base_data result;
 
        delta_obj->real_type = type;
        delta_data = get_data_from_pack(delta_obj);
        delta_size = delta_obj->size;
-       result = patch_delta(base_data, base_size, delta_data, delta_size,
-                            &result_size);
+       result.data = patch_delta(get_base_data(base_obj), base_obj->size,
+                            delta_data, delta_size,
+                            &result.size);
        free(delta_data);
-       if (!result)
+       if (!result.data)
                bad_object(delta_obj->idx.offset, "failed to apply delta");
-       sha1_object(result, result_size, type, delta_obj->idx.sha1);
+       sha1_object(result.data, result.size, type, delta_obj->idx.sha1);
        nr_resolved_deltas++;
 
+       result.obj = delta_obj;
+       link_base_data(base_obj, &result);
+
        hashcpy(delta_base.sha1, delta_obj->idx.sha1);
        if (!find_delta_children(&delta_base, &first, &last)) {
                for (j = first; j <= last; j++) {
                        struct object_entry *child = objects + deltas[j].obj_no;
                        if (child->real_type == OBJ_REF_DELTA)
-                               resolve_delta(child, result, result_size, type);
+                               resolve_delta(child, &result, type);
                }
        }
 
@@ -462,11 +539,11 @@ static void resolve_delta(struct object_entry *delta_obj, void *base_data,
                for (j = first; j <= last; j++) {
                        struct object_entry *child = objects + deltas[j].obj_no;
                        if (child->real_type == OBJ_OFS_DELTA)
-                               resolve_delta(child, result, result_size, type);
+                               resolve_delta(child, &result, type);
                }
        }
 
-       free(result);
+       unlink_base_data(&result);
 }
 
 static int compare_delta_entry(const void *a, const void *b)
@@ -481,7 +558,6 @@ static void parse_pack_objects(unsigned char *sha1)
 {
        int i;
        struct delta_entry *delta = deltas;
-       void *data;
        struct stat st;
 
        /*
@@ -496,7 +572,7 @@ static void parse_pack_objects(unsigned char *sha1)
                                nr_objects);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
-               data = unpack_raw_entry(obj, &delta->base);
+               void *data = unpack_raw_entry(obj, &delta->base);
                obj->real_type = obj->type;
                if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
                        nr_deltas++;
@@ -545,6 +621,7 @@ static void parse_pack_objects(unsigned char *sha1)
                struct object_entry *obj = &objects[i];
                union delta_base base;
                int j, ref, ref_first, ref_last, ofs, ofs_first, ofs_last;
+               struct base_data base_obj;
 
                if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
                        continue;
@@ -555,22 +632,24 @@ static void parse_pack_objects(unsigned char *sha1)
                ofs = !find_delta_children(&base, &ofs_first, &ofs_last);
                if (!ref && !ofs)
                        continue;
-               data = get_data_from_pack(obj);
+               base_obj.data = get_data_from_pack(obj);
+               base_obj.size = obj->size;
+               base_obj.obj = obj;
+               link_base_data(NULL, &base_obj);
+
                if (ref)
                        for (j = ref_first; j <= ref_last; j++) {
                                struct object_entry *child = objects + deltas[j].obj_no;
                                if (child->real_type == OBJ_REF_DELTA)
-                                       resolve_delta(child, data,
-                                                     obj->size, obj->type);
+                                       resolve_delta(child, &base_obj, obj->type);
                        }
                if (ofs)
                        for (j = ofs_first; j <= ofs_last; j++) {
                                struct object_entry *child = objects + deltas[j].obj_no;
                                if (child->real_type == OBJ_OFS_DELTA)
-                                       resolve_delta(child, data,
-                                                     obj->size, obj->type);
+                                       resolve_delta(child, &base_obj, obj->type);
                        }
-               free(data);
+               unlink_base_data(&base_obj);
                display_progress(progress, nr_resolved_deltas);
        }
 }
@@ -601,7 +680,8 @@ static int write_compressed(int fd, void *in, unsigned int size, uint32_t *obj_c
        return size;
 }
 
-static void append_obj_to_pack(const unsigned char *sha1, void *buf,
+static struct object_entry *append_obj_to_pack(
+                              const unsigned char *sha1, void *buf,
                               unsigned long size, enum object_type type)
 {
        struct object_entry *obj = &objects[nr_objects++];
@@ -622,6 +702,7 @@ static void append_obj_to_pack(const unsigned char *sha1, void *buf,
        obj[1].idx.offset = obj[0].idx.offset + n;
        obj[1].idx.offset += write_compressed(output_fd, buf, size, &obj[0].idx.crc32);
        hashcpy(obj->idx.sha1, sha1);
+       return obj;
 }
 
 static int delta_pos_compare(const void *_a, const void *_b)
@@ -656,28 +737,31 @@ static void fix_unresolved_deltas(int nr_unresolved)
 
        for (i = 0; i < n; i++) {
                struct delta_entry *d = sorted_by_pos[i];
-               void *data;
-               unsigned long size;
                enum object_type type;
                int j, first, last;
+               struct base_data base_obj;
 
                if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
                        continue;
-               data = read_sha1_file(d->base.sha1, &type, &size);
-               if (!data)
+               base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
+               if (!base_obj.data)
                        continue;
 
+               if (check_sha1_signature(d->base.sha1, base_obj.data,
+                               base_obj.size, typename(type)))
+                       die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
+               base_obj.obj = append_obj_to_pack(d->base.sha1, base_obj.data,
+                       base_obj.size, type);
+               link_base_data(NULL, &base_obj);
+
                find_delta_children(&d->base, &first, &last);
                for (j = first; j <= last; j++) {
                        struct object_entry *child = objects + deltas[j].obj_no;
                        if (child->real_type == OBJ_REF_DELTA)
-                               resolve_delta(child, data, size, type);
+                               resolve_delta(child, &base_obj, type);
                }
 
-               if (check_sha1_signature(d->base.sha1, data, size, typename(type)))
-                       die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
-               append_obj_to_pack(d->base.sha1, data, size, type);
-               free(data);
+               unlink_base_data(&base_obj);
                display_progress(progress, nr_resolved_deltas);
        }
        free(sorted_by_pos);
index 469831d21b99e2ea6d5d0be51555d853d68a7682..987b0157192b9fe4c7451d94f5948bdbd7f524bb 100644 (file)
@@ -5,23 +5,6 @@
 #define OPT_SHORT 1
 #define OPT_UNSET 2
 
-static inline const char *get_arg(struct parse_opt_ctx_t *p)
-{
-       if (p->opt) {
-               const char *res = p->opt;
-               p->opt = NULL;
-               return res;
-       }
-       p->argc--;
-       return *++p->argv;
-}
-
-static inline const char *skip_prefix(const char *str, const char *prefix)
-{
-       size_t len = strlen(prefix);
-       return strncmp(str, prefix, len) ? NULL : str + len;
-}
-
 static int opterror(const struct option *opt, const char *reason, int flags)
 {
        if (flags & OPT_SHORT)
@@ -31,8 +14,24 @@ static int opterror(const struct option *opt, const char *reason, int flags)
        return error("option `%s' %s", opt->long_name, reason);
 }
 
+static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt,
+                  int flags, const char **arg)
+{
+       if (p->opt) {
+               *arg = p->opt;
+               p->opt = NULL;
+       } else if (p->argc == 1 && (opt->flags & PARSE_OPT_LASTARG_DEFAULT)) {
+               *arg = (const char *)opt->defval;
+       } else if (p->argc) {
+               p->argc--;
+               *arg = *++p->argv;
+       } else
+               return opterror(opt, "requires a value", flags);
+       return 0;
+}
+
 static int get_value(struct parse_opt_ctx_t *p,
-                     const struct option *opt, int flags)
+                    const struct option *opt, int flags)
 {
        const char *s, *arg;
        const int unset = flags & OPT_UNSET;
@@ -58,7 +57,6 @@ static int get_value(struct parse_opt_ctx_t *p,
                }
        }
 
-       arg = p->opt ? p->opt : (p->argc > 1 ? p->argv[1] : NULL);
        switch (opt->type) {
        case OPTION_BIT:
                if (unset)
@@ -80,17 +78,12 @@ static int get_value(struct parse_opt_ctx_t *p,
                return 0;
 
        case OPTION_STRING:
-               if (unset) {
+               if (unset)
                        *(const char **)opt->value = NULL;
-                       return 0;
-               }
-               if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+               else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
                        *(const char **)opt->value = (const char *)opt->defval;
-                       return 0;
-               }
-               if (!arg)
-                       return opterror(opt, "requires a value", flags);
-               *(const char **)opt->value = get_arg(p);
+               else
+                       return get_arg(p, opt, flags, (const char **)opt->value);
                return 0;
 
        case OPTION_CALLBACK:
@@ -100,9 +93,9 @@ static int get_value(struct parse_opt_ctx_t *p,
                        return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
                if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
                        return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
-               if (!arg)
-                       return opterror(opt, "requires a value", flags);
-               return (*opt->callback)(opt, get_arg(p), 0) ? (-1) : 0;
+               if (get_arg(p, opt, flags, &arg))
+                       return -1;
+               return (*opt->callback)(opt, arg, 0) ? (-1) : 0;
 
        case OPTION_INTEGER:
                if (unset) {
@@ -113,9 +106,9 @@ static int get_value(struct parse_opt_ctx_t *p,
                        *(int *)opt->value = opt->defval;
                        return 0;
                }
-               if (!arg)
-                       return opterror(opt, "requires a value", flags);
-               *(int *)opt->value = strtol(get_arg(p), (char **)&s, 10);
+               if (get_arg(p, opt, flags, &arg))
+                       return -1;
+               *(int *)opt->value = strtol(arg, (char **)&s, 10);
                if (*s)
                        return opterror(opt, "expects a numerical value", flags);
                return 0;
@@ -221,7 +214,7 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
        return -2;
 }
 
-void check_typos(const char *arg, const struct option *options)
+static void check_typos(const char *arg, const struct option *options)
 {
        if (strlen(arg) < 3)
                return;
index c5f0b4b4dabb5a40027c0cc588549904e0efcc6f..bc317e7512af7a1cc86641a651ae5415d28e71c4 100644 (file)
@@ -28,6 +28,7 @@ enum parse_opt_option_flags {
        PARSE_OPT_NOARG   = 2,
        PARSE_OPT_NONEG   = 4,
        PARSE_OPT_HIDDEN  = 8,
+       PARSE_OPT_LASTARG_DEFAULT = 16,
 };
 
 struct option;
index 8eb39e915e730ff5039247695adc1e25c14773f4..628a5201c1e8aa3b985bf0b382e5511e17d953d4 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -3,6 +3,8 @@
 #include "utf8.h"
 #include "diff.h"
 #include "revision.h"
+#include "path-list.h"
+#include "mailmap.h"
 
 static char *user_format;
 
@@ -288,6 +290,25 @@ static char *logmsg_reencode(const struct commit *commit,
        return out;
 }
 
+static int mailmap_name(struct strbuf *sb, const char *email)
+{
+       static struct path_list *mail_map;
+       char buffer[1024];
+
+       if (!mail_map) {
+               mail_map = xcalloc(1, sizeof(*mail_map));
+               read_mailmap(mail_map, ".mailmap", NULL);
+       }
+
+       if (!mail_map->nr)
+               return -1;
+
+       if (!map_email(mail_map, email, buffer, sizeof(buffer)))
+               return -1;
+       strbuf_addstr(sb, buffer);
+       return 0;
+}
+
 static size_t format_person_part(struct strbuf *sb, char part,
                                const char *msg, int len)
 {
@@ -309,10 +330,12 @@ static size_t format_person_part(struct strbuf *sb, char part,
        if (end >= len - 2)
                goto skip;
 
-       if (part == 'n') {      /* name */
+       if (part == 'n' || part == 'N') {       /* name */
                while (end > 0 && isspace(msg[end - 1]))
                        end--;
-               strbuf_add(sb, msg, end);
+               if (part != 'N' || !msg[end] || !msg[end + 1] ||
+                   mailmap_name(sb, msg + end + 2) < 0)
+                       strbuf_add(sb, msg, end);
                return placeholder_len;
        }
        start = ++end; /* save email start position */
index f83de8c4158e08bab3e0445c4d15caf2d4d105aa..d801f9d1cf9eca5ddeff2f0af4549bbc1e72423f 100644 (file)
@@ -1410,3 +1410,34 @@ int write_index(const struct index_state *istate, int newfd)
        }
        return ce_flush(&c, newfd);
 }
+
+/*
+ * Read the index file that is potentially unmerged into given
+ * index_state, dropping any unmerged entries.  Returns true is
+ * the index is unmerged.  Callers who want to refuse to work
+ * from an unmerged state can call this and check its return value,
+ * instead of calling read_cache().
+ */
+int read_index_unmerged(struct index_state *istate)
+{
+       int i;
+       struct cache_entry **dst;
+       struct cache_entry *last = NULL;
+
+       read_index(istate);
+       dst = istate->cache;
+       for (i = 0; i < istate->cache_nr; i++) {
+               struct cache_entry *ce = istate->cache[i];
+               if (ce_stage(ce)) {
+                       remove_name_hash(ce);
+                       if (last && !strcmp(ce->name, last->name))
+                               continue;
+                       cache_tree_invalidate_path(istate->cache_tree, ce->name);
+                       last = ce;
+                       continue;
+               }
+               *dst++ = ce;
+       }
+       istate->cache_nr = dst - istate->cache;
+       return !!last;
+}
diff --git a/rerere.c b/rerere.c
new file mode 100644 (file)
index 0000000..1258921
--- /dev/null
+++ b/rerere.c
@@ -0,0 +1,360 @@
+#include "cache.h"
+#include "path-list.h"
+#include "rerere.h"
+#include "xdiff/xdiff.h"
+#include "xdiff-interface.h"
+
+/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
+static int rerere_enabled = -1;
+
+/* automatically update cleanly resolved paths to the index */
+static int rerere_autoupdate;
+
+static char *merge_rr_path;
+
+static const char *rr_path(const char *name, const char *file)
+{
+       return git_path("rr-cache/%s/%s", name, file);
+}
+
+static int has_resolution(const char *name)
+{
+       struct stat st;
+       return !stat(rr_path(name, "postimage"), &st);
+}
+
+static void read_rr(struct path_list *rr)
+{
+       unsigned char sha1[20];
+       char buf[PATH_MAX];
+       FILE *in = fopen(merge_rr_path, "r");
+       if (!in)
+               return;
+       while (fread(buf, 40, 1, in) == 1) {
+               int i;
+               char *name;
+               if (get_sha1_hex(buf, sha1))
+                       die("corrupt MERGE_RR");
+               buf[40] = '\0';
+               name = xstrdup(buf);
+               if (fgetc(in) != '\t')
+                       die("corrupt MERGE_RR");
+               for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++)
+                       ; /* do nothing */
+               if (i == sizeof(buf))
+                       die("filename too long");
+               path_list_insert(buf, rr)->util = name;
+       }
+       fclose(in);
+}
+
+static struct lock_file write_lock;
+
+static int write_rr(struct path_list *rr, int out_fd)
+{
+       int i;
+       for (i = 0; i < rr->nr; i++) {
+               const char *path;
+               int length;
+               if (!rr->items[i].util)
+                       continue;
+               path = rr->items[i].path;
+               length = strlen(path) + 1;
+               if (write_in_full(out_fd, rr->items[i].util, 40) != 40 ||
+                   write_in_full(out_fd, "\t", 1) != 1 ||
+                   write_in_full(out_fd, path, length) != length)
+                       die("unable to write rerere record");
+       }
+       if (commit_lock_file(&write_lock) != 0)
+               die("unable to write rerere record");
+       return 0;
+}
+
+static int handle_file(const char *path,
+        unsigned char *sha1, const char *output)
+{
+       SHA_CTX ctx;
+       char buf[1024];
+       int hunk = 0, hunk_no = 0;
+       struct strbuf one, two;
+       FILE *f = fopen(path, "r");
+       FILE *out = NULL;
+
+       if (!f)
+               return error("Could not open %s", path);
+
+       if (output) {
+               out = fopen(output, "w");
+               if (!out) {
+                       fclose(f);
+                       return error("Could not write %s", output);
+               }
+       }
+
+       if (sha1)
+               SHA1_Init(&ctx);
+
+       strbuf_init(&one, 0);
+       strbuf_init(&two,  0);
+       while (fgets(buf, sizeof(buf), f)) {
+               if (!prefixcmp(buf, "<<<<<<< ")) {
+                       if (hunk)
+                               goto bad;
+                       hunk = 1;
+               } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) {
+                       if (hunk != 1)
+                               goto bad;
+                       hunk = 2;
+               } else if (!prefixcmp(buf, ">>>>>>> ")) {
+                       if (hunk != 2)
+                               goto bad;
+                       if (strbuf_cmp(&one, &two) > 0)
+                               strbuf_swap(&one, &two);
+                       hunk_no++;
+                       hunk = 0;
+                       if (out) {
+                               fputs("<<<<<<<\n", out);
+                               fwrite(one.buf, one.len, 1, out);
+                               fputs("=======\n", out);
+                               fwrite(two.buf, two.len, 1, out);
+                               fputs(">>>>>>>\n", out);
+                       }
+                       if (sha1) {
+                               SHA1_Update(&ctx, one.buf ? one.buf : "",
+                                           one.len + 1);
+                               SHA1_Update(&ctx, two.buf ? two.buf : "",
+                                           two.len + 1);
+                       }
+                       strbuf_reset(&one);
+                       strbuf_reset(&two);
+               } else if (hunk == 1)
+                       strbuf_addstr(&one, buf);
+               else if (hunk == 2)
+                       strbuf_addstr(&two, buf);
+               else if (out)
+                       fputs(buf, out);
+               continue;
+       bad:
+               hunk = 99; /* force error exit */
+               break;
+       }
+       strbuf_release(&one);
+       strbuf_release(&two);
+
+       fclose(f);
+       if (out)
+               fclose(out);
+       if (sha1)
+               SHA1_Final(sha1, &ctx);
+       if (hunk) {
+               if (output)
+                       unlink(output);
+               return error("Could not parse conflict hunks in %s", path);
+       }
+       return hunk_no;
+}
+
+static int find_conflict(struct path_list *conflict)
+{
+       int i;
+       if (read_cache() < 0)
+               return error("Could not read index");
+       for (i = 0; i+1 < active_nr; i++) {
+               struct cache_entry *e2 = active_cache[i];
+               struct cache_entry *e3 = active_cache[i+1];
+               if (ce_stage(e2) == 2 &&
+                   ce_stage(e3) == 3 &&
+                   ce_same_name(e2, e3) &&
+                   S_ISREG(e2->ce_mode) &&
+                   S_ISREG(e3->ce_mode)) {
+                       path_list_insert((const char *)e2->name, conflict);
+                       i++; /* skip over both #2 and #3 */
+               }
+       }
+       return 0;
+}
+
+static int merge(const char *name, const char *path)
+{
+       int ret;
+       mmfile_t cur, base, other;
+       mmbuffer_t result = {NULL, 0};
+       xpparam_t xpp = {XDF_NEED_MINIMAL};
+
+       if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0)
+               return 1;
+
+       if (read_mmfile(&cur, rr_path(name, "thisimage")) ||
+                       read_mmfile(&base, rr_path(name, "preimage")) ||
+                       read_mmfile(&other, rr_path(name, "postimage")))
+               return 1;
+       ret = xdl_merge(&base, &cur, "", &other, "",
+                       &xpp, XDL_MERGE_ZEALOUS, &result);
+       if (!ret) {
+               FILE *f = fopen(path, "w");
+               if (!f)
+                       return error("Could not write to %s", path);
+               fwrite(result.ptr, result.size, 1, f);
+               fclose(f);
+       }
+
+       free(cur.ptr);
+       free(base.ptr);
+       free(other.ptr);
+       free(result.ptr);
+
+       return ret;
+}
+
+static struct lock_file index_lock;
+
+static int update_paths(struct path_list *update)
+{
+       int i;
+       int fd = hold_locked_index(&index_lock, 0);
+       int status = 0;
+
+       if (fd < 0)
+               return -1;
+
+       for (i = 0; i < update->nr; i++) {
+               struct path_list_item *item = &update->items[i];
+               if (add_file_to_cache(item->path, ADD_CACHE_IGNORE_ERRORS))
+                       status = -1;
+       }
+
+       if (!status && active_cache_changed) {
+               if (write_cache(fd, active_cache, active_nr) ||
+                   commit_locked_index(&index_lock))
+                       die("Unable to write new index file");
+       } else if (fd >= 0)
+               rollback_lock_file(&index_lock);
+       return status;
+}
+
+static int do_plain_rerere(struct path_list *rr, int fd)
+{
+       struct path_list conflict = { NULL, 0, 0, 1 };
+       struct path_list update = { NULL, 0, 0, 1 };
+       int i;
+
+       find_conflict(&conflict);
+
+       /*
+        * MERGE_RR records paths with conflicts immediately after merge
+        * failed.  Some of the conflicted paths might have been hand resolved
+        * in the working tree since then, but the initial run would catch all
+        * and register their preimages.
+        */
+
+       for (i = 0; i < conflict.nr; i++) {
+               const char *path = conflict.items[i].path;
+               if (!path_list_has_path(rr, path)) {
+                       unsigned char sha1[20];
+                       char *hex;
+                       int ret;
+                       ret = handle_file(path, sha1, NULL);
+                       if (ret < 1)
+                               continue;
+                       hex = xstrdup(sha1_to_hex(sha1));
+                       path_list_insert(path, rr)->util = hex;
+                       if (mkdir(git_path("rr-cache/%s", hex), 0755))
+                               continue;;
+                       handle_file(path, NULL, rr_path(hex, "preimage"));
+                       fprintf(stderr, "Recorded preimage for '%s'\n", path);
+               }
+       }
+
+       /*
+        * Now some of the paths that had conflicts earlier might have been
+        * hand resolved.  Others may be similar to a conflict already that
+        * was resolved before.
+        */
+
+       for (i = 0; i < rr->nr; i++) {
+               int ret;
+               const char *path = rr->items[i].path;
+               const char *name = (const char *)rr->items[i].util;
+
+               if (has_resolution(name)) {
+                       if (!merge(name, path)) {
+                               fprintf(stderr, "Resolved '%s' using "
+                                               "previous resolution.\n", path);
+                               if (rerere_autoupdate)
+                                       path_list_insert(path, &update);
+                               goto mark_resolved;
+                       }
+               }
+
+               /* Let's see if we have resolved it. */
+               ret = handle_file(path, NULL, NULL);
+               if (ret)
+                       continue;
+
+               fprintf(stderr, "Recorded resolution for '%s'.\n", path);
+               copy_file(rr_path(name, "postimage"), path, 0666);
+       mark_resolved:
+               rr->items[i].util = NULL;
+       }
+
+       if (update.nr)
+               update_paths(&update);
+
+       return write_rr(rr, fd);
+}
+
+static int git_rerere_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "rerere.enabled"))
+               rerere_enabled = git_config_bool(var, value);
+       else if (!strcmp(var, "rerere.autoupdate"))
+               rerere_autoupdate = git_config_bool(var, value);
+       else
+               return git_default_config(var, value, cb);
+       return 0;
+}
+
+static int is_rerere_enabled(void)
+{
+       struct stat st;
+       const char *rr_cache;
+       int rr_cache_exists;
+
+       if (!rerere_enabled)
+               return 0;
+
+       rr_cache = git_path("rr-cache");
+       rr_cache_exists = !stat(rr_cache, &st) && S_ISDIR(st.st_mode);
+       if (rerere_enabled < 0)
+               return rr_cache_exists;
+
+       if (!rr_cache_exists &&
+           (mkdir(rr_cache, 0777) || adjust_shared_perm(rr_cache)))
+               die("Could not create directory %s", rr_cache);
+       return 1;
+}
+
+int setup_rerere(struct path_list *merge_rr)
+{
+       int fd;
+
+       git_config(git_rerere_config, NULL);
+       if (!is_rerere_enabled())
+               return -1;
+
+       merge_rr_path = xstrdup(git_path("MERGE_RR"));
+       fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
+       read_rr(merge_rr);
+       return fd;
+}
+
+int rerere(void)
+{
+       struct path_list merge_rr = { NULL, 0, 0, 1 };
+       int fd;
+
+       fd = setup_rerere(&merge_rr);
+       if (fd < 0)
+               return 0;
+       return do_plain_rerere(&merge_rr, fd);
+}
diff --git a/rerere.h b/rerere.h
new file mode 100644 (file)
index 0000000..35b0fa8
--- /dev/null
+++ b/rerere.h
@@ -0,0 +1,9 @@
+#ifndef RERERE_H
+#define RERERE_H
+
+#include "path-list.h"
+
+extern int setup_rerere(struct path_list *);
+extern int rerere(void);
+
+#endif
index bbd563e6514f98ed5678314573fc4521b9fa0a95..3897fec53170c50921eb1952bc4bdf9c08480638 100644 (file)
@@ -260,7 +260,7 @@ static int tree_difference = REV_TREE_SAME;
 static void file_add_remove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
-                   const char *base, const char *path)
+                   const char *fullpath)
 {
        int diff = REV_TREE_DIFFERENT;
 
@@ -286,7 +286,7 @@ static void file_change(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
-                const char *base, const char *path)
+                const char *fullpath)
 {
        tree_difference = REV_TREE_DIFFERENT;
        DIFF_OPT_SET(options, HAS_CHANGES);
@@ -413,10 +413,26 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
        commit->object.flags |= TREESAME;
 }
 
-static int add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
+static void insert_by_date_cached(struct commit *p, struct commit_list **head,
+                   struct commit_list *cached_base, struct commit_list **cache)
+{
+       struct commit_list *new_entry;
+
+       if (cached_base && p->date < cached_base->item->date)
+               new_entry = insert_by_date(p, &cached_base->next);
+       else
+               new_entry = insert_by_date(p, head);
+
+       if (cache && (!*cache || p->date < (*cache)->item->date))
+               *cache = new_entry;
+}
+
+static int add_parents_to_list(struct rev_info *revs, struct commit *commit,
+                   struct commit_list **list, struct commit_list **cache_ptr)
 {
        struct commit_list *parent = commit->parents;
        unsigned left_flag;
+       struct commit_list *cached_base = cache_ptr ? *cache_ptr : NULL;
 
        if (commit->object.flags & ADDED)
                return 0;
@@ -446,7 +462,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, str
                        if (p->object.flags & SEEN)
                                continue;
                        p->object.flags |= SEEN;
-                       insert_by_date(p, list);
+                       insert_by_date_cached(p, list, cached_base, cache_ptr);
                }
                return 0;
        }
@@ -471,7 +487,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, str
                p->object.flags |= left_flag;
                if (!(p->object.flags & SEEN)) {
                        p->object.flags |= SEEN;
-                       insert_by_date(p, list);
+                       insert_by_date_cached(p, list, cached_base, cache_ptr);
                }
                if(revs->first_parent_only)
                        break;
@@ -612,7 +628,7 @@ static int limit_list(struct rev_info *revs)
 
                if (revs->max_age != -1 && (commit->date < revs->max_age))
                        obj->flags |= UNINTERESTING;
-               if (add_parents_to_list(revs, commit, &list) < 0)
+               if (add_parents_to_list(revs, commit, &list, NULL) < 0)
                        return -1;
                if (obj->flags & UNINTERESTING) {
                        mark_parents_uninteresting(commit);
@@ -1415,10 +1431,12 @@ enum rewrite_result {
 
 static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
 {
+       struct commit_list *cache = NULL;
+
        for (;;) {
                struct commit *p = *pp;
                if (!revs->limited)
-                       if (add_parents_to_list(revs, p, &revs->commits) < 0)
+                       if (add_parents_to_list(revs, p, &revs->commits, &cache) < 0)
                                return rewrite_one_error;
                if (p->parents && p->parents->next)
                        return rewrite_one_ok;
@@ -1542,7 +1560,7 @@ static struct commit *get_revision_1(struct rev_info *revs)
                        if (revs->max_age != -1 &&
                            (commit->date < revs->max_age))
                                continue;
-                       if (add_parents_to_list(revs, commit, &revs->commits) < 0)
+                       if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0)
                                return NULL;
                }
 
index 2df78b5afd57cf8e86f01134bf6c49d74defc52a..e281c14f01d37ab7623998c2990914aca49a7a3b 100644 (file)
@@ -1006,6 +1006,18 @@ static void mark_bad_packed_object(struct packed_git *p,
        p->num_bad_objects++;
 }
 
+static int has_packed_and_bad(const unsigned char *sha1)
+{
+       struct packed_git *p;
+       unsigned i;
+
+       for (p = packed_git; p; p = p->next)
+               for (i = 0; i < p->num_bad_objects; i++)
+                       if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
+                               return 1;
+       return 0;
+}
+
 int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
 {
        unsigned char real_sha1[20];
@@ -1647,7 +1659,7 @@ static void *unpack_delta_entry(struct packed_git *p,
                      sha1_to_hex(base_sha1), (uintmax_t)base_offset,
                      p->pack_name);
                mark_bad_packed_object(p, base_sha1);
-               base = read_sha1_file(base_sha1, type, &base_size);
+               base = read_object(base_sha1, type, &base_size);
                if (!base)
                        return NULL;
        }
@@ -1945,7 +1957,7 @@ static void *read_packed_sha1(const unsigned char *sha1,
                error("failed to read object %s at offset %"PRIuMAX" from %s",
                      sha1_to_hex(sha1), (uintmax_t)e.offset, e.p->pack_name);
                mark_bad_packed_object(e.p, sha1);
-               data = read_sha1_file(sha1, type, size);
+               data = read_object(sha1, type, size);
        }
        return data;
 }
@@ -2010,8 +2022,8 @@ int pretend_sha1_file(void *buf, unsigned long len, enum object_type type,
        return 0;
 }
 
-void *read_sha1_file(const unsigned char *sha1, enum object_type *type,
-                    unsigned long *size)
+void *read_object(const unsigned char *sha1, enum object_type *type,
+                 unsigned long *size)
 {
        unsigned long mapsize;
        void *map, *buf;
@@ -2037,6 +2049,16 @@ void *read_sha1_file(const unsigned char *sha1, enum object_type *type,
        return read_packed_sha1(sha1, type, size);
 }
 
+void *read_sha1_file(const unsigned char *sha1, enum object_type *type,
+                    unsigned long *size)
+{
+       void *data = read_object(sha1, type, size);
+       /* legacy behavior is to die on corrupted objects */
+       if (!data && (has_loose_object(sha1) || has_packed_and_bad(sha1)))
+               die("object %s is corrupted", sha1_to_hex(sha1));
+       return data;
+}
+
 void *read_object_with_reference(const unsigned char *sha1,
                                 const char *required_type_name,
                                 unsigned long *size,
index 31ff491b7448f69385cddb640819e7ea14e2fb3e..6608ee80b08881146b2678dba3120a036b69cc28 100644 (file)
@@ -11,6 +11,7 @@ struct shortlog {
        int wrap;
        int in1;
        int in2;
+       int user_format;
 
        char *common_repo_prefix;
        int email;
index a778865ae7d015535763b856391ba751f2a9f87c..0d65cedaa6566a6dd654753cb574c9ee64b1c90b 100644 (file)
@@ -26,7 +26,7 @@ clean:
        $(RM) -r 'trash directory' test-results
 
 aggregate-results:
-       ./aggregate-results.sh test-results/t*-*
+       '$(SHELL_PATH_SQ)' ./aggregate-results.sh test-results/t*-*
 
 # we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
 full-svn-test:
index 52e88e30465b8de4f0d9bd592bdd525e0f31145a..d5bab75d7da49ebb53e368d67f6b867f5417a125 100755 (executable)
@@ -10,9 +10,9 @@ for file
 do
        while read type value
        do
-               case $type in
-               '')
-                       continue ;;
+               case $type in
+               '')
+                       continue ;;
                fixed)
                        fixed=$(($fixed + $value)) ;;
                success)
@@ -20,9 +20,9 @@ do
                failed)
                        failed=$(($failed + $value)) ;;
                broken)
-                       broken=$(( $broken + $value)) ;;
+                       broken=$(($broken + $value)) ;;
                total)
-                       total=$(( $total + $value)) ;;
+                       total=$(($total + $value)) ;;
                esac
        done <"$file"
 done
index 5d3bd9dda916d454997da0015c35d0fa7d9f72c0..a841df2a9e3a9ed64e81ab7b9778e59cfc714cad 100644 (file)
@@ -98,7 +98,7 @@ LoadModule dav_module $SVN_HTTPD_MODULE_PATH/mod_dav.so
 LoadModule dav_svn_module $SVN_HTTPD_MODULE_PATH/mod_dav_svn.so
 <Location /$repo_base_path>
        DAV svn
-       SVNPath $rawsvnrepo
+       SVNPath "$rawsvnrepo"
 </Location>
 EOF
        "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k start
index 4934a4e01092e27e07254b10ed3e54e8699b54ac..36d9a2ae3aa7adb0bff18bb2a4180a2ba4369766 100755 (executable)
@@ -50,12 +50,12 @@ test_debug \
 
 test_expect_success \
     'rebase topic branch against new master and check git-am did not get halted' \
-    'git-rebase master && test ! -d .dotest'
+    'git-rebase master && test ! -d .git/rebase'
 
 test_expect_success \
        'rebase --merge topic branch that was partially merged upstream' \
        'git-checkout -f my-topic-branch-merge &&
         git-rebase --merge master-merge &&
-        test ! -d .git/.dotest-merge'
+        test ! -d .git/rebase-merge'
 
 test_done
index 092aa265733b0c6cbf75ac532c3c006457668bc8..ffe3dd97b7b1c056d854e28795e1313ce1633452 100755 (executable)
@@ -159,19 +159,19 @@ test_expect_success 'stop on conflicting pick' '
        git tag new-branch1 &&
        test_must_fail git rebase -i master &&
        test "$(git rev-parse HEAD~3)" = "$(git rev-parse master)" &&
-       test_cmp expect .git/.dotest-merge/patch &&
+       test_cmp expect .git/rebase-merge/patch &&
        test_cmp expect2 file1 &&
        test "$(git-diff --name-status |
                sed -n -e "/^U/s/^U[^a-z]*//p")" = file1 &&
-       test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) &&
-       test 0 = $(grep -c "^[^#]" < .git/.dotest-merge/git-rebase-todo)
+       test 4 = $(grep -v "^#" < .git/rebase-merge/done | wc -l) &&
+       test 0 = $(grep -c "^[^#]" < .git/rebase-merge/git-rebase-todo)
 '
 
 test_expect_success 'abort' '
        git rebase --abort &&
        test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
-       ! test -d .git/.dotest-merge
+       ! test -d .git/rebase-merge
 '
 
 test_expect_success 'retain authorship' '
index 1777ffe8a22d8f9dd28efadd3cb9ea65be9c2178..12c8804a030f889bc0a416d5743600aa8a427eb7 100755 (executable)
@@ -74,7 +74,7 @@ testrebase() {
        '
 }
 
-testrebase "" .dotest
-testrebase " --merge" .git/.dotest-merge
+testrebase "" .git/rebase
+testrebase " --merge" .git/rebase-merge
 
 test_done
index bc982607d09bbdeb0d486eb282f5756a48ae5bcb..5cbd5ef6798bcef1c011893a4759cac4047dc6b7 100755 (executable)
@@ -102,7 +102,7 @@ test_expect_success 'am applies patch correctly' '
        git checkout first &&
        test_tick &&
        git am <patch1 &&
-       ! test -d .dotest &&
+       ! test -d .git/rebase &&
        test -z "$(git diff second)" &&
        test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
        test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
@@ -123,7 +123,7 @@ test_expect_success 'am changes committer and keeps author' '
        test_tick &&
        git checkout first &&
        git am patch2 &&
-       ! test -d .dotest &&
+       ! test -d .git/rebase &&
        test "$(git rev-parse master^^)" = "$(git rev-parse HEAD^^)" &&
        test -z "$(git diff master..HEAD)" &&
        test -z "$(git diff master^..HEAD^)" &&
@@ -163,7 +163,7 @@ test_expect_success 'am without --keep removes Re: and [PATCH] stuff' '
 test_expect_success 'am --keep really keeps the subject' '
        git checkout HEAD^ &&
        git am --keep patch4 &&
-       ! test -d .dotest &&
+       ! test -d .git/rebase &&
        git-cat-file commit HEAD |
                grep -q -F "Re: Re: Re: [PATCH 1/5 v2] third"
 '
@@ -176,19 +176,19 @@ test_expect_success 'am -3 falls back to 3-way merge' '
        test_tick &&
        git commit -m "copied stuff" &&
        git am -3 lorem-move.patch &&
-       ! test -d .dotest &&
+       ! test -d .git/rebase &&
        test -z "$(git diff lorem)"
 '
 
 test_expect_success 'am pauses on conflict' '
        git checkout lorem2^^ &&
        ! git am lorem-move.patch &&
-       test -d .dotest
+       test -d .git/rebase
 '
 
 test_expect_success 'am --skip works' '
        git am --skip &&
-       ! test -d .dotest &&
+       ! test -d .git/rebase &&
        test -z "$(git diff lorem2^^ -- file)" &&
        test goodbye = "$(cat another)"
 '
@@ -196,31 +196,31 @@ test_expect_success 'am --skip works' '
 test_expect_success 'am --resolved works' '
        git checkout lorem2^^ &&
        ! git am lorem-move.patch &&
-       test -d .dotest &&
+       test -d .git/rebase &&
        echo resolved >>file &&
        git add file &&
        git am --resolved &&
-       ! test -d .dotest &&
+       ! test -d .git/rebase &&
        test goodbye = "$(cat another)"
 '
 
 test_expect_success 'am takes patches from a Pine mailbox' '
        git checkout first &&
        cat pine patch1 | git am &&
-       ! test -d .dotest &&
+       ! test -d .git/rebase &&
        test -z "$(git diff master^..HEAD)"
 '
 
 test_expect_success 'am fails on mail without patch' '
        ! git am <failmail &&
-       rm -r .dotest/
+       rm -r .git/rebase/
 '
 
 test_expect_success 'am fails on empty patch' '
        echo "---" >>failmail &&
        ! git am <failmail &&
        git am --skip &&
-       ! test -d .dotest
+       ! test -d .git/rebase
 '
 
 test_expect_success 'am works from stdin in subdirectory' '
index a64727d5ad00cb222e3020b6702e4f89e212039c..b5a4202998f51cd282ae46c7caec6e615ee47e3f 100755 (executable)
@@ -9,6 +9,8 @@ test_description='git rerere
 . ./test-lib.sh
 
 cat > a1 << EOF
+Some title
+==========
 Whether 'tis nobler in the mind to suffer
 The slings and arrows of outrageous fortune,
 Or to take arms against a sea of troubles,
@@ -24,6 +26,8 @@ git commit -q -a -m initial
 
 git checkout -b first
 cat >> a1 << EOF
+Some title
+==========
 To die, to sleep;
 To sleep: perchance to dream: ay, there's the rub;
 For in that sleep of death what dreams may come
@@ -35,7 +39,7 @@ git commit -q -a -m first
 
 git checkout -b second master
 git show first:a1 |
-sed -e 's/To die, t/To die! T/' > a1
+sed -e 's/To die, t/To die! T/' -e 's/Some title/Some Title/' > a1
 echo "* END *" >>a1
 git commit -q -a -m second
 
@@ -53,16 +57,16 @@ test_expect_success 'conflicting merge' '
        ! git merge first
 '
 
-sha1=$(sed -e 's/      .*//' .git/rr-cache/MERGE_RR)
+sha1=$(sed -e 's/      .*//' .git/MERGE_RR)
 rr=.git/rr-cache/$sha1
-test_expect_success 'recorded preimage' "grep ======= $rr/preimage"
+test_expect_success 'recorded preimage' "grep ^=======$ $rr/preimage"
 
 test_expect_success 'rerere.enabled works, too' '
        rm -rf .git/rr-cache &&
        git config rerere.enabled true &&
        git reset --hard &&
        ! git merge first &&
-       grep ======= $rr/preimage
+       grep ^=======$ $rr/preimage
 '
 
 test_expect_success 'no postimage or thisimage yet' \
@@ -71,7 +75,7 @@ test_expect_success 'no postimage or thisimage yet' \
 test_expect_success 'preimage has right number of lines' '
 
        cnt=$(sed -ne "/^<<<<<<</,/^>>>>>>>/p" $rr/preimage | wc -l) &&
-       test $cnt = 9
+       test $cnt = 13
 
 '
 
@@ -80,13 +84,23 @@ git show first:a1 > a1
 cat > expect << EOF
 --- a/a1
 +++ b/a1
-@@ -6,17 +6,9 @@
+@@ -1,4 +1,4 @@
+-Some Title
++Some title
+ ==========
+ Whether 'tis nobler in the mind to suffer
+ The slings and arrows of outrageous fortune,
+@@ -8,21 +8,11 @@
  The heart-ache and the thousand natural shocks
  That flesh is heir to, 'tis a consummation
  Devoutly to be wish'd.
 -<<<<<<<
+-Some Title
+-==========
 -To die! To sleep;
 -=======
+ Some title
+ ==========
  To die, to sleep;
 ->>>>>>>
  To sleep: perchance to dream: ay, there's the rub;
@@ -124,12 +138,12 @@ test_expect_success 'another conflicting merge' '
 '
 
 git show first:a1 | sed 's/To die: t/To die! T/' > expect
-test_expect_success 'rerere kicked in' "! grep ======= a1"
+test_expect_success 'rerere kicked in' "! grep ^=======$ a1"
 
 test_expect_success 'rerere prefers first change' 'test_cmp a1 expect'
 
 rm $rr/postimage
-echo "$sha1    a1" | perl -pe 'y/\012/\000/' > .git/rr-cache/MERGE_RR
+echo "$sha1    a1" | perl -pe 'y/\012/\000/' > .git/MERGE_RR
 
 test_expect_success 'rerere clear' 'git rerere clear'
 
@@ -176,7 +190,7 @@ test_expect_success 'file2 added differently in two branches' '
        git add file2 &&
        git commit -m version2 &&
        ! git merge fourth &&
-       sha1=$(sed -e "s/       .*//" .git/rr-cache/MERGE_RR) &&
+       sha1=$(sed -e "s/       .*//" .git/MERGE_RR) &&
        rr=.git/rr-cache/$sha1 &&
        echo Cello > file2 &&
        git add file2 &&
index 577ecc210a55a5da10552a4415c4cbb5e321039b..e9f3e72c7ee5584d956d46126956c641a7d53905 100755 (executable)
@@ -11,7 +11,7 @@ test_expect_success 'split sample box' \
        'git mailsplit -o. ../t5100/sample.mbox >last &&
        last=`cat last` &&
        echo total is $last &&
-       test `cat last` = 9'
+       test `cat last` = 10'
 
 for mail in `echo 00*`
 do
diff --git a/t/t5100/0010 b/t/t5100/0010
new file mode 100644 (file)
index 0000000..f5892c9
--- /dev/null
@@ -0,0 +1,35 @@
+From b9704a518e21158433baa2cc2d591fea687967f6 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Lukas=20Sandstr=C3=B6m?= <lukass@etek.chalmers.se>
+Date: Thu, 10 Jul 2008 23:41:33 +0200
+Subject: Re: discussion that lead to this patch
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+[PATCH] git-mailinfo: Fix getting the subject from the body
+
+"Subject: " isn't in the static array "header", and thus
+memcmp("Subject: ", header[i], 7) will never match.
+
+Signed-off-by: Lukas Sandström <lukass@etek.chalmers.se>
+Signed-off-by: Junio C Hamano <gitster@pobox.com>
+---
+ builtin-mailinfo.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
+index 962aa34..2d1520f 100644
+--- a/builtin-mailinfo.c
++++ b/builtin-mailinfo.c
+@@ -334,7 +334,7 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over
+               return 1;
+       if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
+               for (i = 0; header[i]; i++) {
+-                      if (!memcmp("Subject: ", header[i], 9)) {
++                      if (!memcmp("Subject", header[i], 7)) {
+                               if (! handle_header(line, hdr_data[i], 0)) {
+                                       return 1;
+                               }
+-- 
+1.5.6.2.455.g1efb2
+
diff --git a/t/t5100/info0010 b/t/t5100/info0010
new file mode 100644 (file)
index 0000000..1791241
--- /dev/null
@@ -0,0 +1,5 @@
+Author: Lukas Sandström
+Email: lukass@etek.chalmers.se
+Subject: git-mailinfo: Fix getting the subject from the body
+Date: Thu, 10 Jul 2008 23:41:33 +0200
+
diff --git a/t/t5100/msg0010 b/t/t5100/msg0010
new file mode 100644 (file)
index 0000000..a96c230
--- /dev/null
@@ -0,0 +1,5 @@
+"Subject: " isn't in the static array "header", and thus
+memcmp("Subject: ", header[i], 7) will never match.
+
+Signed-off-by: Lukas Sandström <lukass@etek.chalmers.se>
+Signed-off-by: Junio C Hamano <gitster@pobox.com>
diff --git a/t/t5100/patch0010 b/t/t5100/patch0010
new file mode 100644 (file)
index 0000000..f055481
--- /dev/null
@@ -0,0 +1,20 @@
+---
+ builtin-mailinfo.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
+index 962aa34..2d1520f 100644
+--- a/builtin-mailinfo.c
++++ b/builtin-mailinfo.c
+@@ -334,7 +334,7 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over
+               return 1;
+       if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
+               for (i = 0; header[i]; i++) {
+-                      if (!memcmp("Subject: ", header[i], 9)) {
++                      if (!memcmp("Subject", header[i], 7)) {
+                               if (! handle_header(line, hdr_data[i], 0)) {
+                                       return 1;
+                               }
+-- 
+1.5.6.2.455.g1efb2
+
index 0476b96c33de8bbc6c4b3bc4f510e9fc50aa6440..aba57f922b33b6ab708afbbf82c7e56f6e37bb8d 100644 (file)
@@ -430,3 +430,38 @@ index b426a14..97756ec 100644
 =20
 =20
  2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the
+From b9704a518e21158433baa2cc2d591fea687967f6 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Lukas=20Sandstr=C3=B6m?= <lukass@etek.chalmers.se>
+Date: Thu, 10 Jul 2008 23:41:33 +0200
+Subject: Re: discussion that lead to this patch
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+[PATCH] git-mailinfo: Fix getting the subject from the body
+
+"Subject: " isn't in the static array "header", and thus
+memcmp("Subject: ", header[i], 7) will never match.
+
+Signed-off-by: Lukas Sandström <lukass@etek.chalmers.se>
+Signed-off-by: Junio C Hamano <gitster@pobox.com>
+---
+ builtin-mailinfo.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
+index 962aa34..2d1520f 100644
+--- a/builtin-mailinfo.c
++++ b/builtin-mailinfo.c
+@@ -334,7 +334,7 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over
+               return 1;
+       if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
+               for (i = 0; header[i]; i++) {
+-                      if (!memcmp("Subject: ", header[i], 9)) {
++                      if (!memcmp("Subject", header[i], 7)) {
+                               if (! handle_header(line, hdr_data[i], 0)) {
+                                       return 1;
+                               }
+-- 
+1.5.6.2.455.g1efb2
+
index 88e96fb91b9fd54ddc6bd43de4996c1e70404a6f..c4af9ca0a7edf6230dc6ca8ec10848545971fce7 100755 (executable)
@@ -23,7 +23,7 @@ test_expect_success 'setup' '
 
        : > super-file &&
        git add super-file &&
-       git submodule add . sub &&
+       git submodule add "$(pwd)" sub &&
        git symbolic-ref HEAD refs/heads/super &&
        test_tick &&
        git commit -m super-initial &&
diff --git a/t/t6011-rev-list-with-bad-commit.sh b/t/t6011-rev-list-with-bad-commit.sh
new file mode 100755 (executable)
index 0000000..e51eb41
--- /dev/null
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+test_description='git rev-list should notice bad commits'
+
+. ./test-lib.sh
+
+# Note:
+# - compression level is set to zero to make "corruptions" easier to perform
+# - reflog is disabled to avoid extra references which would twart the test
+
+test_expect_success 'setup' \
+   '
+   git init &&
+   git config core.compression 0 &&
+   git config core.logallrefupdates false &&
+   echo "foo" > foo &&
+   git add foo &&
+   git commit -m "first commit" &&
+   echo "bar" > bar &&
+   git add bar &&
+   git commit -m "second commit" &&
+   echo "baz" > baz &&
+   git add baz &&
+   git commit -m "third commit" &&
+   echo "foo again" >> foo &&
+   git add foo &&
+   git commit -m "fourth commit" &&
+   git repack -a -f -d
+   '
+
+test_expect_success 'verify number of revisions' \
+   '
+   revs=$(git rev-list --all | wc -l) &&
+   test $revs -eq 4 &&
+   first_commit=$(git rev-parse HEAD~3)
+   '
+
+test_expect_success 'corrupt second commit object' \
+   '
+   perl -i.bak -pe "s/second commit/socond commit/" .git/objects/pack/*.pack &&
+   test_must_fail git fsck --full
+   '
+
+test_expect_success 'rev-list should fail' \
+   '
+   test_must_fail git rev-list --all > /dev/null
+   '
+
+test_expect_success 'git repack _MUST_ fail' \
+   '
+   test_must_fail git repack -a -f -d
+   '
+
+test_expect_success 'first commit is still available' \
+   '
+   git log $first_commit
+   '
+
+test_done
+
index 0ab14a6e81302db6a1239d68270ab909cb2fd216..331b9b07d4eedb07377de605ebb87691427b7bb4 100755 (executable)
@@ -89,4 +89,8 @@ EOF
 
 test_expect_success 'Criss-cross merge result' 'cmp file file-expect'
 
+test_expect_success 'Criss-cross merge fails (-s resolve)' \
+'git reset --hard A^ &&
+test_must_fail git merge -s resolve -m "final merge" B'
+
 test_done
index d21cd290d3d8e3d912b94045f3d3d9db8bdd7e7d..9ab5bdacc41d026ea81c65b0d95a61a61521429a 100755 (executable)
@@ -221,37 +221,13 @@ test_expect_success 'setup' '
 
 test_debug 'gitk --all'
 
-test_expect_success 'test option parsing' '
-       if git merge -$ c1
-       then
-               echo "[OOPS] -$ accepted"
-               false
-       fi &&
-       if git merge --no-such c1
-       then
-               echo "[OOPS] --no-such accepted"
-               false
-       fi &&
-       if git merge -s foobar c1
-       then
-               echo "[OOPS] -s foobar accepted"
-               false
-       fi &&
-       if git merge -s=foobar c1
-       then
-               echo "[OOPS] -s=foobar accepted"
-               false
-       fi &&
-       if git merge -m
-       then
-               echo "[OOPS] missing commit msg accepted"
-               false
-       fi &&
-       if git merge
-       then
-               echo "[OOPS] missing commit references accepted"
-               false
-       fi
+test_expect_failure 'test option parsing' '
+       test_must_fail git merge -$ c1 &&
+       test_must_fail git merge --no-such c1 &&
+       test_must_fail git merge -s foobar c1 &&
+       test_must_fail git merge -s=foobar c1 &&
+       test_must_fail git merge -m &&
+       test_must_fail git merge
 '
 
 test_expect_success 'merge c0 with c1' '
@@ -465,11 +441,51 @@ test_expect_success 'merge log message' '
        git merge --no-log c2 &&
        git show -s --pretty=format:%b HEAD >msg.act &&
        verify_diff msg.nolog msg.act "[OOPS] bad merge log message" &&
+
        git merge --log c3 &&
        git show -s --pretty=format:%b HEAD >msg.act &&
+       verify_diff msg.log msg.act "[OOPS] bad merge log message" &&
+
+       git reset --hard HEAD^ &&
+       git config merge.log yes &&
+       git merge c3 &&
+       git show -s --pretty=format:%b HEAD >msg.act &&
        verify_diff msg.log msg.act "[OOPS] bad merge log message"
 '
 
 test_debug 'gitk --all'
 
+test_expect_success 'merge c1 with c0, c2, c0, and c1' '
+       git reset --hard c1 &&
+       git config branch.master.mergeoptions "" &&
+       test_tick &&
+       git merge c0 c2 c0 c1 &&
+       verify_merge file result.1-5 &&
+       verify_parents $c1 $c2
+'
+
+test_debug 'gitk --all'
+
+test_expect_success 'merge c1 with c0, c2, c0, and c1' '
+       git reset --hard c1 &&
+       git config branch.master.mergeoptions "" &&
+       test_tick &&
+       git merge c0 c2 c0 c1 &&
+       verify_merge file result.1-5 &&
+       verify_parents $c1 $c2
+'
+
+test_debug 'gitk --all'
+
+test_expect_success 'merge c1 with c1 and c2' '
+       git reset --hard c1 &&
+       git config branch.master.mergeoptions "" &&
+       test_tick &&
+       git merge c1 c2 &&
+       verify_merge file result.1-5 &&
+       verify_parents $c1 $c2
+'
+
+test_debug 'gitk --all'
+
 test_done
diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
new file mode 100755 (executable)
index 0000000..95b4d71
--- /dev/null
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing pull.* configuration parsing.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       echo c0 >c0.c &&
+       git add c0.c &&
+       git commit -m c0 &&
+       git tag c0 &&
+       echo c1 >c1.c &&
+       git add c1.c &&
+       git commit -m c1 &&
+       git tag c1 &&
+       git reset --hard c0 &&
+       echo c2 >c2.c &&
+       git add c2.c &&
+       git commit -m c2 &&
+       git tag c2 &&
+       git reset --hard c0 &&
+       echo c3 >c3.c &&
+       git add c3.c &&
+       git commit -m c3 &&
+       git tag c3
+'
+
+test_expect_success 'merge c1 with c2' '
+       git reset --hard c1 &&
+       test -f c0.c &&
+       test -f c1.c &&
+       test ! -f c2.c &&
+       test ! -f c3.c &&
+       git merge c2 &&
+       test -f c1.c &&
+       test -f c2.c
+'
+
+test_expect_success 'merge c1 with c2 (ours in pull.twohead)' '
+       git reset --hard c1 &&
+       git config pull.twohead ours &&
+       git merge c2 &&
+       test -f c1.c &&
+       ! test -f c2.c
+'
+
+test_expect_success 'merge c1 with c2 and c3 (recursive in pull.octopus)' '
+       git reset --hard c1 &&
+       git config pull.octopus "recursive" &&
+       test_must_fail git merge c2 c3 &&
+       test "$(git rev-parse c1)" = "$(git rev-parse HEAD)"
+'
+
+test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octopus)' '
+       git reset --hard c1 &&
+       git config pull.octopus "recursive octopus" &&
+       git merge c2 c3 &&
+       test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+       test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
+       test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
+       test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
+       git diff --exit-code &&
+       test -f c0.c &&
+       test -f c1.c &&
+       test -f c2.c &&
+       test -f c3.c
+'
+
+conflict_count()
+{
+       {
+               git diff-files --name-only
+               git ls-files --unmerged
+       } | wc -l
+}
+
+# c4 - c5
+#    \ c6
+#
+# There are two conflicts here:
+#
+# 1) Because foo.c is renamed to bar.c, recursive will handle this,
+# resolve won't.
+#
+# 2) One in conflict.c and that will always fail.
+
+test_expect_success 'setup conflicted merge' '
+       git reset --hard c0 &&
+       echo A >conflict.c &&
+       git add conflict.c &&
+       echo contents >foo.c &&
+       git add foo.c &&
+       git commit -m c4 &&
+       git tag c4 &&
+       echo B >conflict.c &&
+       git add conflict.c &&
+       git mv foo.c bar.c &&
+       git commit -m c5 &&
+       git tag c5 &&
+       git reset --hard c4 &&
+       echo C >conflict.c &&
+       git add conflict.c &&
+       echo secondline >> foo.c &&
+       git add foo.c &&
+       git commit -m c6 &&
+       git tag c6
+'
+
+# First do the merge with resolve and recursive then verify that
+# recusive is choosen.
+
+test_expect_success 'merge picks up the best result' '
+       git config pull.twohead "recursive resolve" &&
+       git reset --hard c5 &&
+       git merge -s resolve c6
+       resolve_count=$(conflict_count) &&
+       git reset --hard c5 &&
+       git merge -s recursive c6
+       recursive_count=$(conflict_count) &&
+       git reset --hard c5 &&
+       git merge c6
+       auto_count=$(conflict_count) &&
+       test $auto_count = $recursive_count &&
+       test $auto_count != $resolve_count
+'
+
+test_done
diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
new file mode 100755 (executable)
index 0000000..fcb8285
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing octopus merge with more than 25 refs.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       echo c0 > c0.c &&
+       git add c0.c &&
+       git commit -m c0 &&
+       git tag c0 &&
+       i=1 &&
+       while test $i -le 30
+       do
+               git reset --hard c0 &&
+               echo c$i > c$i.c &&
+               git add c$i.c &&
+               git commit -m c$i &&
+               git tag c$i &&
+               i=`expr $i + 1` || return 1
+       done
+'
+
+test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
+       git reset --hard c1 &&
+       i=2 &&
+       refs="" &&
+       while test $i -le 30
+       do
+               refs="$refs c$i"
+               i=`expr $i + 1`
+       done
+       git merge $refs &&
+       test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+       i=1 &&
+       while test $i -le 30
+       do
+               test "$(git rev-parse c$i)" = "$(git rev-parse HEAD^$i)" &&
+               i=`expr $i + 1` || return 1
+       done &&
+       git diff --exit-code &&
+       i=1 &&
+       while test $i -le 30
+       do
+               test -f c$i.c &&
+               i=`expr $i + 1` || return 1
+       done
+'
+
+test_done
diff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh
new file mode 100755 (executable)
index 0000000..17b19dc
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing octopus merge when reducing parents to independent branches.'
+
+. ./test-lib.sh
+
+# 0 - 1
+#   \ 2
+#   \ 3
+#   \ 4 - 5
+#
+# So 1, 2, 3 and 5 should be kept, 4 should be avoided.
+
+test_expect_success 'setup' '
+       echo c0 > c0.c &&
+       git add c0.c &&
+       git commit -m c0 &&
+       git tag c0 &&
+       echo c1 > c1.c &&
+       git add c1.c &&
+       git commit -m c1 &&
+       git tag c1 &&
+       git reset --hard c0 &&
+       echo c2 > c2.c &&
+       git add c2.c &&
+       git commit -m c2 &&
+       git tag c2 &&
+       git reset --hard c0 &&
+       echo c3 > c3.c &&
+       git add c3.c &&
+       git commit -m c3 &&
+       git tag c3 &&
+       git reset --hard c0 &&
+       echo c4 > c4.c &&
+       git add c4.c &&
+       git commit -m c4 &&
+       git tag c4 &&
+       echo c5 > c5.c &&
+       git add c5.c &&
+       git commit -m c5 &&
+       git tag c5
+'
+
+test_expect_success 'merge c1 with c2, c3, c4, c5' '
+       git reset --hard c1 &&
+       git merge c2 c3 c4 c5 &&
+       test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+       test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
+       test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
+       test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
+       test "$(git rev-parse c5)" = "$(git rev-parse HEAD^4)" &&
+       git diff --exit-code &&
+       test -f c0.c &&
+       test -f c1.c &&
+       test -f c2.c &&
+       test -f c3.c &&
+       test -f c4.c &&
+       test -f c5.c
+'
+
+test_done
diff --git a/t/t7604-merge-custom-message.sh b/t/t7604-merge-custom-message.sh
new file mode 100755 (executable)
index 0000000..6081677
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing merge when using a custom message for the merge commit.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       echo c0 > c0.c &&
+       git add c0.c &&
+       git commit -m c0 &&
+       git tag c0 &&
+       echo c1 > c1.c &&
+       git add c1.c &&
+       git commit -m c1 &&
+       git tag c1 &&
+       git reset --hard c0 &&
+       echo c2 > c2.c &&
+       git add c2.c &&
+       git commit -m c2 &&
+       git tag c2
+'
+
+cat >expected <<\EOF
+custom message
+
+Merge commit 'c2'
+EOF
+test_expect_success 'merge c2 with a custom message' '
+       git reset --hard c1 &&
+       git merge -m "custom message" c2 &&
+       git cat-file commit HEAD | sed -e "1,/^$/d" > actual &&
+       test_cmp expected actual
+'
+
+test_done
diff --git a/t/t7605-merge-resolve.sh b/t/t7605-merge-resolve.sh
new file mode 100755 (executable)
index 0000000..ee21a10
--- /dev/null
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing the resolve strategy.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       echo c0 > c0.c &&
+       git add c0.c &&
+       git commit -m c0 &&
+       git tag c0 &&
+       echo c1 > c1.c &&
+       git add c1.c &&
+       git commit -m c1 &&
+       git tag c1 &&
+       git reset --hard c0 &&
+       echo c2 > c2.c &&
+       git add c2.c &&
+       git commit -m c2 &&
+       git tag c2 &&
+       git reset --hard c0 &&
+       echo c3 > c2.c &&
+       git add c2.c &&
+       git commit -m c3 &&
+       git tag c3
+'
+
+test_expect_success 'merge c1 to c2' '
+       git reset --hard c1 &&
+       git merge -s resolve c2 &&
+       test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+       test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
+       test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
+       git diff --exit-code &&
+       test -f c0.c &&
+       test -f c1.c &&
+       test -f c2.c
+'
+
+test_expect_success 'merge c2 to c3 (fails)' '
+       git reset --hard c2 &&
+       test_must_fail git merge -s resolve c3
+'
+test_done
index 58a3a7b1c333131051282acbae71dc6a1cb16b13..27a65e05df20c2695b9fb849be62d715c968a433 100755 (executable)
@@ -87,7 +87,7 @@ test_expect_success 'multiple dcommit from git-svn will not clobber svn' "
        "
 
 
-test_expect_success 'check that rebase really failed' 'test -d .dotest'
+test_expect_success 'check that rebase really failed' 'test -d .git/rebase'
 
 test_expect_success 'resolve, continue the rebase and dcommit' "
        echo clobber and I really mean it > file &&
index 047659fde1049777e7fe12113ecb305377b1f8f9..04d2a65c087de78fa8126b68774673532497276e 100755 (executable)
@@ -49,4 +49,13 @@ test_expect_success 'verify metadata for /dir' "
           grep '^git-svn-id: $dir_url@1 $uuid$'
        "
 
+test_expect_success 'find commit based on SVN revision number' "
+        git-svn find-rev r12 |
+           grep `git rev-parse HEAD`
+        "
+
+test_expect_success 'empty rebase' "
+       git-svn rebase
+       "
+
 test_done
index 655f88270b27b637b6d4b7d3fdb3af76f525064f..1e01e5c7483c2772964aa5250bc436b25454de7d 100755 (executable)
@@ -18,7 +18,7 @@ fi
 
 cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'`
 case "$cvsps_version" in
-2.1)
+2.1 | 2.2*)
        ;;
 '')
        say 'skipping cvsimport tests, cvsps not found'
@@ -26,7 +26,7 @@ case "$cvsps_version" in
        exit
        ;;
 *)
-       say 'skipping cvsimport tests, cvsps too old'
+       say 'skipping cvsimport tests, unsupported cvsps version'
        test_done
        exit
        ;;
index e1e2e6c6ce3c4effffb26e9aed7c6bdd2b00a5ae..bbb126fc46cfb28a0bc92cc0842c0dc72017751d 100644 (file)
@@ -15,6 +15,15 @@ static char *malloc_base(const char *base, int baselen, const char *path, int pa
        return newbase;
 }
 
+static char *malloc_fullname(const char *base, int baselen, const char *path, int pathlen)
+{
+       char *fullname = xmalloc(baselen + pathlen + 1);
+       memcpy(fullname, base, baselen);
+       memcpy(fullname + baselen, path, pathlen);
+       fullname[baselen + pathlen] = 0;
+       return fullname;
+}
+
 static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
                       const char *base, int baselen);
 
@@ -24,6 +33,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
        const char *path1, *path2;
        const unsigned char *sha1, *sha2;
        int cmp, pathlen1, pathlen2;
+       char *fullname;
 
        sha1 = tree_entry_extract(t1, &path1, &mode1);
        sha2 = tree_entry_extract(t2, &path2, &mode2);
@@ -55,15 +65,20 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
        if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) {
                int retval;
                char *newbase = malloc_base(base, baselen, path1, pathlen1);
-               if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE))
+               if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
+                       newbase[baselen + pathlen1] = 0;
                        opt->change(opt, mode1, mode2,
-                                   sha1, sha2, base, path1);
+                                   sha1, sha2, newbase);
+                       newbase[baselen + pathlen1] = '/';
+               }
                retval = diff_tree_sha1(sha1, sha2, newbase, opt);
                free(newbase);
                return retval;
        }
 
-       opt->change(opt, mode1, mode2, sha1, sha2, base, path1);
+       fullname = malloc_fullname(base, baselen, path1, pathlen1);
+       opt->change(opt, mode1, mode2, sha1, sha2, fullname);
+       free(fullname);
        return 0;
 }
 
@@ -205,10 +220,10 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
        unsigned mode;
        const char *path;
        const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
+       int pathlen = tree_entry_len(path, sha1);
 
        if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) {
                enum object_type type;
-               int pathlen = tree_entry_len(path, sha1);
                char *newbase = malloc_base(base, baselen, path, pathlen);
                struct tree_desc inner;
                void *tree;
@@ -224,7 +239,9 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
                free(tree);
                free(newbase);
        } else {
-               opt->add_remove(opt, prefix[0], mode, sha1, base, path);
+               char *fullname = malloc_fullname(base, baselen, path, pathlen);
+               opt->add_remove(opt, prefix[0], mode, sha1, fullname);
+               free(fullname);
        }
 }