Merge branch 'ab/pcre-v2' into next
authorJunio C Hamano <gitster@pobox.com>
Thu, 15 Mar 2018 22:10:58 +0000 (15:10 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 15 Mar 2018 22:11:00 +0000 (15:11 -0700)
Git can be built to use either v1 or v2 of the PCRE library, and so
far, the build-time configuration USE_LIBPCRE=YesPlease instructed
the build procedure to use v1, but now it means v2. USE_LIBPCRE1
and USE_LIBPCRE2 can be used to explicitly choose which version to
use, as before.

* ab/pcre-v2:
Makefile: make USE_LIBPCRE=YesPlease mean v2, not v1
configure: detect redundant --with-libpcre & --with-libpcre1
configure: fix a regression in PCRE v1 detection

69 files changed:
Documentation/config.txt
Documentation/git-config.txt
Documentation/git-pull.txt
Documentation/git-rebase.txt
Documentation/git-send-email.txt
Documentation/git.txt
INSTALL
Makefile
builtin/add.c
builtin/commit.c
builtin/config.c
builtin/merge.c
builtin/mv.c
builtin/pull.c
builtin/rebase--helper.c
builtin/remote.c
builtin/rm.c
cache.h
common-main.c
contrib/completion/git-completion.bash
contrib/emacs/.gitignore [deleted file]
contrib/emacs/Makefile [deleted file]
contrib/emacs/README
contrib/emacs/git-blame.el [deleted file]
contrib/emacs/git.el [deleted file]
contrib/examples/git-difftool.perl
environment.c
fetch-pack.c
git-rebase--interactive.sh
git-rebase.sh
git-send-email.perl
git.c
gitweb/INSTALL
gitweb/gitweb.perl
merge-recursive.c
merge-recursive.h
perl/FromCPAN/.gitattributes [new file with mode: 0644]
perl/FromCPAN/Error.pm [new file with mode: 0644]
perl/FromCPAN/Mail/Address.pm [new file with mode: 0644]
perl/Git.pm
perl/Git/Error.pm [deleted file]
perl/Git/FromCPAN/Error.pm [deleted file]
perl/Git/FromCPAN/Mail/Address.pm [deleted file]
perl/Git/LoadCPAN.pm [new file with mode: 0644]
perl/Git/LoadCPAN/Error.pm [new file with mode: 0644]
perl/Git/LoadCPAN/Mail/Address.pm [new file with mode: 0644]
perl/Git/Mail/Address.pm [deleted file]
read-cache.c
refs.c
repository.c
repository.h
rerere.c
sequencer.c
sequencer.h
setup.c
sha1_file.c
strbuf.c
strbuf.h
t/t3430-rebase-recreate-merges.sh [new file with mode: 0755]
t/t3501-revert-cherry-pick.sh
t/t6043-merge-rename-directories.sh [new file with mode: 0755]
t/t7006-pager.sh
t/t7607-merge-overwrite.sh
t/t9001-send-email.sh
t/t9400-git-cvsserver-server.sh
t/t9402-git-cvsserver-refs.sh
unpack-trees.c
unpack-trees.h
userdiff.c
index ce9102cea83766be0ae8d2ed2867fd4c587d7a95..7b4a2a8980fe7633d0118b9bba59d87bd5549442 100644 (file)
@@ -1058,6 +1058,10 @@ branch.<name>.rebase::
        "git pull" is run. See "pull.rebase" for doing this in a non
        branch-specific manner.
 +
+When recreate, also pass `--recreate-merges` along to 'git rebase'
+so that locally committed merge commits will not be flattened
+by running 'git pull'.
++
 When preserve, also pass `--preserve-merges` along to 'git rebase'
 so that locally committed merge commits will not be flattened
 by running 'git pull'.
@@ -2616,6 +2620,10 @@ pull.rebase::
        pull" is run. See "branch.<name>.rebase" for setting this on a
        per-branch basis.
 +
+When recreate, also pass `--recreate-merges` along to 'git rebase'
+so that locally committed merge commits will not be flattened
+by running 'git pull'.
++
 When preserve, also pass `--preserve-merges` along to 'git rebase'
 so that locally committed merge commits will not be flattened
 by running 'git pull'.
index 14da5fc157ee0ea7467e7b9bd9d33d465c1f7a3f..e09ed5d7d5147d93039c479efc8ab450bf5ca8b4 100644 (file)
@@ -233,6 +233,12 @@ See also <<FILES>>.
        using `--file`, `--global`, etc) and `on` when searching all
        config files.
 
+CONFIGURATION
+-------------
+`pager.config` is only respected when listing configuration, i.e., when
+using `--list` or any of the `--get-*` which may return multiple results.
+The default is to use a pager.
+
 [[FILES]]
 FILES
 -----
index ce05b7a5b13eadb6870f16a7168559434d376c20..b4f9f057ea910a6f22369b85b2bf24a7187f0a1e 100644 (file)
@@ -101,13 +101,16 @@ Options related to merging
 include::merge-options.txt[]
 
 -r::
---rebase[=false|true|preserve|interactive]::
+--rebase[=false|true|recreate|preserve|interactive]::
        When true, rebase the current branch on top of the upstream
        branch after fetching. If there is a remote-tracking branch
        corresponding to the upstream branch and the upstream branch
        was rebased since last fetched, the rebase uses that information
        to avoid rebasing non-local changes.
 +
+When set to recreate, rebase with the `--recreate-merges` option passed
+to `git rebase` so that locally created merge commits will not be flattened.
++
 When set to preserve, rebase with the `--preserve-merges` option passed
 to `git rebase` so that locally created merge commits will not be flattened.
 +
index 3277ca143273e01f5f4973ed351c8a5cb4b8e0fa..2b85416f969aee96bf42a7233549a993f1974798 100644 (file)
@@ -378,6 +378,17 @@ The commit list format can be changed by setting the configuration option
 rebase.instructionFormat.  A customized instruction format will automatically
 have the long commit hash prepended to the format.
 
+--recreate-merges[=(rebase-cousins|no-rebase-cousins)]::
+       Recreate merge commits instead of flattening the history by replaying
+       merges. Merge conflict resolutions or manual amendments to merge
+       commits are not recreated automatically, but have to be recreated
+       manually.
++
+By default, or when `no-rebase-cousins` was specified, commits which do not
+have `<upstream>` as direct ancestor keep their original branch point.
+If the `rebase-cousins` mode is turned on, such commits are rebased onto
+`<upstream>` (or `<onto>`, if specified).
+
 -p::
 --preserve-merges::
        Recreate merge commits instead of flattening the history by replaying
@@ -780,7 +791,8 @@ BUGS
 The todo list presented by `--preserve-merges --interactive` does not
 represent the topology of the revision graph.  Editing commits and
 rewording their commit messages should work fine, but attempts to
-reorder commits tend to produce counterintuitive results.
+reorder commits tend to produce counterintuitive results. Use
+--recreate-merges for a more faithful representation.
 
 For example, an attempt to rearrange
 ------------
index 8060ea35c5f932c1e4328f000a501ff399418f27..71ef97ba9b22aad996049bd0c1500a835a433350 100644 (file)
@@ -84,6 +84,11 @@ See the CONFIGURATION section for `sendemail.multiEdit`.
        the value of GIT_AUTHOR_IDENT, or GIT_COMMITTER_IDENT if that is not
        set, as returned by "git var -l".
 
+--reply-to=<address>::
+       Specify the address where replies from recipients should go to.
+       Use this if replies to messages should go to another address than what
+       is specified with the --from parameter.
+
 --in-reply-to=<identifier>::
        Make the first mail (or all the mails with `--no-thread`) appear as a
        reply to the given Message-Id, which avoids breaking threads to
index 8163b5796b192ee5f5c0cb13fc85bdeff8202e64..4767860e72f46d4e4df883f2fdbb4a46eb8e8eda 100644 (file)
@@ -849,6 +849,9 @@ Report bugs to the Git mailing list <git@vger.kernel.org> where the
 development and maintenance is primarily done.  You do not have to be
 subscribed to the list to send a message there.
 
+Issues which are security relevant should be disclosed privately to
+the Git Security mailing list <git-security@googlegroups.com>.
+
 SEE ALSO
 --------
 linkgit:gittutorial[7], linkgit:gittutorial-2[7],
diff --git a/INSTALL b/INSTALL
index 808e07b6599476afc8a0b3a385e73df8b7f9c63d..60e515eaf7432e77d8db8837b7bb95e105ca2126 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -88,9 +88,9 @@ Issues of note:
        export GIT_EXEC_PATH PATH GITPERLLIB
 
  - By default (unless NO_PERL is provided) Git will ship various perl
-   scripts & libraries it needs. However, for simplicity it doesn't
-   use the ExtUtils::MakeMaker toolchain to decide where to place the
-   perl libraries. Depending on the system this can result in the perl
+   scripts. However, for simplicity it doesn't use the
+   ExtUtils::MakeMaker toolchain to decide where to place the perl
+   libraries. Depending on the system this can result in the perl
    libraries not being where you'd like them if they're expected to be
    used by things other than Git itself.
 
@@ -102,6 +102,11 @@ Issues of note:
    Will result in e.g. perllibdir=/usr/share/perl/5.26.1 on Debian,
    perllibdir=/usr/share/perl5 (which we'd use by default) on CentOS.
 
+ - Unless NO_PERL is provided Git will ship various perl libraries it
+   needs. Distributors of Git will usually want to set
+   NO_PERL_CPAN_FALLBACKS if NO_PERL is not provided to use their own
+   copies of the CPAN modules Git needs.
+
  - Git is reasonably self-sufficient, but does depend on a few external
    programs and libraries.  Git can be used without most of them by adding
    the approriate "NO_<LIBRARY>=YesPlease" to the make command line or
index 449ff71f459f3580f39f1feb6d8fcda0c8e50724..180a768690e9883cd9d6f3cb68ae084cc40b9629 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -296,6 +296,12 @@ all::
 #
 # Define NO_PERL if you do not want Perl scripts or libraries at all.
 #
+# Define NO_PERL_CPAN_FALLBACKS if you do not want to install bundled
+# copies of CPAN modules that serve as a fallback in case the modules
+# are not available on the system. This option is intended for
+# distributions that want to use their packaged versions of Perl
+# modules, instead of the fallbacks shipped with Git.
+#
 # Define PYTHON_PATH to the path of your Python binary (often /usr/bin/python
 # but /usr/bin/python2.7 on some platforms).
 #
@@ -2304,14 +2310,22 @@ po/build/locale/%/LC_MESSAGES/git.mo: po/%.po
 
 LIB_PERL := $(wildcard perl/Git.pm perl/Git/*.pm perl/Git/*/*.pm perl/Git/*/*/*.pm)
 LIB_PERL_GEN := $(patsubst perl/%.pm,perl/build/lib/%.pm,$(LIB_PERL))
+LIB_CPAN := $(wildcard perl/FromCPAN/*.pm perl/FromCPAN/*/*.pm)
+LIB_CPAN_GEN := $(patsubst perl/%.pm,perl/build/lib/%.pm,$(LIB_CPAN))
 
 ifndef NO_PERL
 all:: $(LIB_PERL_GEN)
+ifndef NO_PERL_CPAN_FALLBACKS
+all:: $(LIB_CPAN_GEN)
+endif
+NO_PERL_CPAN_FALLBACKS_SQ = $(subst ','\'',$(NO_PERL_CPAN_FALLBACKS))
 endif
 
 perl/build/lib/%.pm: perl/%.pm
        $(QUIET_GEN)mkdir -p $(dir $@) && \
-       sed -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' < $< > $@
+       sed -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \
+           -e 's|@@NO_PERL_CPAN_FALLBACKS@@|$(NO_PERL_CPAN_FALLBACKS_SQ)|g' \
+       < $< > $@
 
 perl/build/man/man3/Git.3pm: perl/Git.pm
        $(QUIET_GEN)mkdir -p $(dir $@) && \
index ac7c1c327766c6a729ab0377fd71c2f8e9042d00..9ef7fb02d56aac94d104b50aed7d7dfda09cfc98 100644 (file)
@@ -534,10 +534,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        unplug_bulk_checkin();
 
 finish:
-       if (active_cache_changed) {
-               if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
-                       die(_("Unable to write new index file"));
-       }
+       if (write_locked_index(&the_index, &lock_file,
+                              COMMIT_LOCK | SKIP_IF_UNCHANGED))
+               die(_("Unable to write new index file"));
 
        UNLEAK(pathspec);
        UNLEAK(dir);
index 092077c3eef7a81b3d814cee951cee98e37eac02..37fcb55ab0a03a5fdabaca1913bc700201fd8e10 100644 (file)
@@ -389,13 +389,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
                if (active_cache_changed
                    || !cache_tree_fully_valid(active_cache_tree))
                        update_main_cache_tree(WRITE_TREE_SILENT);
-               if (active_cache_changed) {
-                       if (write_locked_index(&the_index, &index_lock,
-                                              COMMIT_LOCK))
-                               die(_("unable to write new_index file"));
-               } else {
-                       rollback_lock_file(&index_lock);
-               }
+               if (write_locked_index(&the_index, &index_lock,
+                                      COMMIT_LOCK | SKIP_IF_UNCHANGED))
+                       die(_("unable to write new_index file"));
                commit_style = COMMIT_AS_IS;
                ret = get_index_file();
                goto out;
index ab5f95476e6c726798fd84b4a6300577bf0fafba..01169dd628b24a7b5502550a6342ab73cb8154c5 100644 (file)
@@ -48,6 +48,13 @@ static int show_origin;
 #define ACTION_GET_COLORBOOL (1<<14)
 #define ACTION_GET_URLMATCH (1<<15)
 
+/*
+ * The actions "ACTION_LIST | ACTION_GET_*" which may produce more than
+ * one line of output and which should therefore be paged.
+ */
+#define PAGING_ACTIONS (ACTION_LIST | ACTION_GET_ALL | \
+                       ACTION_GET_REGEXP | ACTION_GET_URLMATCH)
+
 #define TYPE_BOOL (1<<0)
 #define TYPE_INT (1<<1)
 #define TYPE_BOOL_OR_INT (1<<2)
@@ -594,6 +601,9 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                usage_with_options(builtin_config_usage, builtin_config_options);
        }
 
+       if (actions & PAGING_ACTIONS)
+               setup_auto_pager("config", 1);
+
        if (actions == ACTION_LIST) {
                check_argc(argc, 0, 0);
                if (config_with_options(show_all_config, NULL,
index e8d9d4383ed69bd5783b0092a2ee221fc29ce439..ee050a47f34d7394d048f955baabb37a9e716ef8 100644 (file)
@@ -652,10 +652,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
 
        hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
        refresh_cache(REFRESH_QUIET);
-       if (active_cache_changed &&
-           write_locked_index(&the_index, &lock, COMMIT_LOCK))
+       if (write_locked_index(&the_index, &lock,
+                              COMMIT_LOCK | SKIP_IF_UNCHANGED))
                return error(_("Unable to write index."));
-       rollback_lock_file(&lock);
 
        if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) {
                int clean, x;
@@ -692,10 +691,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                                remoteheads->item, reversed, &result);
                if (clean < 0)
                        exit(128);
-               if (active_cache_changed &&
-                   write_locked_index(&the_index, &lock, COMMIT_LOCK))
+               if (write_locked_index(&the_index, &lock,
+                                      COMMIT_LOCK | SKIP_IF_UNCHANGED))
                        die (_("unable to write %s"), get_index_file());
-               rollback_lock_file(&lock);
                return clean ? 0 : 1;
        } else {
                return try_merge_command(strategy, xopts_nr, xopts,
@@ -811,10 +809,9 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
 
        hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
        refresh_cache(REFRESH_QUIET);
-       if (active_cache_changed &&
-           write_locked_index(&the_index, &lock, COMMIT_LOCK))
+       if (write_locked_index(&the_index, &lock,
+                              COMMIT_LOCK | SKIP_IF_UNCHANGED))
                return error(_("Unable to write index."));
-       rollback_lock_file(&lock);
 
        write_tree_trivial(&result_tree);
        printf(_("Wonderful.\n"));
index 8eceb310aae5f8007475f571a0146832eab0be49..6d141f7a532c08e52f1f5f82330d046c60073f93 100644 (file)
@@ -293,8 +293,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
        if (gitmodules_modified)
                stage_updated_gitmodules(&the_index);
 
-       if (active_cache_changed &&
-           write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
+       if (write_locked_index(&the_index, &lock_file,
+                              COMMIT_LOCK | SKIP_IF_UNCHANGED))
                die(_("Unable to write new index file"));
 
        return 0;
index e32d6cd5b4c999bc45b961c1387af066c72a823a..3d1cc60eed6c21d3c3ba5bb5c138a1923539de4b 100644 (file)
@@ -27,14 +27,16 @@ enum rebase_type {
        REBASE_FALSE = 0,
        REBASE_TRUE,
        REBASE_PRESERVE,
+       REBASE_RECREATE,
        REBASE_INTERACTIVE
 };
 
 /**
  * Parses the value of --rebase. If value is a false value, returns
  * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is
- * "preserve", returns REBASE_PRESERVE. If value is a invalid value, dies with
- * a fatal error if fatal is true, otherwise returns REBASE_INVALID.
+ * "recreate", returns REBASE_RECREATE. If value is "preserve", returns
+ * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if
+ * fatal is true, otherwise returns REBASE_INVALID.
  */
 static enum rebase_type parse_config_rebase(const char *key, const char *value,
                int fatal)
@@ -47,6 +49,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,
                return REBASE_TRUE;
        else if (!strcmp(value, "preserve"))
                return REBASE_PRESERVE;
+       else if (!strcmp(value, "recreate"))
+               return REBASE_RECREATE;
        else if (!strcmp(value, "interactive"))
                return REBASE_INTERACTIVE;
 
@@ -130,7 +134,7 @@ static struct option pull_options[] = {
        /* Options passed to git-merge or git-rebase */
        OPT_GROUP(N_("Options related to merging")),
        { OPTION_CALLBACK, 'r', "rebase", &opt_rebase,
-         "false|true|preserve|interactive",
+         "false|true|recreate|preserve|interactive",
          N_("incorporate changes by rebasing rather than merging"),
          PARSE_OPT_OPTARG, parse_opt_rebase },
        OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
@@ -800,7 +804,9 @@ static int run_rebase(const struct object_id *curr_head,
        argv_push_verbosity(&args);
 
        /* Options passed to git-rebase */
-       if (opt_rebase == REBASE_PRESERVE)
+       if (opt_rebase == REBASE_RECREATE)
+               argv_array_push(&args, "--recreate-merges");
+       else if (opt_rebase == REBASE_PRESERVE)
                argv_array_push(&args, "--preserve-merges");
        else if (opt_rebase == REBASE_INTERACTIVE)
                argv_array_push(&args, "--interactive");
index ad074705bb51d1de4221b3c5dfaa7229903c0ef0..5d1f12de57bc7bf50960047c4a0a81a4798d0498 100644 (file)
@@ -12,8 +12,8 @@ static const char * const builtin_rebase_helper_usage[] = {
 int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 {
        struct replay_opts opts = REPLAY_OPTS_INIT;
-       unsigned flags = 0, keep_empty = 0;
-       int abbreviate_commands = 0;
+       unsigned flags = 0, keep_empty = 0, recreate_merges = 0;
+       int abbreviate_commands = 0, rebase_cousins = -1;
        enum {
                CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
                CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
@@ -24,6 +24,9 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")),
                OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
                        N_("allow commits with empty messages")),
+               OPT_BOOL(0, "recreate-merges", &recreate_merges, N_("recreate merge commits")),
+               OPT_BOOL(0, "rebase-cousins", &rebase_cousins,
+                        N_("keep original branch points of cousins")),
                OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
                                CONTINUE),
                OPT_CMDMODE(0, "abort", &command, N_("abort rebase"),
@@ -57,8 +60,14 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 
        flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
        flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
+       flags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;
+       flags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
        flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
 
+       if (rebase_cousins >= 0 && !recreate_merges)
+               warning(_("--[no-]rebase-cousins has no effect without "
+                         "--recreate-merges"));
+
        if (command == CONTINUE && argc == 1)
                return !!sequencer_continue(&opts);
        if (command == ABORT && argc == 1)
index 805ffc05cdb80e4a69de4134e757f9c71e8033dc..210890c8a8ea77d31e70e14f0551f338d6828e50 100644 (file)
@@ -306,6 +306,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)
                                info->rebase = v;
                        else if (!strcmp(value, "preserve"))
                                info->rebase = NORMAL_REBASE;
+                       else if (!strcmp(value, "recreate"))
+                               info->rebase = NORMAL_REBASE;
                        else if (!strcmp(value, "interactive"))
                                info->rebase = INTERACTIVE_REBASE;
                }
index a818efe230a0667b9e3e5f03efbb11b1db1df273..4447bb4d0faf8c34f3fc96361a651eaad396d6d4 100644 (file)
@@ -385,10 +385,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
                        stage_updated_gitmodules(&the_index);
        }
 
-       if (active_cache_changed) {
-               if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
-                       die(_("Unable to write new index file"));
-       }
+       if (write_locked_index(&the_index, &lock_file,
+                              COMMIT_LOCK | SKIP_IF_UNCHANGED))
+               die(_("Unable to write new index file"));
 
        return 0;
 }
diff --git a/cache.h b/cache.h
index d06932ed0bd049c8d41906da3508725c8978aa1c..4859e2be1a2566687336135bff144155dbcb4551 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -459,7 +459,7 @@ static inline enum object_type object_type(unsigned int mode)
  */
 extern const char * const local_repo_env[];
 
-extern void setup_git_env(void);
+extern void setup_git_env(const char *git_dir);
 
 /*
  * Returns true iff we have a configured git repository (either via
@@ -599,6 +599,7 @@ extern int read_index_unmerged(struct index_state *);
 
 /* For use with `write_locked_index()`. */
 #define COMMIT_LOCK            (1 << 0)
+#define SKIP_IF_UNCHANGED      (1 << 1)
 
 /*
  * Write the index while holding an already-taken lock. Close the lock,
@@ -615,6 +616,9 @@ extern int read_index_unmerged(struct index_state *);
  * With `COMMIT_LOCK`, the lock is always committed or rolled back.
  * Without it, the lock is closed, but neither committed nor rolled
  * back.
+ *
+ * If `SKIP_IF_UNCHANGED` is given and the index is unchanged, nothing
+ * is written (and the lock is rolled back if `COMMIT_LOCK` is given).
  */
 extern int write_locked_index(struct index_state *, struct lock_file *lock, unsigned flags);
 
@@ -1773,6 +1777,8 @@ struct object_info {
 #define OBJECT_INFO_SKIP_CACHED 4
 /* Do not retry packed storage after checking packed and loose storage */
 #define OBJECT_INFO_QUICK 8
+/* Do not check loose object */
+#define OBJECT_INFO_IGNORE_LOOSE 16
 extern int sha1_object_info_extended(const unsigned char *, struct object_info *, unsigned flags);
 
 /*
index 6a689007e7ce3fe08f148e8b82c0a1c618c513a5..7d716d5a5491ba6b3a596f2128c4f08253d1d364 100644 (file)
@@ -34,6 +34,8 @@ int main(int argc, const char **argv)
 
        git_setup_gettext();
 
+       initialize_the_repository();
+
        attr_start();
 
        git_extract_argv0_path(argv[0]);
index f1981aa75df2128f93a3d6b5b3434ab6bda86576..d345d1fe0c3957c2744989c6758c79cffe0f52cb 100644 (file)
@@ -1945,7 +1945,7 @@ _git_rebase ()
        --*)
                __gitcomp "
                        --onto --merge --strategy --interactive
-                       --preserve-merges --stat --no-stat
+                       --recreate-merges --preserve-merges --stat --no-stat
                        --committer-date-is-author-date --ignore-date
                        --ignore-whitespace --whitespace=
                        --autosquash --no-autosquash
@@ -2019,7 +2019,7 @@ _git_send_email ()
                        --compose --confirm= --dry-run --envelope-sender
                        --from --identity
                        --in-reply-to --no-chain-reply-to --no-signed-off-by-cc
-                       --no-suppress-from --no-thread --quiet
+                       --no-suppress-from --no-thread --quiet --reply-to
                        --signed-off-by-cc --smtp-pass --smtp-server
                        --smtp-server-port --smtp-encryption= --smtp-user
                        --subject --suppress-cc= --suppress-from --thread --to
@@ -2116,7 +2116,7 @@ _git_config ()
                return
                ;;
        branch.*.rebase)
-               __gitcomp "false true preserve interactive"
+               __gitcomp "false true recreate preserve interactive"
                return
                ;;
        remote.pushdefault)
diff --git a/contrib/emacs/.gitignore b/contrib/emacs/.gitignore
deleted file mode 100644 (file)
index c531d98..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.elc
diff --git a/contrib/emacs/Makefile b/contrib/emacs/Makefile
deleted file mode 100644 (file)
index 24d9312..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-## Build and install stuff
-
-EMACS = emacs
-
-ELC = git.elc git-blame.elc
-INSTALL ?= install
-INSTALL_ELC = $(INSTALL) -m 644
-prefix ?= $(HOME)
-emacsdir = $(prefix)/share/emacs/site-lisp
-RM ?= rm -f
-
-all: $(ELC)
-
-install: all
-       $(INSTALL) -d $(DESTDIR)$(emacsdir)
-       $(INSTALL_ELC) $(ELC:.elc=.el) $(ELC) $(DESTDIR)$(emacsdir)
-
-%.elc: %.el
-       $(EMACS) -batch -f batch-byte-compile $<
-
-clean:; $(RM) $(ELC)
index 82368bdbfff199465ff8e8cbea49d99e5485e1d7..977a16f1e339faca937dfd1a60bb10395bfd59c4 100644 (file)
@@ -1,30 +1,24 @@
-This directory contains various modules for Emacs support.
+This directory used to contain various modules for Emacs support.
 
-To make the modules available to Emacs, you should add this directory
-to your load-path, and then require the modules you want. This can be
-done by adding to your .emacs something like this:
+These were added shortly after Git was first released. Since then
+Emacs's own support for Git got better than what was offered by these
+modes. There are also popular 3rd-party Git modes such as Magit which
+offer replacements for these.
 
-  (add-to-list 'load-path ".../git/contrib/emacs")
-  (require 'git)
-  (require 'git-blame)
-
-
-The following modules are available:
+The following modules were available, and can be dug up from the Git
+history:
 
 * git.el:
 
-  Status manager that displays the state of all the files of the
-  project, and provides easy access to the most frequently used git
-  commands. The user interface is as far as possible compatible with
-  the pcl-cvs mode. It can be started with `M-x git-status'.
+  Wrapper for "git status" that provided access to other git commands.
+
+  Modern alternatives to this include Magit, and VC mode that ships
+  with Emacs.
 
 * git-blame.el:
 
-  Emacs implementation of incremental git-blame.  When you turn it on
-  while viewing a file, the editor buffer will be updated by setting
-  the background of individual lines to a color that reflects which
-  commit it comes from.  And when you move around the buffer, a
-  one-line summary will be shown in the echo area.
+  A wrapper for "git blame" written before Emacs's own vc-annotate
+  mode learned to invoke git-blame, which can be done via C-x v g.
 
 * vc-git.el:
 
diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el
deleted file mode 100644 (file)
index 510e0f7..0000000
+++ /dev/null
@@ -1,483 +0,0 @@
-;;; git-blame.el --- Minor mode for incremental blame for Git  -*- coding: utf-8 -*-
-;;
-;; Copyright (C) 2007  David KÃ¥gedal
-;;
-;; Authors:    David KÃ¥gedal <davidk@lysator.liu.se>
-;; Created:    31 Jan 2007
-;; Message-ID: <87iren2vqx.fsf@morpheus.local>
-;; License:    GPL
-;; Keywords:   git, version control, release management
-;;
-;; Compatibility: Emacs21, Emacs22 and EmacsCVS
-;;                Git 1.5 and up
-
-;; This file is *NOT* part of GNU Emacs.
-;; This file is distributed under the same terms as GNU Emacs.
-
-;; This program is free software; you can redistribute it and/or
-;; modify it under the terms of the GNU General Public License as
-;; published by the Free Software Foundation; either version 2 of
-;; the License, or (at your option) any later version.
-
-;; This program is distributed in the hope that it will be
-;; useful, but WITHOUT ANY WARRANTY; without even the implied
-;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-;; PURPOSE.  See the GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public
-;; License along with this program; if not, see
-;; <http://www.gnu.org/licenses/>.
-
-;; http://www.fsf.org/copyleft/gpl.html
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;;
-;;; Commentary:
-;;
-;; Here is an Emacs implementation of incremental git-blame.  When you
-;; turn it on while viewing a file, the editor buffer will be updated by
-;; setting the background of individual lines to a color that reflects
-;; which commit it comes from.  And when you move around the buffer, a
-;; one-line summary will be shown in the echo area.
-
-;;; Installation:
-;;
-;; To use this package, put it somewhere in `load-path' (or add
-;; directory with git-blame.el to `load-path'), and add the following
-;; line to your .emacs:
-;;
-;;    (require 'git-blame)
-;;
-;; If you do not want to load this package before it is necessary, you
-;; can make use of the `autoload' feature, e.g. by adding to your .emacs
-;; the following lines
-;;
-;;    (autoload 'git-blame-mode "git-blame"
-;;              "Minor mode for incremental blame for Git." t)
-;;
-;; Then first use of `M-x git-blame-mode' would load the package.
-
-;;; Compatibility:
-;;
-;; It requires GNU Emacs 21 or later and Git 1.5.0 and up
-;;
-;; If you'are using Emacs 20, try changing this:
-;;
-;;            (overlay-put ovl 'face (list :background
-;;                                         (cdr (assq 'color (cddddr info)))))
-;;
-;; to
-;;
-;;            (overlay-put ovl 'face (cons 'background-color
-;;                                         (cdr (assq 'color (cddddr info)))))
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;;
-;;; Code:
-
-(eval-when-compile (require 'cl))                            ; to use `push', `pop'
-(require 'format-spec)
-
-(defface git-blame-prefix-face
-  '((((background dark)) (:foreground "gray"
-                          :background "black"))
-    (((background light)) (:foreground "gray"
-                           :background "white"))
-    (t (:weight bold)))
-  "The face used for the hash prefix."
-  :group 'git-blame)
-
-(defgroup git-blame nil
-  "A minor mode showing Git blame information."
-  :group 'git
-  :link '(function-link git-blame-mode))
-
-
-(defcustom git-blame-use-colors t
-  "Use colors to indicate commits in `git-blame-mode'."
-  :type 'boolean
-  :group 'git-blame)
-
-(defcustom git-blame-prefix-format
-  "%h %20A:"
-  "The format of the prefix added to each line in `git-blame'
-mode. The format is passed to `format-spec' with the following format keys:
-
-  %h - the abbreviated hash
-  %H - the full hash
-  %a - the author name
-  %A - the author email
-  %c - the committer name
-  %C - the committer email
-  %s - the commit summary
-"
-  :group 'git-blame)
-
-(defcustom git-blame-mouseover-format
-  "%h %a %A: %s"
-  "The format of the description shown when pointing at a line in
-`git-blame' mode. The format string is passed to `format-spec'
-with the following format keys:
-
-  %h - the abbreviated hash
-  %H - the full hash
-  %a - the author name
-  %A - the author email
-  %c - the committer name
-  %C - the committer email
-  %s - the commit summary
-"
-  :group 'git-blame)
-
-
-(defun git-blame-color-scale (&rest elements)
-  "Given a list, returns a list of triples formed with each
-elements of the list.
-
-a b => bbb bba bab baa abb aba aaa aab"
-  (let (result)
-    (dolist (a elements)
-      (dolist (b elements)
-        (dolist (c elements)
-          (setq result (cons (format "#%s%s%s" a b c) result)))))
-    result))
-
-;; (git-blame-color-scale "0c" "04" "24" "1c" "2c" "34" "14" "3c") =>
-;; ("#3c3c3c" "#3c3c14" "#3c3c34" "#3c3c2c" "#3c3c1c" "#3c3c24"
-;; "#3c3c04" "#3c3c0c" "#3c143c" "#3c1414" "#3c1434" "#3c142c" ...)
-
-(defmacro git-blame-random-pop (l)
-  "Select a random element from L and returns it. Also remove
-selected element from l."
-  ;; only works on lists with unique elements
-  `(let ((e (elt ,l (random (length ,l)))))
-     (setq ,l (remove e ,l))
-     e))
-
-(defvar git-blame-log-oneline-format
-  "format:[%cr] %cn: %s"
-  "*Formatting option used for describing current line in the minibuffer.
-
-This option is used to pass to git log --pretty= command-line option,
-and describe which commit the current line was made.")
-
-(defvar git-blame-dark-colors
-  (git-blame-color-scale "0c" "04" "24" "1c" "2c" "34" "14" "3c")
-  "*List of colors (format #RGB) to use in a dark environment.
-
-To check out the list, evaluate (list-colors-display git-blame-dark-colors).")
-
-(defvar git-blame-light-colors
-  (git-blame-color-scale "c4" "d4" "cc" "dc" "f4" "e4" "fc" "ec")
-  "*List of colors (format #RGB) to use in a light environment.
-
-To check out the list, evaluate (list-colors-display git-blame-light-colors).")
-
-(defvar git-blame-colors '()
-  "Colors used by git-blame. The list is built once when activating git-blame
-minor mode.")
-
-(defvar git-blame-ancient-color "dark green"
-  "*Color to be used for ancient commit.")
-
-(defvar git-blame-autoupdate t
-  "*Automatically update the blame display while editing")
-
-(defvar git-blame-proc nil
-  "The running git-blame process")
-(make-variable-buffer-local 'git-blame-proc)
-
-(defvar git-blame-overlays nil
-  "The git-blame overlays used in the current buffer.")
-(make-variable-buffer-local 'git-blame-overlays)
-
-(defvar git-blame-cache nil
-  "A cache of git-blame information for the current buffer")
-(make-variable-buffer-local 'git-blame-cache)
-
-(defvar git-blame-idle-timer nil
-  "An idle timer that updates the blame")
-(make-variable-buffer-local 'git-blame-cache)
-
-(defvar git-blame-update-queue nil
-  "A queue of update requests")
-(make-variable-buffer-local 'git-blame-update-queue)
-
-;; FIXME: docstrings
-(defvar git-blame-file nil)
-(defvar git-blame-current nil)
-
-(defvar git-blame-mode nil)
-(make-variable-buffer-local 'git-blame-mode)
-
-(defvar git-blame-mode-line-string " blame"
-  "String to display on the mode line when git-blame is active.")
-
-(or (assq 'git-blame-mode minor-mode-alist)
-    (setq minor-mode-alist
-         (cons '(git-blame-mode git-blame-mode-line-string) minor-mode-alist)))
-
-;;;###autoload
-(defun git-blame-mode (&optional arg)
-  "Toggle minor mode for displaying Git blame
-
-With prefix ARG, turn the mode on if ARG is positive."
-  (interactive "P")
-  (cond
-   ((null arg)
-    (if git-blame-mode (git-blame-mode-off) (git-blame-mode-on)))
-   ((> (prefix-numeric-value arg) 0) (git-blame-mode-on))
-   (t (git-blame-mode-off))))
-
-(defun git-blame-mode-on ()
-  "Turn on git-blame mode.
-
-See also function `git-blame-mode'."
-  (make-local-variable 'git-blame-colors)
-  (if git-blame-autoupdate
-      (add-hook 'after-change-functions 'git-blame-after-change nil t)
-    (remove-hook 'after-change-functions 'git-blame-after-change t))
-  (git-blame-cleanup)
-  (let ((bgmode (cdr (assoc 'background-mode (frame-parameters)))))
-    (if (eq bgmode 'dark)
-       (setq git-blame-colors git-blame-dark-colors)
-      (setq git-blame-colors git-blame-light-colors)))
-  (setq git-blame-cache (make-hash-table :test 'equal))
-  (setq git-blame-mode t)
-  (git-blame-run))
-
-(defun git-blame-mode-off ()
-  "Turn off git-blame mode.
-
-See also function `git-blame-mode'."
-  (git-blame-cleanup)
-  (if git-blame-idle-timer (cancel-timer git-blame-idle-timer))
-  (setq git-blame-mode nil))
-
-;;;###autoload
-(defun git-reblame ()
-  "Recalculate all blame information in the current buffer"
-  (interactive)
-  (unless git-blame-mode
-    (error "Git-blame is not active"))
-
-  (git-blame-cleanup)
-  (git-blame-run))
-
-(defun git-blame-run (&optional startline endline)
-  (if git-blame-proc
-      ;; Should maybe queue up a new run here
-      (message "Already running git blame")
-    (let ((display-buf (current-buffer))
-          (blame-buf (get-buffer-create
-                      (concat " git blame for " (buffer-name))))
-          (args '("--incremental" "--contents" "-")))
-      (if startline
-          (setq args (append args
-                             (list "-L" (format "%d,%d" startline endline)))))
-      (setq args (append args
-                         (list (file-name-nondirectory buffer-file-name))))
-      (setq git-blame-proc
-            (apply 'start-process
-                   "git-blame" blame-buf
-                   "git" "blame"
-                   args))
-      (with-current-buffer blame-buf
-        (erase-buffer)
-        (make-local-variable 'git-blame-file)
-        (make-local-variable 'git-blame-current)
-        (setq git-blame-file display-buf)
-        (setq git-blame-current nil))
-      (set-process-filter git-blame-proc 'git-blame-filter)
-      (set-process-sentinel git-blame-proc 'git-blame-sentinel)
-      (process-send-region git-blame-proc (point-min) (point-max))
-      (process-send-eof git-blame-proc))))
-
-(defun remove-git-blame-text-properties (start end)
-  (let ((modified (buffer-modified-p))
-        (inhibit-read-only t))
-    (remove-text-properties start end '(point-entered nil))
-    (set-buffer-modified-p modified)))
-
-(defun git-blame-cleanup ()
-  "Remove all blame properties"
-    (mapc 'delete-overlay git-blame-overlays)
-    (setq git-blame-overlays nil)
-    (remove-git-blame-text-properties (point-min) (point-max)))
-
-(defun git-blame-update-region (start end)
-  "Rerun blame to get updates between START and END"
-  (let ((overlays (overlays-in start end)))
-    (while overlays
-      (let ((overlay (pop overlays)))
-        (if (< (overlay-start overlay) start)
-            (setq start (overlay-start overlay)))
-        (if (> (overlay-end overlay) end)
-            (setq end (overlay-end overlay)))
-        (setq git-blame-overlays (delete overlay git-blame-overlays))
-        (delete-overlay overlay))))
-  (remove-git-blame-text-properties start end)
-  ;; We can be sure that start and end are at line breaks
-  (git-blame-run (1+ (count-lines (point-min) start))
-                 (count-lines (point-min) end)))
-
-(defun git-blame-sentinel (proc status)
-  (with-current-buffer (process-buffer proc)
-    (with-current-buffer git-blame-file
-      (setq git-blame-proc nil)
-      (if git-blame-update-queue
-          (git-blame-delayed-update))))
-  ;;(kill-buffer (process-buffer proc))
-  ;;(message "git blame finished")
-  )
-
-(defvar in-blame-filter nil)
-
-(defun git-blame-filter (proc str)
-  (with-current-buffer (process-buffer proc)
-    (save-excursion
-      (goto-char (process-mark proc))
-      (insert-before-markers str)
-      (goto-char (point-min))
-      (unless in-blame-filter
-        (let ((more t)
-              (in-blame-filter t))
-          (while more
-            (setq more (git-blame-parse))))))))
-
-(defun git-blame-parse ()
-  (cond ((looking-at "\\([0-9a-f]\\{40\\}\\) \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\)\n")
-         (let ((hash (match-string 1))
-               (src-line (string-to-number (match-string 2)))
-               (res-line (string-to-number (match-string 3)))
-               (num-lines (string-to-number (match-string 4))))
-           (delete-region (point) (match-end 0))
-           (setq git-blame-current (list (git-blame-new-commit hash)
-                                         src-line res-line num-lines)))
-         t)
-        ((looking-at "\\([a-z-]+\\) \\(.+\\)\n")
-         (let ((key (match-string 1))
-               (value (match-string 2)))
-           (delete-region (point) (match-end 0))
-           (git-blame-add-info (car git-blame-current) key value)
-           (when (string= key "filename")
-             (git-blame-create-overlay (car git-blame-current)
-                                       (caddr git-blame-current)
-                                       (cadddr git-blame-current))
-             (setq git-blame-current nil)))
-         t)
-        (t
-         nil)))
-
-(defun git-blame-new-commit (hash)
-  (with-current-buffer git-blame-file
-    (or (gethash hash git-blame-cache)
-        ;; Assign a random color to each new commit info
-        ;; Take care not to select the same color multiple times
-        (let* ((color (if git-blame-colors
-                          (git-blame-random-pop git-blame-colors)
-                        git-blame-ancient-color))
-               (info `(,hash (color . ,color))))
-          (puthash hash info git-blame-cache)
-          info))))
-
-(defun git-blame-create-overlay (info start-line num-lines)
-  (with-current-buffer git-blame-file
-    (save-excursion
-      (let ((inhibit-point-motion-hooks t)
-            (inhibit-modification-hooks t))
-        (goto-char (point-min))
-        (forward-line (1- start-line))
-        (let* ((start (point))
-               (end (progn (forward-line num-lines) (point)))
-               (ovl (make-overlay start end))
-               (hash (car info))
-               (spec `((?h . ,(substring hash 0 6))
-                       (?H . ,hash)
-                       (?a . ,(git-blame-get-info info 'author))
-                       (?A . ,(git-blame-get-info info 'author-mail))
-                       (?c . ,(git-blame-get-info info 'committer))
-                       (?C . ,(git-blame-get-info info 'committer-mail))
-                       (?s . ,(git-blame-get-info info 'summary)))))
-          (push ovl git-blame-overlays)
-          (overlay-put ovl 'git-blame info)
-          (overlay-put ovl 'help-echo
-                       (format-spec git-blame-mouseover-format spec))
-          (if git-blame-use-colors
-              (overlay-put ovl 'face (list :background
-                                           (cdr (assq 'color (cdr info))))))
-          (overlay-put ovl 'line-prefix
-                       (propertize (format-spec git-blame-prefix-format spec)
-                                   'face 'git-blame-prefix-face)))))))
-
-(defun git-blame-add-info (info key value)
-  (nconc info (list (cons (intern key) value))))
-
-(defun git-blame-get-info (info key)
-  (cdr (assq key (cdr info))))
-
-(defun git-blame-current-commit ()
-  (let ((info (get-char-property (point) 'git-blame)))
-    (if info
-        (car info)
-      (error "No commit info"))))
-
-(defun git-describe-commit (hash)
-  (with-temp-buffer
-    (call-process "git" nil t nil
-                  "log" "-1"
-                 (concat "--pretty=" git-blame-log-oneline-format)
-                  hash)
-    (buffer-substring (point-min) (point-max))))
-
-(defvar git-blame-last-identification nil)
-(make-variable-buffer-local 'git-blame-last-identification)
-(defun git-blame-identify (&optional hash)
-  (interactive)
-  (let ((info (gethash (or hash (git-blame-current-commit)) git-blame-cache)))
-    (when (and info (not (eq info git-blame-last-identification)))
-      (message "%s" (nth 4 info))
-      (setq git-blame-last-identification info))))
-
-;; (defun git-blame-after-save ()
-;;   (when git-blame-mode
-;;     (git-blame-cleanup)
-;;     (git-blame-run)))
-;; (add-hook 'after-save-hook 'git-blame-after-save)
-
-(defun git-blame-after-change (start end length)
-  (when git-blame-mode
-    (git-blame-enq-update start end)))
-
-(defvar git-blame-last-update nil)
-(make-variable-buffer-local 'git-blame-last-update)
-(defun git-blame-enq-update (start end)
-  "Mark the region between START and END as needing blame update"
-  ;; Try to be smart and avoid multiple callouts for sequential
-  ;; editing
-  (cond ((and git-blame-last-update
-              (= start (cdr git-blame-last-update)))
-         (setcdr git-blame-last-update end))
-        ((and git-blame-last-update
-              (= end (car git-blame-last-update)))
-         (setcar git-blame-last-update start))
-        (t
-         (setq git-blame-last-update (cons start end))
-         (setq git-blame-update-queue (nconc git-blame-update-queue
-                                             (list git-blame-last-update)))))
-  (unless (or git-blame-proc git-blame-idle-timer)
-    (setq git-blame-idle-timer
-          (run-with-idle-timer 0.5 nil 'git-blame-delayed-update))))
-
-(defun git-blame-delayed-update ()
-  (setq git-blame-idle-timer nil)
-  (if git-blame-update-queue
-      (let ((first (pop git-blame-update-queue))
-            (inhibit-point-motion-hooks t))
-        (git-blame-update-region (car first) (cdr first)))))
-
-(provide 'git-blame)
-
-;;; git-blame.el ends here
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
deleted file mode 100644 (file)
index 97919f2..0000000
+++ /dev/null
@@ -1,1704 +0,0 @@
-;;; git.el --- A user interface for git
-
-;; Copyright (C) 2005, 2006, 2007, 2008, 2009 Alexandre Julliard <julliard@winehq.org>
-
-;; Version: 1.0
-
-;; This program is free software; you can redistribute it and/or
-;; modify it under the terms of the GNU General Public License as
-;; published by the Free Software Foundation; either version 2 of
-;; the License, or (at your option) any later version.
-;;
-;; This program is distributed in the hope that it will be
-;; useful, but WITHOUT ANY WARRANTY; without even the implied
-;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-;; PURPOSE.  See the GNU General Public License for more details.
-;;
-;; You should have received a copy of the GNU General Public
-;; License along with this program; if not, see
-;; <http://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; This file contains an interface for the git version control
-;; system. It provides easy access to the most frequently used git
-;; commands. The user interface is as far as possible identical to
-;; that of the PCL-CVS mode.
-;;
-;; To install: put this file on the load-path and place the following
-;; in your .emacs file:
-;;
-;;    (require 'git)
-;;
-;; To start: `M-x git-status'
-;;
-;; TODO
-;;  - diff against other branch
-;;  - renaming files from the status buffer
-;;  - creating tags
-;;  - fetch/pull
-;;  - revlist browser
-;;  - git-show-branch browser
-;;
-
-;;; Compatibility:
-;;
-;; This file works on GNU Emacs 21 or later. It may work on older
-;; versions but this is not guaranteed.
-;;
-;; It may work on XEmacs 21, provided that you first install the ewoc
-;; and log-edit packages.
-;;
-
-(eval-when-compile (require 'cl))
-(require 'ewoc)
-(require 'log-edit)
-(require 'easymenu)
-
-
-;;;; Customizations
-;;;; ------------------------------------------------------------
-
-(defgroup git nil
-  "A user interface for the git versioning system."
-  :group 'tools)
-
-(defcustom git-committer-name nil
-  "User name to use for commits.
-The default is to fall back to the repository config,
-then to `add-log-full-name' and then to `user-full-name'."
-  :group 'git
-  :type '(choice (const :tag "Default" nil)
-                 (string :tag "Name")))
-
-(defcustom git-committer-email nil
-  "Email address to use for commits.
-The default is to fall back to the git repository config,
-then to `add-log-mailing-address' and then to `user-mail-address'."
-  :group 'git
-  :type '(choice (const :tag "Default" nil)
-                 (string :tag "Email")))
-
-(defcustom git-commits-coding-system nil
-  "Default coding system for the log message of git commits."
-  :group 'git
-  :type '(choice (const :tag "From repository config" nil)
-                 (coding-system)))
-
-(defcustom git-append-signed-off-by nil
-  "Whether to append a Signed-off-by line to the commit message before editing."
-  :group 'git
-  :type 'boolean)
-
-(defcustom git-reuse-status-buffer t
-  "Whether `git-status' should try to reuse an existing buffer
-if there is already one that displays the same directory."
-  :group 'git
-  :type 'boolean)
-
-(defcustom git-per-dir-ignore-file ".gitignore"
-  "Name of the per-directory ignore file."
-  :group 'git
-  :type 'string)
-
-(defcustom git-show-uptodate nil
-  "Whether to display up-to-date files."
-  :group 'git
-  :type 'boolean)
-
-(defcustom git-show-ignored nil
-  "Whether to display ignored files."
-  :group 'git
-  :type 'boolean)
-
-(defcustom git-show-unknown t
-  "Whether to display unknown files."
-  :group 'git
-  :type 'boolean)
-
-
-(defface git-status-face
-  '((((class color) (background light)) (:foreground "purple"))
-    (((class color) (background dark)) (:foreground "salmon")))
-  "Git mode face used to highlight added and modified files."
-  :group 'git)
-
-(defface git-unmerged-face
-  '((((class color) (background light)) (:foreground "red" :bold t))
-    (((class color) (background dark)) (:foreground "red" :bold t)))
-  "Git mode face used to highlight unmerged files."
-  :group 'git)
-
-(defface git-unknown-face
-  '((((class color) (background light)) (:foreground "goldenrod" :bold t))
-    (((class color) (background dark)) (:foreground "goldenrod" :bold t)))
-  "Git mode face used to highlight unknown files."
-  :group 'git)
-
-(defface git-uptodate-face
-  '((((class color) (background light)) (:foreground "grey60"))
-    (((class color) (background dark)) (:foreground "grey40")))
-  "Git mode face used to highlight up-to-date files."
-  :group 'git)
-
-(defface git-ignored-face
-  '((((class color) (background light)) (:foreground "grey60"))
-    (((class color) (background dark)) (:foreground "grey40")))
-  "Git mode face used to highlight ignored files."
-  :group 'git)
-
-(defface git-mark-face
-  '((((class color) (background light)) (:foreground "red" :bold t))
-    (((class color) (background dark)) (:foreground "tomato" :bold t)))
-  "Git mode face used for the file marks."
-  :group 'git)
-
-(defface git-header-face
-  '((((class color) (background light)) (:foreground "blue"))
-    (((class color) (background dark)) (:foreground "blue")))
-  "Git mode face used for commit headers."
-  :group 'git)
-
-(defface git-separator-face
-  '((((class color) (background light)) (:foreground "brown"))
-    (((class color) (background dark)) (:foreground "brown")))
-  "Git mode face used for commit separator."
-  :group 'git)
-
-(defface git-permission-face
-  '((((class color) (background light)) (:foreground "green" :bold t))
-    (((class color) (background dark)) (:foreground "green" :bold t)))
-  "Git mode face used for permission changes."
-  :group 'git)
-
-
-;;;; Utilities
-;;;; ------------------------------------------------------------
-
-(defconst git-log-msg-separator "--- log message follows this line ---")
-
-(defvar git-log-edit-font-lock-keywords
-  `(("^\\(Author:\\|Date:\\|Merge:\\|Signed-off-by:\\)\\(.*\\)$"
-     (1 font-lock-keyword-face)
-     (2 font-lock-function-name-face))
-    (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$")
-     (1 font-lock-comment-face))))
-
-(defun git-get-env-strings (env)
-  "Build a list of NAME=VALUE strings from a list of environment strings."
-  (mapcar (lambda (entry) (concat (car entry) "=" (cdr entry))) env))
-
-(defun git-call-process (buffer &rest args)
-  "Wrapper for call-process that sets environment strings."
-  (apply #'call-process "git" nil buffer nil args))
-
-(defun git-call-process-display-error (&rest args)
-  "Wrapper for call-process that displays error messages."
-  (let* ((dir default-directory)
-         (buffer (get-buffer-create "*Git Command Output*"))
-         (ok (with-current-buffer buffer
-               (let ((default-directory dir)
-                     (buffer-read-only nil))
-                 (erase-buffer)
-                 (eq 0 (apply #'git-call-process (list buffer t) args))))))
-    (unless ok (display-message-or-buffer buffer))
-    ok))
-
-(defun git-call-process-string (&rest args)
-  "Wrapper for call-process that returns the process output as a string,
-or nil if the git command failed."
-  (with-temp-buffer
-    (and (eq 0 (apply #'git-call-process t args))
-         (buffer-string))))
-
-(defun git-call-process-string-display-error (&rest args)
-  "Wrapper for call-process that displays error message and returns
-the process output as a string, or nil if the git command failed."
-  (with-temp-buffer
-    (if (eq 0 (apply #'git-call-process (list t t) args))
-        (buffer-string)
-      (display-message-or-buffer (current-buffer))
-      nil)))
-
-(defun git-run-process-region (buffer start end program args)
-  "Run a git process with a buffer region as input."
-  (let ((output-buffer (current-buffer))
-        (dir default-directory))
-    (with-current-buffer buffer
-      (cd dir)
-      (apply #'call-process-region start end program
-             nil (list output-buffer t) nil args))))
-
-(defun git-run-command-buffer (buffer-name &rest args)
-  "Run a git command, sending the output to a buffer named BUFFER-NAME."
-  (let ((dir default-directory)
-        (buffer (get-buffer-create buffer-name)))
-    (message "Running git %s..." (car args))
-    (with-current-buffer buffer
-      (let ((default-directory dir)
-            (buffer-read-only nil))
-        (erase-buffer)
-        (apply #'git-call-process buffer args)))
-    (message "Running git %s...done" (car args))
-    buffer))
-
-(defun git-run-command-region (buffer start end env &rest args)
-  "Run a git command with specified buffer region as input."
-  (with-temp-buffer
-    (if (eq 0 (if env
-                  (git-run-process-region
-                   buffer start end "env"
-                   (append (git-get-env-strings env) (list "git") args))
-                (git-run-process-region buffer start end "git" args)))
-        (buffer-string)
-      (display-message-or-buffer (current-buffer))
-      nil)))
-
-(defun git-run-hook (hook env &rest args)
-  "Run a git hook and display its output if any."
-  (let ((dir default-directory)
-        (hook-name (expand-file-name (concat ".git/hooks/" hook))))
-    (or (not (file-executable-p hook-name))
-        (let (status (buffer (get-buffer-create "*Git Hook Output*")))
-          (with-current-buffer buffer
-            (erase-buffer)
-            (cd dir)
-            (setq status
-                  (if env
-                      (apply #'call-process "env" nil (list buffer t) nil
-                             (append (git-get-env-strings env) (list hook-name) args))
-                    (apply #'call-process hook-name nil (list buffer t) nil args))))
-          (display-message-or-buffer buffer)
-          (eq 0 status)))))
-
-(defun git-get-string-sha1 (string)
-  "Read a SHA1 from the specified string."
-  (and string
-       (string-match "[0-9a-f]\\{40\\}" string)
-       (match-string 0 string)))
-
-(defun git-get-committer-name ()
-  "Return the name to use as GIT_COMMITTER_NAME."
-  ; copied from log-edit
-  (or git-committer-name
-      (git-config "user.name")
-      (and (boundp 'add-log-full-name) add-log-full-name)
-      (and (fboundp 'user-full-name) (user-full-name))
-      (and (boundp 'user-full-name) user-full-name)))
-
-(defun git-get-committer-email ()
-  "Return the email address to use as GIT_COMMITTER_EMAIL."
-  ; copied from log-edit
-  (or git-committer-email
-      (git-config "user.email")
-      (and (boundp 'add-log-mailing-address) add-log-mailing-address)
-      (and (fboundp 'user-mail-address) (user-mail-address))
-      (and (boundp 'user-mail-address) user-mail-address)))
-
-(defun git-get-commits-coding-system ()
-  "Return the coding system to use for commits."
-  (let ((repo-config (git-config "i18n.commitencoding")))
-    (or git-commits-coding-system
-        (and repo-config
-             (fboundp 'locale-charset-to-coding-system)
-             (locale-charset-to-coding-system repo-config))
-      'utf-8)))
-
-(defun git-get-logoutput-coding-system ()
-  "Return the coding system used for git-log output."
-  (let ((repo-config (or (git-config "i18n.logoutputencoding")
-                         (git-config "i18n.commitencoding"))))
-    (or git-commits-coding-system
-        (and repo-config
-             (fboundp 'locale-charset-to-coding-system)
-             (locale-charset-to-coding-system repo-config))
-      'utf-8)))
-
-(defun git-escape-file-name (name)
-  "Escape a file name if necessary."
-  (if (string-match "[\n\t\"\\]" name)
-      (concat "\""
-              (mapconcat (lambda (c)
-                   (case c
-                     (?\n "\\n")
-                     (?\t "\\t")
-                     (?\\ "\\\\")
-                     (?\" "\\\"")
-                     (t (char-to-string c))))
-                 name "")
-              "\"")
-    name))
-
-(defun git-success-message (text files)
-  "Print a success message after having handled FILES."
-  (let ((n (length files)))
-    (if (equal n 1)
-        (message "%s %s" text (car files))
-      (message "%s %d files" text n))))
-
-(defun git-get-top-dir (dir)
-  "Retrieve the top-level directory of a git tree."
-  (let ((cdup (with-output-to-string
-                (with-current-buffer standard-output
-                  (cd dir)
-                  (unless (eq 0 (git-call-process t "rev-parse" "--show-cdup"))
-                    (error "cannot find top-level git tree for %s." dir))))))
-    (expand-file-name (concat (file-name-as-directory dir)
-                              (car (split-string cdup "\n"))))))
-
-;stolen from pcl-cvs
-(defun git-append-to-ignore (file)
-  "Add a file name to the ignore file in its directory."
-  (let* ((fullname (expand-file-name file))
-         (dir (file-name-directory fullname))
-         (name (file-name-nondirectory fullname))
-         (ignore-name (expand-file-name git-per-dir-ignore-file dir))
-         (created (not (file-exists-p ignore-name))))
-  (save-window-excursion
-    (set-buffer (find-file-noselect ignore-name))
-    (goto-char (point-max))
-    (unless (zerop (current-column)) (insert "\n"))
-    (insert "/" name "\n")
-    (sort-lines nil (point-min) (point-max))
-    (save-buffer))
-  (when created
-    (git-call-process nil "update-index" "--add" "--" (file-relative-name ignore-name)))
-  (git-update-status-files (list (file-relative-name ignore-name)))))
-
-; propertize definition for XEmacs, stolen from erc-compat
-(eval-when-compile
-  (unless (fboundp 'propertize)
-    (defun propertize (string &rest props)
-      (let ((string (copy-sequence string)))
-        (while props
-          (put-text-property 0 (length string) (nth 0 props) (nth 1 props) string)
-          (setq props (cddr props)))
-        string))))
-
-;;;; Wrappers for basic git commands
-;;;; ------------------------------------------------------------
-
-(defun git-rev-parse (rev)
-  "Parse a revision name and return its SHA1."
-  (git-get-string-sha1
-   (git-call-process-string "rev-parse" rev)))
-
-(defun git-config (key)
-  "Retrieve the value associated to KEY in the git repository config file."
-  (let ((str (git-call-process-string "config" key)))
-    (and str (car (split-string str "\n")))))
-
-(defun git-symbolic-ref (ref)
-  "Wrapper for the git-symbolic-ref command."
-  (let ((str (git-call-process-string "symbolic-ref" ref)))
-    (and str (car (split-string str "\n")))))
-
-(defun git-update-ref (ref newval &optional oldval reason)
-  "Update a reference by calling git-update-ref."
-  (let ((args (and oldval (list oldval))))
-    (when newval (push newval args))
-    (push ref args)
-    (when reason
-     (push reason args)
-     (push "-m" args))
-    (unless newval (push "-d" args))
-    (apply 'git-call-process-display-error "update-ref" args)))
-
-(defun git-for-each-ref (&rest specs)
-  "Return a list of refs using git-for-each-ref.
-Each entry is a cons of (SHORT-NAME . FULL-NAME)."
-  (let (refs)
-    (with-temp-buffer
-      (apply #'git-call-process t "for-each-ref" "--format=%(refname)" specs)
-      (goto-char (point-min))
-      (while (re-search-forward "^[^/\n]+/[^/\n]+/\\(.+\\)$" nil t)
-       (push (cons (match-string 1) (match-string 0)) refs)))
-    (nreverse refs)))
-
-(defun git-read-tree (tree &optional index-file)
-  "Read a tree into the index file."
-  (let ((process-environment
-         (append (and index-file (list (concat "GIT_INDEX_FILE=" index-file))) process-environment)))
-    (apply 'git-call-process-display-error "read-tree" (if tree (list tree)))))
-
-(defun git-write-tree (&optional index-file)
-  "Call git-write-tree and return the resulting tree SHA1 as a string."
-  (let ((process-environment
-         (append (and index-file (list (concat "GIT_INDEX_FILE=" index-file))) process-environment)))
-    (git-get-string-sha1
-     (git-call-process-string-display-error "write-tree"))))
-
-(defun git-commit-tree (buffer tree parent)
-  "Create a commit and possibly update HEAD.
-Create a commit with the message in BUFFER using the tree with hash TREE.
-Use PARENT as the parent of the new commit. If PARENT is the current \"HEAD\",
-update the \"HEAD\" reference to the new commit."
-  (let ((author-name (git-get-committer-name))
-        (author-email (git-get-committer-email))
-        (subject "commit (initial): ")
-        author-date log-start log-end args coding-system-for-write)
-    (when parent
-      (setq subject "commit: ")
-      (push "-p" args)
-      (push parent args))
-    (with-current-buffer buffer
-      (goto-char (point-min))
-      (if
-          (setq log-start (re-search-forward (concat "^" (regexp-quote git-log-msg-separator) "\n") nil t))
-          (save-restriction
-            (narrow-to-region (point-min) log-start)
-            (goto-char (point-min))
-            (when (re-search-forward "^Author: +\\(.*?\\) *<\\(.*\\)> *$" nil t)
-              (setq author-name (match-string 1)
-                    author-email (match-string 2)))
-            (goto-char (point-min))
-            (when (re-search-forward "^Date: +\\(.*\\)$" nil t)
-              (setq author-date (match-string 1)))
-            (goto-char (point-min))
-            (when (re-search-forward "^Merge: +\\(.*\\)" nil t)
-              (setq subject "commit (merge): ")
-              (dolist (parent (split-string (match-string 1) " +" t))
-                (push "-p" args)
-                (push parent args))))
-        (setq log-start (point-min)))
-      (setq log-end (point-max))
-      (goto-char log-start)
-      (when (re-search-forward ".*$" nil t)
-        (setq subject (concat subject (match-string 0))))
-      (setq coding-system-for-write buffer-file-coding-system))
-    (let ((commit
-           (git-get-string-sha1
-            (let ((env `(("GIT_AUTHOR_NAME" . ,author-name)
-                         ("GIT_AUTHOR_EMAIL" . ,author-email)
-                         ("GIT_COMMITTER_NAME" . ,(git-get-committer-name))
-                         ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email)))))
-              (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env))
-              (apply #'git-run-command-region
-                     buffer log-start log-end env
-                     "commit-tree" tree (nreverse args))))))
-      (when commit (git-update-ref "HEAD" commit parent subject))
-      commit)))
-
-(defun git-empty-db-p ()
-  "Check if the git db is empty (no commit done yet)."
-  (not (eq 0 (git-call-process nil "rev-parse" "--verify" "HEAD"))))
-
-(defun git-get-merge-heads ()
-  "Retrieve the merge heads from the MERGE_HEAD file if present."
-  (let (heads)
-    (when (file-readable-p ".git/MERGE_HEAD")
-      (with-temp-buffer
-        (insert-file-contents ".git/MERGE_HEAD" nil nil nil t)
-        (goto-char (point-min))
-        (while (re-search-forward "[0-9a-f]\\{40\\}" nil t)
-          (push (match-string 0) heads))))
-    (nreverse heads)))
-
-(defun git-get-commit-description (commit)
-  "Get a one-line description of COMMIT."
-  (let ((coding-system-for-read (git-get-logoutput-coding-system)))
-    (let ((descr (git-call-process-string "log" "--max-count=1" "--pretty=oneline" commit)))
-      (if (and descr (string-match "\\`\\([0-9a-f]\\{40\\}\\) *\\(.*\\)$" descr))
-          (concat (substring (match-string 1 descr) 0 10) " - " (match-string 2 descr))
-        descr))))
-
-;;;; File info structure
-;;;; ------------------------------------------------------------
-
-; fileinfo structure stolen from pcl-cvs
-(defstruct (git-fileinfo
-            (:copier nil)
-            (:constructor git-create-fileinfo (state name &optional old-perm new-perm rename-state orig-name marked))
-            (:conc-name git-fileinfo->))
-  marked              ;; t/nil
-  state               ;; current state
-  name                ;; file name
-  old-perm new-perm   ;; permission flags
-  rename-state        ;; rename or copy state
-  orig-name           ;; original name for renames or copies
-  needs-update        ;; whether file needs to be updated
-  needs-refresh)      ;; whether file needs to be refreshed
-
-(defvar git-status nil)
-
-(defun git-set-fileinfo-state (info state)
-  "Set the state of a file info."
-  (unless (eq (git-fileinfo->state info) state)
-    (setf (git-fileinfo->state info) state
-         (git-fileinfo->new-perm info) (git-fileinfo->old-perm info)
-          (git-fileinfo->rename-state info) nil
-          (git-fileinfo->orig-name info) nil
-          (git-fileinfo->needs-update info) nil
-          (git-fileinfo->needs-refresh info) t)))
-
-(defun git-status-filenames-map (status func files &rest args)
-  "Apply FUNC to the status files names in the FILES list.
-The list must be sorted."
-  (when files
-    (let ((file (pop files))
-          (node (ewoc-nth status 0)))
-      (while (and file node)
-        (let* ((info (ewoc-data node))
-               (name (git-fileinfo->name info)))
-          (if (string-lessp name file)
-              (setq node (ewoc-next status node))
-            (if (string-equal name file)
-                (apply func info args))
-            (setq file (pop files))))))))
-
-(defun git-set-filenames-state (status files state)
-  "Set the state of a list of named files. The list must be sorted"
-  (when files
-    (git-status-filenames-map status #'git-set-fileinfo-state files state)
-    (unless state  ;; delete files whose state has been set to nil
-      (ewoc-filter status (lambda (info) (git-fileinfo->state info))))))
-
-(defun git-state-code (code)
-  "Convert from a string to a added/deleted/modified state."
-  (case (string-to-char code)
-    (?M 'modified)
-    (?? 'unknown)
-    (?A 'added)
-    (?D 'deleted)
-    (?U 'unmerged)
-    (?T 'modified)
-    (t nil)))
-
-(defun git-status-code-as-string (code)
-  "Format a git status code as string."
-  (case code
-    ('modified (propertize "Modified" 'face 'git-status-face))
-    ('unknown  (propertize "Unknown " 'face 'git-unknown-face))
-    ('added    (propertize "Added   " 'face 'git-status-face))
-    ('deleted  (propertize "Deleted " 'face 'git-status-face))
-    ('unmerged (propertize "Unmerged" 'face 'git-unmerged-face))
-    ('uptodate (propertize "Uptodate" 'face 'git-uptodate-face))
-    ('ignored  (propertize "Ignored " 'face 'git-ignored-face))
-    (t "?       ")))
-
-(defun git-file-type-as-string (old-perm new-perm)
-  "Return a string describing the file type based on its permissions."
-  (let* ((old-type (lsh (or old-perm 0) -9))
-        (new-type (lsh (or new-perm 0) -9))
-        (str (case new-type
-               (64  ;; file
-                (case old-type
-                  (64 nil)
-                  (80 "   (type change symlink -> file)")
-                  (112 "   (type change subproject -> file)")))
-                (80  ;; symlink
-                 (case old-type
-                   (64 "   (type change file -> symlink)")
-                   (112 "   (type change subproject -> symlink)")
-                   (t "   (symlink)")))
-                 (112  ;; subproject
-                  (case old-type
-                    (64 "   (type change file -> subproject)")
-                    (80 "   (type change symlink -> subproject)")
-                    (t "   (subproject)")))
-                  (72 nil)  ;; directory (internal, not a real git state)
-                 (0  ;; deleted or unknown
-                  (case old-type
-                    (80 "   (symlink)")
-                    (112 "   (subproject)")))
-                 (t (format "   (unknown type %o)" new-type)))))
-    (cond (str (propertize str 'face 'git-status-face))
-          ((eq new-type 72) "/")
-          (t ""))))
-
-(defun git-rename-as-string (info)
-  "Return a string describing the copy or rename associated with INFO, or an empty string if none."
-  (let ((state (git-fileinfo->rename-state info)))
-    (if state
-        (propertize
-         (concat "   ("
-                 (if (eq state 'copy) "copied from "
-                   (if (eq (git-fileinfo->state info) 'added) "renamed from "
-                     "renamed to "))
-                 (git-escape-file-name (git-fileinfo->orig-name info))
-                 ")") 'face 'git-status-face)
-      "")))
-
-(defun git-permissions-as-string (old-perm new-perm)
-  "Format a permission change as string."
-  (propertize
-   (if (or (not old-perm)
-           (not new-perm)
-           (eq 0 (logand ?\111 (logxor old-perm new-perm))))
-       "  "
-     (if (eq 0 (logand ?\111 old-perm)) "+x" "-x"))
-  'face 'git-permission-face))
-
-(defun git-fileinfo-prettyprint (info)
-  "Pretty-printer for the git-fileinfo structure."
-  (let ((old-perm (git-fileinfo->old-perm info))
-       (new-perm (git-fileinfo->new-perm info)))
-    (insert (concat "   " (if (git-fileinfo->marked info) (propertize "*" 'face 'git-mark-face) " ")
-                   " " (git-status-code-as-string (git-fileinfo->state info))
-                   " " (git-permissions-as-string old-perm new-perm)
-                   "  " (git-escape-file-name (git-fileinfo->name info))
-                   (git-file-type-as-string old-perm new-perm)
-                   (git-rename-as-string info)))))
-
-(defun git-update-node-fileinfo (node info)
-  "Update the fileinfo of the specified node. The names are assumed to match already."
-  (let ((data (ewoc-data node)))
-    (setf
-     ;; preserve the marked flag
-     (git-fileinfo->marked info) (git-fileinfo->marked data)
-     (git-fileinfo->needs-update data) nil)
-    (when (not (equal info data))
-      (setf (git-fileinfo->needs-refresh info) t
-            (ewoc-data node) info))))
-
-(defun git-insert-info-list (status infolist files)
-  "Insert a sorted list of file infos in the status buffer, replacing existing ones if any."
-  (let* ((info (pop infolist))
-         (node (ewoc-nth status 0))
-         (name (and info (git-fileinfo->name info)))
-         remaining)
-    (while info
-      (let ((nodename (and node (git-fileinfo->name (ewoc-data node)))))
-        (while (and files (string-lessp (car files) name))
-          (push (pop files) remaining))
-        (when (and files (string-equal (car files) name))
-          (setq files (cdr files)))
-        (cond ((not nodename)
-               (setq node (ewoc-enter-last status info))
-               (setq info (pop infolist))
-               (setq name (and info (git-fileinfo->name info))))
-              ((string-lessp nodename name)
-               (setq node (ewoc-next status node)))
-              ((string-equal nodename name)
-               ;; preserve the marked flag
-               (git-update-node-fileinfo node info)
-               (setq info (pop infolist))
-               (setq name (and info (git-fileinfo->name info))))
-              (t
-               (setq node (ewoc-enter-before status node info))
-               (setq info (pop infolist))
-               (setq name (and info (git-fileinfo->name info)))))))
-    (nconc (nreverse remaining) files)))
-
-(defun git-run-diff-index (status files)
-  "Run git-diff-index on FILES and parse the results into STATUS.
-Return the list of files that haven't been handled."
-  (let (infolist)
-    (with-temp-buffer
-      (apply #'git-call-process t "diff-index" "-z" "-M" "HEAD" "--" files)
-      (goto-char (point-min))
-      (while (re-search-forward
-             ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMUT]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
-              nil t 1)
-        (let ((old-perm (string-to-number (match-string 1) 8))
-              (new-perm (string-to-number (match-string 2) 8))
-              (state (or (match-string 4) (match-string 6)))
-              (name (or (match-string 5) (match-string 7)))
-              (new-name (match-string 8)))
-          (if new-name  ; copy or rename
-              (if (eq ?C (string-to-char state))
-                  (push (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) infolist)
-                (push (git-create-fileinfo 'deleted name 0 0 'rename new-name) infolist)
-                (push (git-create-fileinfo 'added new-name old-perm new-perm 'rename name) infolist))
-            (push (git-create-fileinfo (git-state-code state) name old-perm new-perm) infolist)))))
-    (setq infolist (sort (nreverse infolist)
-                         (lambda (info1 info2)
-                           (string-lessp (git-fileinfo->name info1)
-                                         (git-fileinfo->name info2)))))
-    (git-insert-info-list status infolist files)))
-
-(defun git-find-status-file (status file)
-  "Find a given file in the status ewoc and return its node."
-  (let ((node (ewoc-nth status 0)))
-    (while (and node (not (string= file (git-fileinfo->name (ewoc-data node)))))
-      (setq node (ewoc-next status node)))
-    node))
-
-(defun git-run-ls-files (status files default-state &rest options)
-  "Run git-ls-files on FILES and parse the results into STATUS.
-Return the list of files that haven't been handled."
-  (let (infolist)
-    (with-temp-buffer
-      (apply #'git-call-process t "ls-files" "-z" (append options (list "--") files))
-      (goto-char (point-min))
-      (while (re-search-forward "\\([^\0]*?\\)\\(/?\\)\0" nil t 1)
-        (let ((name (match-string 1)))
-          (push (git-create-fileinfo default-state name 0
-                                     (if (string-equal "/" (match-string 2)) (lsh ?\110 9) 0))
-                infolist))))
-    (setq infolist (nreverse infolist))  ;; assume it is sorted already
-    (git-insert-info-list status infolist files)))
-
-(defun git-run-ls-files-cached (status files default-state)
-  "Run git-ls-files -c on FILES and parse the results into STATUS.
-Return the list of files that haven't been handled."
-  (let (infolist)
-    (with-temp-buffer
-      (apply #'git-call-process t "ls-files" "-z" "-s" "-c" "--" files)
-      (goto-char (point-min))
-      (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t)
-       (let* ((new-perm (string-to-number (match-string 1) 8))
-              (old-perm (if (eq default-state 'added) 0 new-perm))
-              (name (match-string 2)))
-         (push (git-create-fileinfo default-state name old-perm new-perm) infolist))))
-    (setq infolist (nreverse infolist))  ;; assume it is sorted already
-    (git-insert-info-list status infolist files)))
-
-(defun git-run-ls-unmerged (status files)
-  "Run git-ls-files -u on FILES and parse the results into STATUS."
-  (with-temp-buffer
-    (apply #'git-call-process t "ls-files" "-z" "-u" "--" files)
-    (goto-char (point-min))
-    (let (unmerged-files)
-      (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
-        (push (match-string 1) unmerged-files))
-      (setq unmerged-files (nreverse unmerged-files))  ;; assume it is sorted already
-      (git-set-filenames-state status unmerged-files 'unmerged))))
-
-(defun git-get-exclude-files ()
-  "Get the list of exclude files to pass to git-ls-files."
-  (let (files
-        (config (git-config "core.excludesfile")))
-    (when (file-readable-p ".git/info/exclude")
-      (push ".git/info/exclude" files))
-    (when (and config (file-readable-p config))
-      (push config files))
-    files))
-
-(defun git-run-ls-files-with-excludes (status files default-state &rest options)
-  "Run git-ls-files on FILES with appropriate --exclude-from options."
-  (let ((exclude-files (git-get-exclude-files)))
-    (apply #'git-run-ls-files status files default-state "--directory" "--no-empty-directory"
-           (concat "--exclude-per-directory=" git-per-dir-ignore-file)
-           (append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
-
-(defun git-update-status-files (&optional files mark-files)
-  "Update the status of FILES from the index.
-The FILES list must be sorted."
-  (unless git-status (error "Not in git-status buffer."))
-  ;; set the needs-update flag on existing files
-  (if files
-      (git-status-filenames-map
-       git-status (lambda (info) (setf (git-fileinfo->needs-update info) t)) files)
-    (ewoc-map (lambda (info) (setf (git-fileinfo->needs-update info) t) nil) git-status)
-    (git-call-process nil "update-index" "--refresh")
-    (when git-show-uptodate
-      (git-run-ls-files-cached git-status nil 'uptodate)))
-  (let ((remaining-files
-          (if (git-empty-db-p) ; we need some special handling for an empty db
-             (git-run-ls-files-cached git-status files 'added)
-            (git-run-diff-index git-status files))))
-    (git-run-ls-unmerged git-status files)
-    (when (or remaining-files (and git-show-unknown (not files)))
-      (setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'unknown "-o")))
-    (when (or remaining-files (and git-show-ignored (not files)))
-      (setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'ignored "-o" "-i")))
-    (unless files
-      (setq remaining-files (git-get-filenames (ewoc-collect git-status #'git-fileinfo->needs-update))))
-    (when remaining-files
-      (setq remaining-files (git-run-ls-files-cached git-status remaining-files 'uptodate)))
-    (git-set-filenames-state git-status remaining-files nil)
-    (when mark-files (git-mark-files git-status files))
-    (git-refresh-files)
-    (git-refresh-ewoc-hf git-status)))
-
-(defun git-mark-files (status files)
-  "Mark all the specified FILES, and unmark the others."
-  (let ((file (and files (pop files)))
-        (node (ewoc-nth status 0)))
-    (while node
-      (let ((info (ewoc-data node)))
-        (if (and file (string-equal (git-fileinfo->name info) file))
-            (progn
-              (unless (git-fileinfo->marked info)
-                (setf (git-fileinfo->marked info) t)
-                (setf (git-fileinfo->needs-refresh info) t))
-              (setq file (pop files))
-              (setq node (ewoc-next status node)))
-          (when (git-fileinfo->marked info)
-            (setf (git-fileinfo->marked info) nil)
-            (setf (git-fileinfo->needs-refresh info) t))
-          (if (and file (string-lessp file (git-fileinfo->name info)))
-              (setq file (pop files))
-            (setq node (ewoc-next status node))))))))
-
-(defun git-marked-files ()
-  "Return a list of all marked files, or if none a list containing just the file at cursor position."
-  (unless git-status (error "Not in git-status buffer."))
-  (or (ewoc-collect git-status (lambda (info) (git-fileinfo->marked info)))
-      (list (ewoc-data (ewoc-locate git-status)))))
-
-(defun git-marked-files-state (&rest states)
-  "Return a sorted list of marked files that are in the specified states."
-  (let ((files (git-marked-files))
-        result)
-    (dolist (info files)
-      (when (memq (git-fileinfo->state info) states)
-        (push info result)))
-    (nreverse result)))
-
-(defun git-refresh-files ()
-  "Refresh all files that need it and clear the needs-refresh flag."
-  (unless git-status (error "Not in git-status buffer."))
-  (ewoc-map
-   (lambda (info)
-     (let ((refresh (git-fileinfo->needs-refresh info)))
-       (setf (git-fileinfo->needs-refresh info) nil)
-       refresh))
-   git-status)
-  ; move back to goal column
-  (when goal-column (move-to-column goal-column)))
-
-(defun git-refresh-ewoc-hf (status)
-  "Refresh the ewoc header and footer."
-  (let ((branch (git-symbolic-ref "HEAD"))
-        (head (if (git-empty-db-p) "Nothing committed yet"
-                (git-get-commit-description "HEAD")))
-        (merge-heads (git-get-merge-heads)))
-    (ewoc-set-hf status
-                 (format "Directory:  %s\nBranch:     %s\nHead:       %s%s\n"
-                         default-directory
-                         (if branch
-                             (if (string-match "^refs/heads/" branch)
-                                 (substring branch (match-end 0))
-                               branch)
-                           "none (detached HEAD)")
-                         head
-                         (if merge-heads
-                             (concat "\nMerging:    "
-                                     (mapconcat (lambda (str) (git-get-commit-description str)) merge-heads "\n            "))
-                           ""))
-                 (if (ewoc-nth status 0) "" "    No changes."))))
-
-(defun git-get-filenames (files)
-  (mapcar (lambda (info) (git-fileinfo->name info)) files))
-
-(defun git-update-index (index-file files)
-  "Run git-update-index on a list of files."
-  (let ((process-environment (append (and index-file (list (concat "GIT_INDEX_FILE=" index-file)))
-                                     process-environment))
-        added deleted modified)
-    (dolist (info files)
-      (case (git-fileinfo->state info)
-        ('added (push info added))
-        ('deleted (push info deleted))
-        ('modified (push info modified))))
-    (and
-     (or (not added) (apply #'git-call-process-display-error "update-index" "--add" "--" (git-get-filenames added)))
-     (or (not deleted) (apply #'git-call-process-display-error "update-index" "--remove" "--" (git-get-filenames deleted)))
-     (or (not modified) (apply #'git-call-process-display-error "update-index" "--" (git-get-filenames modified))))))
-
-(defun git-run-pre-commit-hook ()
-  "Run the pre-commit hook if any."
-  (unless git-status (error "Not in git-status buffer."))
-  (let ((files (git-marked-files-state 'added 'deleted 'modified)))
-    (or (not files)
-        (not (file-executable-p ".git/hooks/pre-commit"))
-        (let ((index-file (make-temp-file "gitidx")))
-          (unwind-protect
-            (let ((head-tree (unless (git-empty-db-p) (git-rev-parse "HEAD^{tree}"))))
-              (git-read-tree head-tree index-file)
-              (git-update-index index-file files)
-              (git-run-hook "pre-commit" `(("GIT_INDEX_FILE" . ,index-file))))
-          (delete-file index-file))))))
-
-(defun git-do-commit ()
-  "Perform the actual commit using the current buffer as log message."
-  (interactive)
-  (let ((buffer (current-buffer))
-        (index-file (make-temp-file "gitidx")))
-    (with-current-buffer log-edit-parent-buffer
-      (if (git-marked-files-state 'unmerged)
-          (message "You cannot commit unmerged files, resolve them first.")
-        (unwind-protect
-            (let ((files (git-marked-files-state 'added 'deleted 'modified))
-                  head tree head-tree)
-              (unless (git-empty-db-p)
-                (setq head (git-rev-parse "HEAD")
-                      head-tree (git-rev-parse "HEAD^{tree}")))
-              (message "Running git commit...")
-              (when
-                  (and
-                   (git-read-tree head-tree index-file)
-                   (git-update-index nil files)         ;update both the default index
-                   (git-update-index index-file files)  ;and the temporary one
-                   (setq tree (git-write-tree index-file)))
-                (if (or (not (string-equal tree head-tree))
-                        (yes-or-no-p "The tree was not modified, do you really want to perform an empty commit? "))
-                    (let ((commit (git-commit-tree buffer tree head)))
-                      (when commit
-                        (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
-                        (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
-                        (with-current-buffer buffer (erase-buffer))
-                        (git-update-status-files (git-get-filenames files))
-                        (git-call-process nil "rerere")
-                        (git-call-process nil "gc" "--auto")
-                        (message "Committed %s." commit)
-                        (git-run-hook "post-commit" nil)))
-                  (message "Commit aborted."))))
-          (delete-file index-file))))))
-
-
-;;;; Interactive functions
-;;;; ------------------------------------------------------------
-
-(defun git-mark-file ()
-  "Mark the file that the cursor is on and move to the next one."
-  (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (let* ((pos (ewoc-locate git-status))
-         (info (ewoc-data pos)))
-    (setf (git-fileinfo->marked info) t)
-    (ewoc-invalidate git-status pos)
-    (ewoc-goto-next git-status 1)))
-
-(defun git-unmark-file ()
-  "Unmark the file that the cursor is on and move to the next one."
-  (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (let* ((pos (ewoc-locate git-status))
-         (info (ewoc-data pos)))
-    (setf (git-fileinfo->marked info) nil)
-    (ewoc-invalidate git-status pos)
-    (ewoc-goto-next git-status 1)))
-
-(defun git-unmark-file-up ()
-  "Unmark the file that the cursor is on and move to the previous one."
-  (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (let* ((pos (ewoc-locate git-status))
-         (info (ewoc-data pos)))
-    (setf (git-fileinfo->marked info) nil)
-    (ewoc-invalidate git-status pos)
-    (ewoc-goto-prev git-status 1)))
-
-(defun git-mark-all ()
-  "Mark all files."
-  (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (ewoc-map (lambda (info) (unless (git-fileinfo->marked info)
-                             (setf (git-fileinfo->marked info) t))) git-status)
-  ; move back to goal column after invalidate
-  (when goal-column (move-to-column goal-column)))
-
-(defun git-unmark-all ()
-  "Unmark all files."
-  (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (ewoc-map (lambda (info) (when (git-fileinfo->marked info)
-                             (setf (git-fileinfo->marked info) nil)
-                             t)) git-status)
-  ; move back to goal column after invalidate
-  (when goal-column (move-to-column goal-column)))
-
-(defun git-toggle-all-marks ()
-  "Toggle all file marks."
-  (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) (not (git-fileinfo->marked info))) t) git-status)
-  ; move back to goal column after invalidate
-  (when goal-column (move-to-column goal-column)))
-
-(defun git-next-file (&optional n)
-  "Move the selection down N files."
-  (interactive "p")
-  (unless git-status (error "Not in git-status buffer."))
-  (ewoc-goto-next git-status n))
-
-(defun git-prev-file (&optional n)
-  "Move the selection up N files."
-  (interactive "p")
-  (unless git-status (error "Not in git-status buffer."))
-  (ewoc-goto-prev git-status n))
-
-(defun git-next-unmerged-file (&optional n)
-  "Move the selection down N unmerged files."
-  (interactive "p")
-  (unless git-status (error "Not in git-status buffer."))
-  (let* ((last (ewoc-locate git-status))
-         (node (ewoc-next git-status last)))
-    (while (and node (> n 0))
-      (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
-        (setq n (1- n))
-        (setq last node))
-      (setq node (ewoc-next git-status node)))
-    (ewoc-goto-node git-status last)))
-
-(defun git-prev-unmerged-file (&optional n)
-  "Move the selection up N unmerged files."
-  (interactive "p")
-  (unless git-status (error "Not in git-status buffer."))
-  (let* ((last (ewoc-locate git-status))
-         (node (ewoc-prev git-status last)))
-    (while (and node (> n 0))
-      (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
-        (setq n (1- n))
-        (setq last node))
-      (setq node (ewoc-prev git-status node)))
-    (ewoc-goto-node git-status last)))
-
-(defun git-insert-file (file)
-  "Insert file(s) into the git-status buffer."
-  (interactive "fInsert file: ")
-  (git-update-status-files (list (file-relative-name file))))
-
-(defun git-add-file ()
-  "Add marked file(s) to the index cache."
-  (interactive)
-  (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored 'unmerged))))
-    ;; FIXME: add support for directories
-    (unless files
-      (push (file-relative-name (read-file-name "File to add: " nil nil t)) files))
-    (when (apply 'git-call-process-display-error "update-index" "--add" "--" files)
-      (git-update-status-files files)
-      (git-success-message "Added" files))))
-
-(defun git-ignore-file ()
-  "Add marked file(s) to the ignore list."
-  (interactive)
-  (let ((files (git-get-filenames (git-marked-files-state 'unknown))))
-    (unless files
-      (push (file-relative-name (read-file-name "File to ignore: " nil nil t)) files))
-    (dolist (f files) (git-append-to-ignore f))
-    (git-update-status-files files)
-    (git-success-message "Ignored" files)))
-
-(defun git-remove-file ()
-  "Remove the marked file(s)."
-  (interactive)
-  (let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate 'ignored))))
-    (unless files
-      (push (file-relative-name (read-file-name "File to remove: " nil nil t)) files))
-    (if (yes-or-no-p
-         (if (cdr files)
-             (format "Remove %d files? " (length files))
-           (format "Remove %s? " (car files))))
-        (progn
-          (dolist (name files)
-            (ignore-errors
-              (if (file-directory-p name)
-                  (delete-directory name)
-                (delete-file name))))
-          (when (apply 'git-call-process-display-error "update-index" "--remove" "--" files)
-            (git-update-status-files files)
-            (git-success-message "Removed" files)))
-      (message "Aborting"))))
-
-(defun git-revert-file ()
-  "Revert changes to the marked file(s)."
-  (interactive)
-  (let ((files (git-marked-files-state 'added 'deleted 'modified 'unmerged))
-        added modified)
-    (when (and files
-               (yes-or-no-p
-                (if (cdr files)
-                    (format "Revert %d files? " (length files))
-                  (format "Revert %s? " (git-fileinfo->name (car files))))))
-      (dolist (info files)
-        (case (git-fileinfo->state info)
-          ('added (push (git-fileinfo->name info) added))
-          ('deleted (push (git-fileinfo->name info) modified))
-          ('unmerged (push (git-fileinfo->name info) modified))
-          ('modified (push (git-fileinfo->name info) modified))))
-      ;; check if a buffer contains one of the files and isn't saved
-      (dolist (file modified)
-        (let ((buffer (get-file-buffer file)))
-          (when (and buffer (buffer-modified-p buffer))
-            (error "Buffer %s is modified. Please kill or save modified buffers before reverting." (buffer-name buffer)))))
-      (let ((ok (and
-                 (or (not added)
-                     (apply 'git-call-process-display-error "update-index" "--force-remove" "--" added))
-                 (or (not modified)
-                     (apply 'git-call-process-display-error "checkout" "HEAD" modified))))
-            (names (git-get-filenames files)))
-        (git-update-status-files names)
-        (when ok
-          (dolist (file modified)
-            (let ((buffer (get-file-buffer file)))
-              (when buffer (with-current-buffer buffer (revert-buffer t t t)))))
-          (git-success-message "Reverted" names))))))
-
-(defun git-remove-handled ()
-  "Remove handled files from the status list."
-  (interactive)
-  (ewoc-filter git-status
-               (lambda (info)
-                 (case (git-fileinfo->state info)
-                   ('ignored git-show-ignored)
-                   ('uptodate git-show-uptodate)
-                   ('unknown git-show-unknown)
-                   (t t))))
-  (unless (ewoc-nth git-status 0)  ; refresh header if list is empty
-    (git-refresh-ewoc-hf git-status)))
-
-(defun git-toggle-show-uptodate ()
-  "Toogle the option for showing up-to-date files."
-  (interactive)
-  (if (setq git-show-uptodate (not git-show-uptodate))
-      (git-refresh-status)
-    (git-remove-handled)))
-
-(defun git-toggle-show-ignored ()
-  "Toogle the option for showing ignored files."
-  (interactive)
-  (if (setq git-show-ignored (not git-show-ignored))
-      (progn
-        (message "Inserting ignored files...")
-        (git-run-ls-files-with-excludes git-status nil 'ignored "-o" "-i")
-        (git-refresh-files)
-        (git-refresh-ewoc-hf git-status)
-        (message "Inserting ignored files...done"))
-    (git-remove-handled)))
-
-(defun git-toggle-show-unknown ()
-  "Toogle the option for showing unknown files."
-  (interactive)
-  (if (setq git-show-unknown (not git-show-unknown))
-      (progn
-        (message "Inserting unknown files...")
-        (git-run-ls-files-with-excludes git-status nil 'unknown "-o")
-        (git-refresh-files)
-        (git-refresh-ewoc-hf git-status)
-        (message "Inserting unknown files...done"))
-    (git-remove-handled)))
-
-(defun git-expand-directory (info)
-  "Expand the directory represented by INFO to list its files."
-  (when (eq (lsh (git-fileinfo->new-perm info) -9) ?\110)
-    (let ((dir (git-fileinfo->name info)))
-      (git-set-filenames-state git-status (list dir) nil)
-      (git-run-ls-files-with-excludes git-status (list (concat dir "/")) 'unknown "-o")
-      (git-refresh-files)
-      (git-refresh-ewoc-hf git-status)
-      t)))
-
-(defun git-setup-diff-buffer (buffer)
-  "Setup a buffer for displaying a diff."
-  (let ((dir default-directory))
-    (with-current-buffer buffer
-      (diff-mode)
-      (goto-char (point-min))
-      (setq default-directory dir)
-      (setq buffer-read-only t)))
-  (display-buffer buffer)
-  ; shrink window only if it displays the status buffer
-  (when (eq (window-buffer) (current-buffer))
-    (shrink-window-if-larger-than-buffer)))
-
-(defun git-diff-file ()
-  "Diff the marked file(s) against HEAD."
-  (interactive)
-  (let ((files (git-marked-files)))
-    (git-setup-diff-buffer
-     (apply #'git-run-command-buffer "*git-diff*" "diff-index" "-p" "-M" "HEAD" "--" (git-get-filenames files)))))
-
-(defun git-diff-file-merge-head (arg)
-  "Diff the marked file(s) against the first merge head (or the nth one with a numeric prefix)."
-  (interactive "p")
-  (let ((files (git-marked-files))
-        (merge-heads (git-get-merge-heads)))
-    (unless merge-heads (error "No merge in progress"))
-    (git-setup-diff-buffer
-     (apply #'git-run-command-buffer "*git-diff*" "diff-index" "-p" "-M"
-            (or (nth (1- arg) merge-heads) "HEAD") "--" (git-get-filenames files)))))
-
-(defun git-diff-unmerged-file (stage)
-  "Diff the marked unmerged file(s) against the specified stage."
-  (let ((files (git-marked-files)))
-    (git-setup-diff-buffer
-     (apply #'git-run-command-buffer "*git-diff*" "diff-files" "-p" stage "--" (git-get-filenames files)))))
-
-(defun git-diff-file-base ()
-  "Diff the marked unmerged file(s) against the common base file."
-  (interactive)
-  (git-diff-unmerged-file "-1"))
-
-(defun git-diff-file-mine ()
-  "Diff the marked unmerged file(s) against my pre-merge version."
-  (interactive)
-  (git-diff-unmerged-file "-2"))
-
-(defun git-diff-file-other ()
-  "Diff the marked unmerged file(s) against the other's pre-merge version."
-  (interactive)
-  (git-diff-unmerged-file "-3"))
-
-(defun git-diff-file-combined ()
-  "Do a combined diff of the marked unmerged file(s)."
-  (interactive)
-  (git-diff-unmerged-file "-c"))
-
-(defun git-diff-file-idiff ()
-  "Perform an interactive diff on the current file."
-  (interactive)
-  (let ((files (git-marked-files-state 'added 'deleted 'modified)))
-    (unless (eq 1 (length files))
-      (error "Cannot perform an interactive diff on multiple files."))
-    (let* ((filename (car (git-get-filenames files)))
-           (buff1 (find-file-noselect filename))
-           (buff2 (git-run-command-buffer (concat filename ".~HEAD~") "cat-file" "blob" (concat "HEAD:" filename))))
-      (ediff-buffers buff1 buff2))))
-
-(defun git-log-file ()
-  "Display a log of changes to the marked file(s)."
-  (interactive)
-  (let* ((files (git-marked-files))
-         (coding-system-for-read git-commits-coding-system)
-         (buffer (apply #'git-run-command-buffer "*git-log*" "rev-list" "--pretty" "HEAD" "--" (git-get-filenames files))))
-    (with-current-buffer buffer
-      ; (git-log-mode)  FIXME: implement log mode
-      (goto-char (point-min))
-      (setq buffer-read-only t))
-    (display-buffer buffer)))
-
-(defun git-log-edit-files ()
-  "Return a list of marked files for use in the log-edit buffer."
-  (with-current-buffer log-edit-parent-buffer
-    (git-get-filenames (git-marked-files-state 'added 'deleted 'modified))))
-
-(defun git-log-edit-diff ()
-  "Run a diff of the current files being committed from a log-edit buffer."
-  (with-current-buffer log-edit-parent-buffer
-    (git-diff-file)))
-
-(defun git-append-sign-off (name email)
-  "Append a Signed-off-by entry to the current buffer, avoiding duplicates."
-  (let ((sign-off (format "Signed-off-by: %s <%s>" name email))
-        (case-fold-search t))
-    (goto-char (point-min))
-    (unless (re-search-forward (concat "^" (regexp-quote sign-off)) nil t)
-      (goto-char (point-min))
-      (unless (re-search-forward "^Signed-off-by: " nil t)
-        (setq sign-off (concat "\n" sign-off)))
-      (goto-char (point-max))
-      (insert sign-off "\n"))))
-
-(defun git-setup-log-buffer (buffer &optional merge-heads author-name author-email subject date msg)
-  "Setup the log buffer for a commit."
-  (unless git-status (error "Not in git-status buffer."))
-  (let ((dir default-directory)
-        (committer-name (git-get-committer-name))
-        (committer-email (git-get-committer-email))
-        (sign-off git-append-signed-off-by))
-    (with-current-buffer buffer
-      (cd dir)
-      (erase-buffer)
-      (insert
-       (propertize
-        (format "Author: %s <%s>\n%s%s"
-                (or author-name committer-name)
-                (or author-email committer-email)
-                (if date (format "Date: %s\n" date) "")
-                (if merge-heads
-                    (format "Merge: %s\n"
-                            (mapconcat 'identity merge-heads " "))
-                  ""))
-        'face 'git-header-face)
-       (propertize git-log-msg-separator 'face 'git-separator-face)
-       "\n")
-      (when subject (insert subject "\n\n"))
-      (cond (msg (insert msg "\n"))
-            ((file-readable-p ".git/rebase-apply/msg")
-             (insert-file-contents ".git/rebase-apply/msg"))
-            ((file-readable-p ".git/MERGE_MSG")
-             (insert-file-contents ".git/MERGE_MSG")))
-      ; delete empty lines at end
-      (goto-char (point-min))
-      (when (re-search-forward "\n+\\'" nil t)
-        (replace-match "\n" t t))
-      (when sign-off (git-append-sign-off committer-name committer-email)))
-    buffer))
-
-(define-derived-mode git-log-edit-mode log-edit-mode "Git-Log-Edit"
-  "Major mode for editing git log messages.
-
-Set up git-specific `font-lock-keywords' for `log-edit-mode'."
-  (set (make-local-variable 'font-lock-defaults)
-       '(git-log-edit-font-lock-keywords t t)))
-
-(defun git-commit-file ()
-  "Commit the marked file(s), asking for a commit message."
-  (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (when (git-run-pre-commit-hook)
-    (let ((buffer (get-buffer-create "*git-commit*"))
-          (coding-system (git-get-commits-coding-system))
-          author-name author-email subject date)
-      (when (eq 0 (buffer-size buffer))
-        (when (file-readable-p ".git/rebase-apply/info")
-          (with-temp-buffer
-            (insert-file-contents ".git/rebase-apply/info")
-            (goto-char (point-min))
-            (when (re-search-forward "^Author: \\(.*\\)\nEmail: \\(.*\\)$" nil t)
-              (setq author-name (match-string 1))
-              (setq author-email (match-string 2)))
-            (goto-char (point-min))
-            (when (re-search-forward "^Subject: \\(.*\\)$" nil t)
-              (setq subject (match-string 1)))
-            (goto-char (point-min))
-            (when (re-search-forward "^Date: \\(.*\\)$" nil t)
-              (setq date (match-string 1)))))
-        (git-setup-log-buffer buffer (git-get-merge-heads) author-name author-email subject date))
-      (if (boundp 'log-edit-diff-function)
-         (log-edit 'git-do-commit nil '((log-edit-listfun . git-log-edit-files)
-                                        (log-edit-diff-function . git-log-edit-diff)) buffer 'git-log-edit-mode)
-       (log-edit 'git-do-commit nil 'git-log-edit-files buffer
-                  'git-log-edit-mode))
-      (setq paragraph-separate (concat (regexp-quote git-log-msg-separator) "$\\|Author: \\|Date: \\|Merge: \\|Signed-off-by: \\|\f\\|[        ]*$"))
-      (setq buffer-file-coding-system coding-system)
-      (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
-
-(defun git-setup-commit-buffer (commit)
-  "Setup the commit buffer with the contents of COMMIT."
-  (let (parents author-name author-email subject date msg)
-    (with-temp-buffer
-      (let ((coding-system (git-get-logoutput-coding-system)))
-        (git-call-process t "log" "-1" "--pretty=medium" "--abbrev=40" commit)
-        (goto-char (point-min))
-        (when (re-search-forward "^Merge: *\\(.*\\)$" nil t)
-          (setq parents (cdr (split-string (match-string 1) " +"))))
-        (when (re-search-forward "^Author: *\\(.*\\) <\\(.*\\)>$" nil t)
-          (setq author-name (match-string 1))
-          (setq author-email (match-string 2)))
-        (when (re-search-forward "^Date: *\\(.*\\)$" nil t)
-          (setq date (match-string 1)))
-        (while (re-search-forward "^    \\(.*\\)$" nil t)
-          (push (match-string 1) msg))
-        (setq msg (nreverse msg))
-        (setq subject (pop msg))
-        (while (and msg (zerop (length (car msg))) (pop msg)))))
-    (git-setup-log-buffer (get-buffer-create "*git-commit*")
-                          parents author-name author-email subject date
-                          (mapconcat #'identity msg "\n"))))
-
-(defun git-get-commit-files (commit)
-  "Retrieve a sorted list of files modified by COMMIT."
-  (let (files)
-    (with-temp-buffer
-      (git-call-process t "diff-tree" "-m" "-r" "-z" "--name-only" "--no-commit-id" "--root" commit)
-      (goto-char (point-min))
-      (while (re-search-forward "\\([^\0]*\\)\0" nil t 1)
-        (push (match-string 1) files)))
-    (sort files #'string-lessp)))
-
-(defun git-read-commit-name (prompt &optional default)
-  "Ask for a commit name, with completion for local branch, remote branch and tag."
-  (completing-read prompt
-                   (list* "HEAD" "ORIG_HEAD" "FETCH_HEAD" (mapcar #'car (git-for-each-ref)))
-                  nil nil nil nil default))
-
-(defun git-checkout (branch &optional merge)
-  "Checkout a branch, tag, or any commit.
-Use a prefix arg if git should merge while checking out."
-  (interactive
-   (list (git-read-commit-name "Checkout: ")
-         current-prefix-arg))
-  (unless git-status (error "Not in git-status buffer."))
-  (let ((args (list branch "--")))
-    (when merge (push "-m" args))
-    (when (apply #'git-call-process-display-error "checkout" args)
-      (git-update-status-files))))
-
-(defun git-branch (branch)
-  "Create a branch from the current HEAD and switch to it."
-  (interactive (list (git-read-commit-name "Branch: ")))
-  (unless git-status (error "Not in git-status buffer."))
-  (if (git-rev-parse (concat "refs/heads/" branch))
-      (if (yes-or-no-p (format "Branch %s already exists, replace it? " branch))
-          (and (git-call-process-display-error "branch" "-f" branch)
-               (git-call-process-display-error "checkout" branch))
-        (message "Canceled."))
-    (git-call-process-display-error "checkout" "-b" branch))
-    (git-refresh-ewoc-hf git-status))
-
-(defun git-amend-commit ()
-  "Undo the last commit on HEAD, and set things up to commit an
-amended version of it."
-  (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (when (git-empty-db-p) (error "No commit to amend."))
-  (let* ((commit (git-rev-parse "HEAD"))
-         (files (git-get-commit-files commit)))
-    (when (if (git-rev-parse "HEAD^")
-              (git-call-process-display-error "reset" "--soft" "HEAD^")
-            (and (git-update-ref "ORIG_HEAD" commit)
-                 (git-update-ref "HEAD" nil commit)))
-      (git-update-status-files files t)
-      (git-setup-commit-buffer commit)
-      (git-commit-file))))
-
-(defun git-cherry-pick-commit (arg)
-  "Cherry-pick a commit."
-  (interactive (list (git-read-commit-name "Cherry-pick commit: ")))
-  (unless git-status (error "Not in git-status buffer."))
-  (let ((commit (git-rev-parse (concat arg "^0"))))
-    (unless commit (error "Not a valid commit '%s'." arg))
-    (when (git-rev-parse (concat commit "^2"))
-      (error "Cannot cherry-pick a merge commit."))
-    (let ((files (git-get-commit-files commit))
-          (ok (git-call-process-display-error "cherry-pick" "-n" commit)))
-      (git-update-status-files files ok)
-      (with-current-buffer (git-setup-commit-buffer commit)
-        (goto-char (point-min))
-        (if (re-search-forward "^\n*Signed-off-by:" nil t 1)
-            (goto-char (match-beginning 0))
-          (goto-char (point-max)))
-        (insert "(cherry picked from commit " commit ")\n"))
-      (when ok (git-commit-file)))))
-
-(defun git-revert-commit (arg)
-  "Revert a commit."
-  (interactive (list (git-read-commit-name "Revert commit: ")))
-  (unless git-status (error "Not in git-status buffer."))
-  (let ((commit (git-rev-parse (concat arg "^0"))))
-    (unless commit (error "Not a valid commit '%s'." arg))
-    (when (git-rev-parse (concat commit "^2"))
-      (error "Cannot revert a merge commit."))
-    (let ((files (git-get-commit-files commit))
-          (subject (git-get-commit-description commit))
-          (ok (git-call-process-display-error "revert" "-n" commit)))
-      (git-update-status-files files ok)
-      (when (string-match "^[0-9a-f]+ - \\(.*\\)$" subject)
-        (setq subject (match-string 1 subject)))
-      (git-setup-log-buffer (get-buffer-create "*git-commit*")
-                            (git-get-merge-heads) nil nil (format "Revert \"%s\"" subject) nil
-                            (format "This reverts commit %s.\n" commit))
-      (when ok (git-commit-file)))))
-
-(defun git-find-file ()
-  "Visit the current file in its own buffer."
-  (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (let ((info (ewoc-data (ewoc-locate git-status))))
-    (unless (git-expand-directory info)
-      (find-file (git-fileinfo->name info))
-      (when (eq 'unmerged (git-fileinfo->state info))
-        (smerge-mode 1)))))
-
-(defun git-find-file-other-window ()
-  "Visit the current file in its own buffer in another window."
-  (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (let ((info (ewoc-data (ewoc-locate git-status))))
-    (find-file-other-window (git-fileinfo->name info))
-    (when (eq 'unmerged (git-fileinfo->state info))
-      (smerge-mode))))
-
-(defun git-find-file-imerge ()
-  "Visit the current file in interactive merge mode."
-  (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (let ((info (ewoc-data (ewoc-locate git-status))))
-    (find-file (git-fileinfo->name info))
-    (smerge-ediff)))
-
-(defun git-view-file ()
-  "View the current file in its own buffer."
-  (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (let ((info (ewoc-data (ewoc-locate git-status))))
-    (view-file (git-fileinfo->name info))))
-
-(defun git-refresh-status ()
-  "Refresh the git status buffer."
-  (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (message "Refreshing git status...")
-  (git-update-status-files)
-  (message "Refreshing git status...done"))
-
-(defun git-status-quit ()
-  "Quit git-status mode."
-  (interactive)
-  (bury-buffer))
-
-;;;; Major Mode
-;;;; ------------------------------------------------------------
-
-(defvar git-status-mode-hook nil
-  "Run after `git-status-mode' is setup.")
-
-(defvar git-status-mode-map nil
-  "Keymap for git major mode.")
-
-(defvar git-status nil
-  "List of all files managed by the git-status mode.")
-
-(unless git-status-mode-map
-  (let ((map (make-keymap))
-        (commit-map (make-sparse-keymap))
-        (diff-map (make-sparse-keymap))
-        (toggle-map (make-sparse-keymap)))
-    (suppress-keymap map)
-    (define-key map "?"   'git-help)
-    (define-key map "h"   'git-help)
-    (define-key map " "   'git-next-file)
-    (define-key map "a"   'git-add-file)
-    (define-key map "c"   'git-commit-file)
-    (define-key map "\C-c" commit-map)
-    (define-key map "d"    diff-map)
-    (define-key map "="   'git-diff-file)
-    (define-key map "f"   'git-find-file)
-    (define-key map "\r"  'git-find-file)
-    (define-key map "g"   'git-refresh-status)
-    (define-key map "i"   'git-ignore-file)
-    (define-key map "I"   'git-insert-file)
-    (define-key map "l"   'git-log-file)
-    (define-key map "m"   'git-mark-file)
-    (define-key map "M"   'git-mark-all)
-    (define-key map "n"   'git-next-file)
-    (define-key map "N"   'git-next-unmerged-file)
-    (define-key map "o"   'git-find-file-other-window)
-    (define-key map "p"   'git-prev-file)
-    (define-key map "P"   'git-prev-unmerged-file)
-    (define-key map "q"   'git-status-quit)
-    (define-key map "r"   'git-remove-file)
-    (define-key map "t"    toggle-map)
-    (define-key map "T"   'git-toggle-all-marks)
-    (define-key map "u"   'git-unmark-file)
-    (define-key map "U"   'git-revert-file)
-    (define-key map "v"   'git-view-file)
-    (define-key map "x"   'git-remove-handled)
-    (define-key map "\C-?" 'git-unmark-file-up)
-    (define-key map "\M-\C-?" 'git-unmark-all)
-    ; the commit submap
-    (define-key commit-map "\C-a" 'git-amend-commit)
-    (define-key commit-map "\C-b" 'git-branch)
-    (define-key commit-map "\C-o" 'git-checkout)
-    (define-key commit-map "\C-p" 'git-cherry-pick-commit)
-    (define-key commit-map "\C-v" 'git-revert-commit)
-    ; the diff submap
-    (define-key diff-map "b" 'git-diff-file-base)
-    (define-key diff-map "c" 'git-diff-file-combined)
-    (define-key diff-map "=" 'git-diff-file)
-    (define-key diff-map "e" 'git-diff-file-idiff)
-    (define-key diff-map "E" 'git-find-file-imerge)
-    (define-key diff-map "h" 'git-diff-file-merge-head)
-    (define-key diff-map "m" 'git-diff-file-mine)
-    (define-key diff-map "o" 'git-diff-file-other)
-    ; the toggle submap
-    (define-key toggle-map "u" 'git-toggle-show-uptodate)
-    (define-key toggle-map "i" 'git-toggle-show-ignored)
-    (define-key toggle-map "k" 'git-toggle-show-unknown)
-    (define-key toggle-map "m" 'git-toggle-all-marks)
-    (setq git-status-mode-map map))
-  (easy-menu-define git-menu git-status-mode-map
-    "Git Menu"
-    `("Git"
-      ["Refresh" git-refresh-status t]
-      ["Commit" git-commit-file t]
-      ["Checkout..." git-checkout t]
-      ["New Branch..." git-branch t]
-      ["Cherry-pick Commit..." git-cherry-pick-commit t]
-      ["Revert Commit..." git-revert-commit t]
-      ("Merge"
-       ["Next Unmerged File" git-next-unmerged-file t]
-       ["Prev Unmerged File" git-prev-unmerged-file t]
-       ["Interactive Merge File" git-find-file-imerge t]
-       ["Diff Against Common Base File" git-diff-file-base t]
-       ["Diff Combined" git-diff-file-combined t]
-       ["Diff Against Merge Head" git-diff-file-merge-head t]
-       ["Diff Against Mine" git-diff-file-mine t]
-       ["Diff Against Other" git-diff-file-other t])
-      "--------"
-      ["Add File" git-add-file t]
-      ["Revert File" git-revert-file t]
-      ["Ignore File" git-ignore-file t]
-      ["Remove File" git-remove-file t]
-      ["Insert File" git-insert-file t]
-      "--------"
-      ["Find File" git-find-file t]
-      ["View File" git-view-file t]
-      ["Diff File" git-diff-file t]
-      ["Interactive Diff File" git-diff-file-idiff t]
-      ["Log" git-log-file t]
-      "--------"
-      ["Mark" git-mark-file t]
-      ["Mark All" git-mark-all t]
-      ["Unmark" git-unmark-file t]
-      ["Unmark All" git-unmark-all t]
-      ["Toggle All Marks" git-toggle-all-marks t]
-      ["Hide Handled Files" git-remove-handled t]
-      "--------"
-      ["Show Uptodate Files" git-toggle-show-uptodate :style toggle :selected git-show-uptodate]
-      ["Show Ignored Files" git-toggle-show-ignored :style toggle :selected git-show-ignored]
-      ["Show Unknown Files" git-toggle-show-unknown :style toggle :selected git-show-unknown]
-      "--------"
-      ["Quit" git-status-quit t])))
-
-
-;; git mode should only run in the *git status* buffer
-(put 'git-status-mode 'mode-class 'special)
-
-(defun git-status-mode ()
-  "Major mode for interacting with Git.
-Commands:
-\\{git-status-mode-map}"
-  (kill-all-local-variables)
-  (buffer-disable-undo)
-  (setq mode-name "git status"
-        major-mode 'git-status-mode
-        goal-column 17
-        buffer-read-only t)
-  (use-local-map git-status-mode-map)
-  (let ((buffer-read-only nil))
-    (erase-buffer)
-  (let ((status (ewoc-create 'git-fileinfo-prettyprint "" "")))
-    (set (make-local-variable 'git-status) status))
-  (set (make-local-variable 'list-buffers-directory) default-directory)
-  (make-local-variable 'git-show-uptodate)
-  (make-local-variable 'git-show-ignored)
-  (make-local-variable 'git-show-unknown)
-  (run-hooks 'git-status-mode-hook)))
-
-(defun git-find-status-buffer (dir)
-  "Find the git status buffer handling a specified directory."
-  (let ((list (buffer-list))
-        (fulldir (expand-file-name dir))
-        found)
-    (while (and list (not found))
-      (let ((buffer (car list)))
-        (with-current-buffer buffer
-          (when (and list-buffers-directory
-                     (string-equal fulldir (expand-file-name list-buffers-directory))
-                    (eq major-mode 'git-status-mode))
-            (setq found buffer))))
-      (setq list (cdr list)))
-    found))
-
-(defun git-status (dir)
-  "Entry point into git-status mode."
-  (interactive "DSelect directory: ")
-  (setq dir (git-get-top-dir dir))
-  (if (file-exists-p (concat (file-name-as-directory dir) ".git"))
-      (let ((buffer (or (and git-reuse-status-buffer (git-find-status-buffer dir))
-                        (create-file-buffer (expand-file-name "*git-status*" dir)))))
-        (switch-to-buffer buffer)
-        (cd dir)
-        (git-status-mode)
-        (git-refresh-status)
-        (goto-char (point-min))
-        (add-hook 'after-save-hook 'git-update-saved-file))
-    (message "%s is not a git working tree." dir)))
-
-(defun git-update-saved-file ()
-  "Update the corresponding git-status buffer when a file is saved.
-Meant to be used in `after-save-hook'."
-  (let* ((file (expand-file-name buffer-file-name))
-         (dir (condition-case nil (git-get-top-dir (file-name-directory file)) (error nil)))
-         (buffer (and dir (git-find-status-buffer dir))))
-    (when buffer
-      (with-current-buffer buffer
-        (let ((filename (file-relative-name file dir)))
-          ; skip files located inside the .git directory
-          (unless (string-match "^\\.git/" filename)
-            (git-call-process nil "add" "--refresh" "--" filename)
-            (git-update-status-files (list filename))))))))
-
-(defun git-help ()
-  "Display help for Git mode."
-  (interactive)
-  (describe-function 'git-status-mode))
-
-(provide 'git)
-;;; git.el ends here
index fb0fd0b84bee8e55973c1da3f34c83b7fbf16562..b2ea80f9edcafab01de5d4a5339341cd542034f3 100755 (executable)
@@ -13,7 +13,7 @@
 use 5.008;
 use strict;
 use warnings;
-use Git::Error qw(:try);
+use Git::LoadCPAN::Error qw(:try);
 use File::Basename qw(dirname);
 use File::Copy;
 use File::Find;
index d6dd64662ce4d09296b61708b020a71eef3488f6..21565c3c525c64793282d8c3f029733615a87fc2 100644 (file)
@@ -13,6 +13,7 @@
 #include "refs.h"
 #include "fmt-merge-msg.h"
 #include "commit.h"
+#include "argv-array.h"
 
 int trust_executable_bit = 1;
 int trust_ctime = 1;
@@ -147,10 +148,35 @@ static char *expand_namespace(const char *raw_namespace)
        return strbuf_detach(&buf, NULL);
 }
 
-void setup_git_env(void)
+/*
+ * Wrapper of getenv() that returns a strdup value. This value is kept
+ * in argv to be freed later.
+ */
+static const char *getenv_safe(struct argv_array *argv, const char *name)
+{
+       const char *value = getenv(name);
+
+       if (!value)
+               return NULL;
+
+       argv_array_push(argv, value);
+       return argv->argv[argv->argc - 1];
+}
+
+void setup_git_env(const char *git_dir)
 {
        const char *shallow_file;
        const char *replace_ref_base;
+       struct set_gitdir_args args = { NULL };
+       struct argv_array to_free = ARGV_ARRAY_INIT;
+
+       args.commondir = getenv_safe(&to_free, GIT_COMMON_DIR_ENVIRONMENT);
+       args.object_dir = getenv_safe(&to_free, DB_ENVIRONMENT);
+       args.graft_file = getenv_safe(&to_free, GRAFT_ENVIRONMENT);
+       args.index_file = getenv_safe(&to_free, INDEX_ENVIRONMENT);
+       args.alternate_db = getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT);
+       repo_set_gitdir(the_repository, git_dir, &args);
+       argv_array_clear(&to_free);
 
        if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
                check_replace_refs = 0;
@@ -300,8 +326,7 @@ int set_git_dir(const char *path)
 {
        if (setenv(GIT_DIR_ENVIRONMENT, path, 1))
                return error("Could not set GIT_DIR to '%s'", path);
-       repo_set_gitdir(the_repository, path);
-       setup_git_env();
+       setup_git_env(path);
        return 0;
 }
 
index d97461296d5692521cd038f9ffa0b5f5c3c3f3dd..2ea358861dacb91697137dc22891122e35b6a472 100644 (file)
@@ -711,6 +711,28 @@ static void mark_alternate_complete(struct object *obj)
        mark_complete(&obj->oid);
 }
 
+struct loose_object_iter {
+       struct oidset *loose_object_set;
+       struct ref *refs;
+};
+
+/*
+ *  If the number of refs is not larger than the number of loose objects,
+ *  this function stops inserting.
+ */
+static int add_loose_objects_to_set(const struct object_id *oid,
+                                   const char *path,
+                                   void *data)
+{
+       struct loose_object_iter *iter = data;
+       oidset_insert(iter->loose_object_set, oid);
+       if (iter->refs == NULL)
+               return 1;
+
+       iter->refs = iter->refs->next;
+       return 0;
+}
+
 static int everything_local(struct fetch_pack_args *args,
                            struct ref **refs,
                            struct ref **sought, int nr_sought)
@@ -719,16 +741,31 @@ static int everything_local(struct fetch_pack_args *args,
        int retval;
        int old_save_commit_buffer = save_commit_buffer;
        timestamp_t cutoff = 0;
+       struct oidset loose_oid_set = OIDSET_INIT;
+       int use_oidset = 0;
+       struct loose_object_iter iter = {&loose_oid_set, *refs};
+
+       /* Enumerate all loose objects or know refs are not so many. */
+       use_oidset = !for_each_loose_object(add_loose_objects_to_set,
+                                           &iter, 0);
 
        save_commit_buffer = 0;
 
        for (ref = *refs; ref; ref = ref->next) {
                struct object *o;
+               unsigned int flags = OBJECT_INFO_QUICK;
 
-               if (!has_object_file_with_flags(&ref->old_oid,
-                                               OBJECT_INFO_QUICK))
-                       continue;
+               if (use_oidset &&
+                   !oidset_contains(&loose_oid_set, &ref->old_oid)) {
+                       /*
+                        * I know this does not exist in the loose form,
+                        * so check if it exists in a non-loose form.
+                        */
+                       flags |= OBJECT_INFO_IGNORE_LOOSE;
+               }
 
+               if (!has_object_file_with_flags(&ref->old_oid, flags))
+                       continue;
                o = parse_object(&ref->old_oid);
                if (!o)
                        continue;
@@ -744,6 +781,8 @@ static int everything_local(struct fetch_pack_args *args,
                }
        }
 
+       oidset_clear(&loose_oid_set);
+
        if (!args->no_dependents) {
                if (!args->deepen) {
                        for_each_ref(mark_complete_oid, NULL);
index 331c8dfeac3cac2fd2d9d9287d5c487669fe80a0..47a9cb81b80f7eae7799f3771019ee5c6d759f0f 100644 (file)
@@ -155,13 +155,19 @@ reschedule_last_action () {
 append_todo_help () {
        gettext "
 Commands:
-p, pick = use commit
-r, reword = use commit, but edit the commit message
-e, edit = use commit, but stop for amending
-s, squash = use commit, but meld into previous commit
-f, fixup = like \"squash\", but discard this commit's log message
-x, exec = run command (the rest of the line) using shell
-d, drop = remove commit
+p, pick <commit> = use commit
+r, reword <commit> = use commit, but edit the commit message
+e, edit <commit> = use commit, but stop for amending
+s, squash <commit> = use commit, but meld into previous commit
+f, fixup <commit> = like \"squash\", but discard this commit's log message
+x, exec <commit> = run command (the rest of the line) using shell
+d, drop <commit> = remove commit
+l, label <label> = label current HEAD with a name
+t, reset <label> = reset HEAD to a label
+m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
+.       create a merge commit using the original merge commit's
+.       message (or the oneline, if no original merge commit was
+.       specified). Use -c <commit> to reword the commit message.
 
 These lines can be re-ordered; they are executed from top to bottom.
 " | git stripspace --comment-lines >>"$todo"
@@ -906,6 +912,8 @@ fi
 if test t != "$preserve_merges"
 then
        git rebase--helper --make-script ${keep_empty:+--keep-empty} \
+               ${recreate_merges:+--recreate-merges} \
+               ${rebase_cousins:+--rebase-cousins} \
                $revisions ${restrict_revision+^$restrict_revision} >"$todo" ||
        die "$(gettext "Could not generate todo list")"
 else
index a1f6e5de6a3ed1fe9a6217a136611682f3db6582..b24e4d5d87640232f69198228da6037a49aebfce 100755 (executable)
@@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat
 autostash          automatically stash/stash pop before and after
 fork-point         use 'merge-base --fork-point' to refine upstream
 onto=!             rebase onto given branch instead of upstream
+recreate-merges?   try to recreate merges instead of skipping them
 p,preserve-merges! try to recreate merges instead of ignoring them
 s,strategy=!       use the given merge strategy
 no-ff!             cherry-pick all commits, even if unchanged
@@ -88,6 +89,8 @@ type=
 state_dir=
 # One of {'', continue, skip, abort}, as parsed from command line
 action=
+recreate_merges=
+rebase_cousins=
 preserve_merges=
 autosquash=
 keep_empty=
@@ -269,6 +272,19 @@ do
        --allow-empty-message)
                allow_empty_message=--allow-empty-message
                ;;
+       --recreate-merges)
+               recreate_merges=t
+               test -z "$interactive_rebase" && interactive_rebase=implied
+               ;;
+       --recreate-merges=*)
+               recreate_merges=t
+               case "${1#*=}" in
+               rebase-cousins) rebase_cousins=t;;
+               no-rebase-cousins) rebase_cousins=;;
+               *) die "Unknown mode: $1";;
+               esac
+               test -z "$interactive_rebase" && interactive_rebase=implied
+               ;;
        --preserve-merges)
                preserve_merges=t
                test -z "$interactive_rebase" && interactive_rebase=implied
index 435c7c9e26d75e8f6a4f635753f248fcf8106790..2fa7818ca9a8ac7363d17aef17b864f7361b3eb8 100755 (executable)
 use Term::ANSIColor;
 use File::Temp qw/ tempdir tempfile /;
 use File::Spec::Functions qw(catdir catfile);
-use Git::Error qw(:try);
+use Git::LoadCPAN::Error qw(:try);
 use Cwd qw(abs_path cwd);
 use Git;
 use Git::I18N;
-use Git::Mail::Address;
+use Net::Domain ();
+use Net::SMTP ();
+use Git::LoadCPAN::Mail::Address;
 
 Getopt::Long::Configure qw/ pass_through /;
 
@@ -57,6 +59,7 @@ sub usage {
     --[no-]cc               <str>  * Email Cc:
     --[no-]bcc              <str>  * Email Bcc:
     --subject               <str>  * Email "Subject:"
+    --reply-to              <str>  * Email "Reply-To:"
     --in-reply-to           <str>  * Email "In-Reply-To:"
     --[no-]xmailer                 * Add "X-Mailer:" header (default).
     --[no-]annotate                * Review each patch that will be sent in an editor.
@@ -167,13 +170,13 @@ sub format_2822_time {
 
 # Variables we fill in automatically, or via prompting:
 my (@to,$no_to,@initial_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh,
-       $initial_reply_to,$initial_subject,@files,
+       $initial_in_reply_to,$reply_to,$initial_subject,@files,
        $author,$sender,$smtp_authpass,$annotate,$use_xmailer,$compose,$time);
 
 my $envelope_sender;
 
 # Example reply to:
-#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
+#$initial_in_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
 
 my $repo = eval { Git->repository() };
 my @repo = $repo ? ($repo) : ();
@@ -315,7 +318,8 @@ sub signal_handler {
     if !$help and $dump_aliases and @ARGV;
 $rc = GetOptions(
                    "sender|from=s" => \$sender,
-                    "in-reply-to=s" => \$initial_reply_to,
+                    "in-reply-to=s" => \$initial_in_reply_to,
+                   "reply-to=s" => \$reply_to,
                    "subject=s" => \$initial_subject,
                    "to=s" => \@initial_to,
                    "to-cmd=s" => \$to_cmd,
@@ -681,7 +685,8 @@ sub get_patch_subject {
 
        my $tpl_sender = $sender || $repoauthor || $repocommitter || '';
        my $tpl_subject = $initial_subject || '';
-       my $tpl_reply_to = $initial_reply_to || '';
+       my $tpl_in_reply_to = $initial_in_reply_to || '';
+       my $tpl_reply_to = $reply_to || '';
 
        print $c <<EOT1, Git::prefix_lines("GIT: ", __ <<EOT2), <<EOT3;
 From $tpl_sender # This line is ignored.
@@ -693,8 +698,9 @@ sub get_patch_subject {
 Clear the body content if you don't wish to send a summary.
 EOT2
 From: $tpl_sender
+Reply-To: $tpl_reply_to
 Subject: $tpl_subject
-In-Reply-To: $tpl_reply_to
+In-Reply-To: $tpl_in_reply_to
 
 EOT3
        for my $f (@files) {
@@ -708,57 +714,73 @@ sub get_patch_subject {
                do_edit($compose_filename);
        }
 
-       open my $c2, ">", $compose_filename . ".final"
-               or die sprintf(__("Failed to open %s.final: %s"), $compose_filename, $!);
-
        open $c, "<", $compose_filename
                or die sprintf(__("Failed to open %s: %s"), $compose_filename, $!);
 
-       my $need_8bit_cte = file_has_nonascii($compose_filename);
-       my $in_body = 0;
-       my $summary_empty = 1;
        if (!defined $compose_encoding) {
                $compose_encoding = "UTF-8";
        }
-       while(<$c>) {
-               next if m/^GIT:/;
-               if ($in_body) {
-                       $summary_empty = 0 unless (/^\n$/);
-               } elsif (/^\n$/) {
-                       $in_body = 1;
-                       if ($need_8bit_cte) {
-                               print $c2 "MIME-Version: 1.0\n",
-                                        "Content-Type: text/plain; ",
-                                          "charset=$compose_encoding\n",
-                                        "Content-Transfer-Encoding: 8bit\n";
-                       }
-               } elsif (/^MIME-Version:/i) {
-                       $need_8bit_cte = 0;
-               } elsif (/^Subject:\s*(.+)\s*$/i) {
-                       $initial_subject = $1;
-                       my $subject = $initial_subject;
-                       $_ = "Subject: " .
-                               quote_subject($subject, $compose_encoding) .
-                               "\n";
-               } elsif (/^In-Reply-To:\s*(.+)\s*$/i) {
-                       $initial_reply_to = $1;
-                       next;
-               } elsif (/^From:\s*(.+)\s*$/i) {
-                       $sender = $1;
-                       next;
-               } elsif (/^(?:To|Cc|Bcc):/i) {
-                       print __("To/Cc/Bcc fields are not interpreted yet, they have been ignored\n");
-                       next;
+
+       my %parsed_email;
+       while (my $line = <$c>) {
+               next if $line =~ m/^GIT:/;
+               parse_header_line($line, \%parsed_email);
+               if ($line =~ /^$/) {
+                       $parsed_email{'body'} = filter_body($c);
                }
-               print $c2 $_;
        }
        close $c;
-       close $c2;
 
-       if ($summary_empty) {
+       open my $c2, ">", $compose_filename . ".final"
+       or die sprintf(__("Failed to open %s.final: %s"), $compose_filename, $!);
+
+
+       if ($parsed_email{'From'}) {
+               $sender = delete($parsed_email{'From'});
+       }
+       if ($parsed_email{'In-Reply-To'}) {
+               $initial_in_reply_to = delete($parsed_email{'In-Reply-To'});
+       }
+       if ($parsed_email{'Reply-To'}) {
+               $reply_to = delete($parsed_email{'Reply-To'});
+       }
+       if ($parsed_email{'Subject'}) {
+               $initial_subject = delete($parsed_email{'Subject'});
+               print $c2 "Subject: " .
+                       quote_subject($initial_subject, $compose_encoding) .
+                       "\n";
+       }
+
+       if ($parsed_email{'MIME-Version'}) {
+               print $c2 "MIME-Version: $parsed_email{'MIME-Version'}\n",
+                               "Content-Type: $parsed_email{'Content-Type'};\n",
+                               "Content-Transfer-Encoding: $parsed_email{'Content-Transfer-Encoding'}\n";
+               delete($parsed_email{'MIME-Version'});
+               delete($parsed_email{'Content-Type'});
+               delete($parsed_email{'Content-Transfer-Encoding'});
+       } elsif (file_has_nonascii($compose_filename)) {
+               my $content_type = (delete($parsed_email{'Content-Type'}) or
+                       "text/plain; charset=$compose_encoding");
+               print $c2 "MIME-Version: 1.0\n",
+                       "Content-Type: $content_type\n",
+                       "Content-Transfer-Encoding: 8bit\n";
+       }
+       # Preserve unknown headers
+       foreach my $key (keys %parsed_email) {
+               next if $key eq 'body';
+               print $c2 "$key: $parsed_email{$key}";
+       }
+
+       if ($parsed_email{'body'}) {
+               print $c2 "\n$parsed_email{'body'}\n";
+               delete($parsed_email{'body'});
+       } else {
                print __("Summary email is empty, skipping it\n");
                $compose = -1;
        }
+
+       close $c2;
+
 } elsif ($annotate) {
        do_edit(@files);
 }
@@ -797,6 +819,32 @@ sub ask {
        return;
 }
 
+sub parse_header_line {
+       my $lines = shift;
+       my $parsed_line = shift;
+       my $addr_pat = join "|", qw(To Cc Bcc);
+
+       foreach (split(/\n/, $lines)) {
+               if (/^($addr_pat):\s*(.+)$/i) {
+                       $parsed_line->{$1} = [ parse_address_line($2) ];
+               } elsif (/^([^:]*):\s*(.+)\s*$/i) {
+                       $parsed_line->{$1} = $2;
+               }
+       }
+}
+
+sub filter_body {
+       my $c = shift;
+       my $body = "";
+       while (my $body_line = <$c>) {
+               if ($body_line !~ m/^GIT:/) {
+                       $body .= $body_line;
+               }
+       }
+       return $body;
+}
+
+
 my %broken_encoding;
 
 sub file_declares_8bit_cte {
@@ -877,16 +925,22 @@ sub expand_one_alias {
 @initial_cc = process_address_list(@initial_cc);
 @bcclist = process_address_list(@bcclist);
 
-if ($thread && !defined $initial_reply_to && $prompting) {
-       $initial_reply_to = ask(
+if ($thread && !defined $initial_in_reply_to && $prompting) {
+       $initial_in_reply_to = ask(
                __("Message-ID to be used as In-Reply-To for the first email (if any)? "),
                default => "",
                valid_re => qr/\@.*\./, confirm_only => 1);
 }
-if (defined $initial_reply_to) {
-       $initial_reply_to =~ s/^\s*<?//;
-       $initial_reply_to =~ s/>?\s*$//;
-       $initial_reply_to = "<$initial_reply_to>" if $initial_reply_to ne '';
+if (defined $initial_in_reply_to) {
+       $initial_in_reply_to =~ s/^\s*<?//;
+       $initial_in_reply_to =~ s/>?\s*$//;
+       $initial_in_reply_to = "<$initial_in_reply_to>" if $initial_in_reply_to ne '';
+}
+
+if (defined $reply_to) {
+       $reply_to =~ s/^\s+|\s+$//g;
+       ($reply_to) = expand_aliases($reply_to);
+       $reply_to = sanitize_address($reply_to);
 }
 
 if (!defined $smtp_server) {
@@ -906,7 +960,7 @@ sub expand_one_alias {
 }
 
 # Variables we set as part of the loop over files
-our ($message_id, %mail, $subject, $reply_to, $references, $message,
+our ($message_id, %mail, $subject, $in_reply_to, $references, $message,
        $needs_confirm, $message_num, $ask_default);
 
 sub extract_valid_address {
@@ -1147,10 +1201,8 @@ sub valid_fqdn {
 sub maildomain_net {
        my $maildomain;
 
-       if (eval { require Net::Domain; 1 }) {
-               my $domain = Net::Domain::domainname();
-               $maildomain = $domain if valid_fqdn($domain);
-       }
+       my $domain = Net::Domain::domainname();
+       $maildomain = $domain if valid_fqdn($domain);
 
        return $maildomain;
 }
@@ -1158,17 +1210,15 @@ sub maildomain_net {
 sub maildomain_mta {
        my $maildomain;
 
-       if (eval { require Net::SMTP; 1 }) {
-               for my $host (qw(mailhost localhost)) {
-                       my $smtp = Net::SMTP->new($host);
-                       if (defined $smtp) {
-                               my $domain = $smtp->domain;
-                               $smtp->quit;
+       for my $host (qw(mailhost localhost)) {
+               my $smtp = Net::SMTP->new($host);
+               if (defined $smtp) {
+                       my $domain = $smtp->domain;
+                       $smtp->quit;
 
-                               $maildomain = $domain if valid_fqdn($domain);
+                       $maildomain = $domain if valid_fqdn($domain);
 
-                               last if $maildomain;
-                       }
+                       last if $maildomain;
                }
        }
 
@@ -1315,11 +1365,14 @@ sub send_message {
        if ($use_xmailer) {
                $header .= "X-Mailer: git-send-email $gitversion\n";
        }
-       if ($reply_to) {
+       if ($in_reply_to) {
 
-               $header .= "In-Reply-To: $reply_to\n";
+               $header .= "In-Reply-To: $in_reply_to\n";
                $header .= "References: $references\n";
        }
+       if ($reply_to) {
+               $header .= "Reply-To: $reply_to\n";
+       }
        if (@xh) {
                $header .= join("\n", @xh) . "\n";
        }
@@ -1494,8 +1547,8 @@ sub send_message {
        return 1;
 }
 
-$reply_to = $initial_reply_to;
-$references = $initial_reply_to || '';
+$in_reply_to = $initial_in_reply_to;
+$references = $initial_in_reply_to || '';
 $subject = $initial_subject;
 $message_num = 0;
 
@@ -1705,9 +1758,9 @@ sub send_message {
 
        # set up for the next message
        if ($thread && $message_was_sent &&
-               ($chain_reply_to || !defined $reply_to || length($reply_to) == 0 ||
+               ($chain_reply_to || !defined $in_reply_to || length($in_reply_to) == 0 ||
                $message_num == 1)) {
-               $reply_to = $message_id;
+               $in_reply_to = $message_id;
                if (length $references > 0) {
                        $references .= "\n $message_id";
                } else {
diff --git a/git.c b/git.c
index 96cd734f1282161eebde954f7e638cf8dd8c3516..ceaa58ef40e536f1290cce3ad1223004063e41a6 100644 (file)
--- a/git.c
+++ b/git.c
@@ -389,7 +389,7 @@ static struct cmd_struct commands[] = {
        { "column", cmd_column, RUN_SETUP_GENTLY },
        { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
        { "commit-tree", cmd_commit_tree, RUN_SETUP },
-       { "config", cmd_config, RUN_SETUP_GENTLY },
+       { "config", cmd_config, RUN_SETUP_GENTLY | DELAY_PAGER_CONFIG },
        { "count-objects", cmd_count_objects, RUN_SETUP },
        { "credential", cmd_credential, RUN_SETUP_GENTLY },
        { "describe", cmd_describe, RUN_SETUP },
index 408f2859d3c42becc333b8c0e4ee6366de7fdb14..a58e6b3c44b0ef6175df1417a3ed6f7775bd9953 100644 (file)
@@ -29,12 +29,11 @@ Requirements
 ------------
 
  - Core git tools
- - Perl
+ - Perl 5.8
  - Perl modules: CGI, Encode, Fcntl, File::Find, File::Basename.
  - web server
 
 The following optional Perl modules are required for extra features
- - Digest::MD5 - for gravatar support
  - CGI::Fast and FCGI - for running gitweb as FastCGI script
  - HTML::TagCloud - for fancy tag cloud in project list view
  - HTTP::Date or Time::ParseDate - to support If-Modified-Since for feeds
index 2417057f2bc61a98e68dc6c817e456a21bf6044e..2594a4badb3d7b942b28b57ca036650328a1b050 100755 (executable)
@@ -20,6 +20,8 @@
 use File::Find qw();
 use File::Basename qw(basename);
 use Time::HiRes qw(gettimeofday tv_interval);
+use Digest::MD5 qw(md5_hex);
+
 binmode STDOUT, ':utf8';
 
 if (!defined($CGI::VERSION) || $CGI::VERSION < 4.08) {
@@ -490,7 +492,6 @@ sub evaluate_uri {
        # Currently available providers are gravatar and picon.
        # If an unknown provider is specified, the feature is disabled.
 
-       # Gravatar depends on Digest::MD5.
        # Picon currently relies on the indiana.edu database.
 
        # To enable system wide have in $GITWEB_CONFIG
@@ -1166,18 +1167,8 @@ sub configure_gitweb_features {
        our @snapshot_fmts = gitweb_get_feature('snapshot');
        @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
 
-       # check that the avatar feature is set to a known provider name,
-       # and for each provider check if the dependencies are satisfied.
-       # if the provider name is invalid or the dependencies are not met,
-       # reset $git_avatar to the empty string.
        our ($git_avatar) = gitweb_get_feature('avatar');
-       if ($git_avatar eq 'gravatar') {
-               $git_avatar = '' unless (eval { require Digest::MD5; 1; });
-       } elsif ($git_avatar eq 'picon') {
-               # no dependencies
-       } else {
-               $git_avatar = '';
-       }
+       $git_avatar = '' unless $git_avatar =~ /^(?:gravatar|picon)$/s;
 
        our @extra_branch_refs = gitweb_get_feature('extra-branch-refs');
        @extra_branch_refs = filter_and_validate_refs (@extra_branch_refs);
@@ -2167,7 +2158,7 @@ sub gravatar_url {
        my $size = shift;
        $avatar_cache{$email} ||=
                "//www.gravatar.com/avatar/" .
-                       Digest::MD5::md5_hex($email) . "?s=";
+                       md5_hex($email) . "?s=";
        return $avatar_cache{$email} . $size;
 }
 
index 129577987ba25402a6b9494b103a4643e2b37e68..8fa7efee2fd3ade38de97d36aad9c584aa0c49be 100644 (file)
@@ -49,6 +49,67 @@ static unsigned int path_hash(const char *path)
        return ignore_case ? strihash(path) : strhash(path);
 }
 
+static struct dir_rename_entry *dir_rename_find_entry(struct hashmap *hashmap,
+                                                     char *dir)
+{
+       struct dir_rename_entry key;
+
+       if (dir == NULL)
+               return NULL;
+       hashmap_entry_init(&key, strhash(dir));
+       key.dir = dir;
+       return hashmap_get(hashmap, &key, NULL);
+}
+
+static int dir_rename_cmp(const void *unused_cmp_data,
+                         const void *entry,
+                         const void *entry_or_key,
+                         const void *unused_keydata)
+{
+       const struct dir_rename_entry *e1 = entry;
+       const struct dir_rename_entry *e2 = entry_or_key;
+
+       return strcmp(e1->dir, e2->dir);
+}
+
+static void dir_rename_init(struct hashmap *map)
+{
+       hashmap_init(map, dir_rename_cmp, NULL, 0);
+}
+
+static void dir_rename_entry_init(struct dir_rename_entry *entry,
+                                 char *directory)
+{
+       hashmap_entry_init(entry, strhash(directory));
+       entry->dir = directory;
+       entry->non_unique_new_dir = 0;
+       strbuf_init(&entry->new_dir, 0);
+       string_list_init(&entry->possible_new_dirs, 0);
+}
+
+static struct collision_entry *collision_find_entry(struct hashmap *hashmap,
+                                                   char *target_file)
+{
+       struct collision_entry key;
+
+       hashmap_entry_init(&key, strhash(target_file));
+       key.target_file = target_file;
+       return hashmap_get(hashmap, &key, NULL);
+}
+
+static int collision_cmp(void *unused_cmp_data,
+                        const struct collision_entry *e1,
+                        const struct collision_entry *e2,
+                        const void *unused_keydata)
+{
+       return strcmp(e1->target_file, e2->target_file);
+}
+
+static void collision_init(struct hashmap *map)
+{
+       hashmap_init(map, (hashmap_cmp_fn) collision_cmp, NULL, 0);
+}
+
 static void flush_output(struct merge_options *o)
 {
        if (o->buffer_output < 2 && o->obuf.len) {
@@ -119,6 +180,7 @@ static int oid_eq(const struct object_id *a, const struct object_id *b)
 
 enum rename_type {
        RENAME_NORMAL = 0,
+       RENAME_DIR,
        RENAME_DELETE,
        RENAME_ONE_FILE_TO_ONE,
        RENAME_ONE_FILE_TO_TWO,
@@ -275,32 +337,37 @@ static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)
        init_tree_desc(desc, tree->buffer, tree->size);
 }
 
-static int git_merge_trees(int index_only,
+static int git_merge_trees(struct merge_options *o,
                           struct tree *common,
                           struct tree *head,
                           struct tree *merge)
 {
        int rc;
        struct tree_desc t[3];
-       struct unpack_trees_options opts;
 
-       memset(&opts, 0, sizeof(opts));
-       if (index_only)
-               opts.index_only = 1;
+       memset(&o->unpack_opts, 0, sizeof(o->unpack_opts));
+       if (o->call_depth)
+               o->unpack_opts.index_only = 1;
        else
-               opts.update = 1;
-       opts.merge = 1;
-       opts.head_idx = 2;
-       opts.fn = threeway_merge;
-       opts.src_index = &the_index;
-       opts.dst_index = &the_index;
-       setup_unpack_trees_porcelain(&opts, "merge");
+               o->unpack_opts.update = 1;
+       o->unpack_opts.merge = 1;
+       o->unpack_opts.head_idx = 2;
+       o->unpack_opts.fn = threeway_merge;
+       o->unpack_opts.src_index = &the_index;
+       o->unpack_opts.dst_index = &the_index;
+       setup_unpack_trees_porcelain(&o->unpack_opts, "merge");
 
        init_tree_desc_from_tree(t+0, common);
        init_tree_desc_from_tree(t+1, head);
        init_tree_desc_from_tree(t+2, merge);
 
-       rc = unpack_trees(3, t, &opts);
+       rc = unpack_trees(3, t, &o->unpack_opts);
+       /*
+        * unpack_trees NULLifies src_index, but it's used in verify_uptodate,
+        * so set to the new index which will usually have modification
+        * timestamp info copied over.
+        */
+       o->unpack_opts.src_index = &the_index;
        cache_tree_free(&active_cache_tree);
        return rc;
 }
@@ -360,6 +427,21 @@ static void get_files_dirs(struct merge_options *o, struct tree *tree)
        read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o);
 }
 
+static int get_tree_entry_if_blob(const unsigned char *tree,
+                                 const char *path,
+                                 unsigned char *hashy,
+                                 unsigned int *mode_o)
+{
+       int ret;
+
+       ret = get_tree_entry(tree, path, hashy, mode_o);
+       if (S_ISDIR(*mode_o)) {
+               hashcpy(hashy, null_sha1);
+               *mode_o = 0;
+       }
+       return ret;
+}
+
 /*
  * Returns an index_entry instance which doesn't have to correspond to
  * a real cache entry in Git's index.
@@ -370,12 +452,12 @@ static struct stage_data *insert_stage_data(const char *path,
 {
        struct string_list_item *item;
        struct stage_data *e = xcalloc(1, sizeof(struct stage_data));
-       get_tree_entry(o->object.oid.hash, path,
-                       e->stages[1].oid.hash, &e->stages[1].mode);
-       get_tree_entry(a->object.oid.hash, path,
-                       e->stages[2].oid.hash, &e->stages[2].mode);
-       get_tree_entry(b->object.oid.hash, path,
-                       e->stages[3].oid.hash, &e->stages[3].mode);
+       get_tree_entry_if_blob(o->object.oid.hash, path,
+                              e->stages[1].oid.hash, &e->stages[1].mode);
+       get_tree_entry_if_blob(a->object.oid.hash, path,
+                              e->stages[2].oid.hash, &e->stages[2].mode);
+       get_tree_entry_if_blob(b->object.oid.hash, path,
+                              e->stages[3].oid.hash, &e->stages[3].mode);
        item = string_list_insert(entries, path);
        item->util = e;
        return e;
@@ -534,78 +616,10 @@ struct rename {
         */
        struct stage_data *src_entry;
        struct stage_data *dst_entry;
+       unsigned add_turned_into_rename:1;
        unsigned processed:1;
 };
 
-/*
- * Get information of all renames which occurred between 'o_tree' and
- * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and
- * 'b_tree') to be able to associate the correct cache entries with
- * the rename information. 'tree' is always equal to either a_tree or b_tree.
- */
-static struct string_list *get_renames(struct merge_options *o,
-                                      struct tree *tree,
-                                      struct tree *o_tree,
-                                      struct tree *a_tree,
-                                      struct tree *b_tree,
-                                      struct string_list *entries)
-{
-       int i;
-       struct string_list *renames;
-       struct diff_options opts;
-
-       renames = xcalloc(1, sizeof(struct string_list));
-       if (!o->detect_rename)
-               return renames;
-
-       diff_setup(&opts);
-       opts.flags.recursive = 1;
-       opts.flags.rename_empty = 0;
-       opts.detect_rename = DIFF_DETECT_RENAME;
-       opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
-                           o->diff_rename_limit >= 0 ? o->diff_rename_limit :
-                           1000;
-       opts.rename_score = o->rename_score;
-       opts.show_rename_progress = o->show_rename_progress;
-       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-       diff_setup_done(&opts);
-       diff_tree_oid(&o_tree->object.oid, &tree->object.oid, "", &opts);
-       diffcore_std(&opts);
-       if (opts.needed_rename_limit > o->needed_rename_limit)
-               o->needed_rename_limit = opts.needed_rename_limit;
-       for (i = 0; i < diff_queued_diff.nr; ++i) {
-               struct string_list_item *item;
-               struct rename *re;
-               struct diff_filepair *pair = diff_queued_diff.queue[i];
-               if (pair->status != 'R') {
-                       diff_free_filepair(pair);
-                       continue;
-               }
-               re = xmalloc(sizeof(*re));
-               re->processed = 0;
-               re->pair = pair;
-               item = string_list_lookup(entries, re->pair->one->path);
-               if (!item)
-                       re->src_entry = insert_stage_data(re->pair->one->path,
-                                       o_tree, a_tree, b_tree, entries);
-               else
-                       re->src_entry = item->util;
-
-               item = string_list_lookup(entries, re->pair->two->path);
-               if (!item)
-                       re->dst_entry = insert_stage_data(re->pair->two->path,
-                                       o_tree, a_tree, b_tree, entries);
-               else
-                       re->dst_entry = item->util;
-               item = string_list_insert(renames, pair->one->path);
-               item->util = re;
-       }
-       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-       diff_queued_diff.nr = 0;
-       diff_flush(&opts);
-       return renames;
-}
-
 static int update_stages(struct merge_options *opt, const char *path,
                         const struct diff_filespec *o,
                         const struct diff_filespec *a,
@@ -637,6 +651,27 @@ static int update_stages(struct merge_options *opt, const char *path,
        return 0;
 }
 
+static int update_stages_for_stage_data(struct merge_options *opt,
+                                       const char *path,
+                                       const struct stage_data *stage_data)
+{
+       struct diff_filespec o, a, b;
+
+       o.mode = stage_data->stages[1].mode;
+       oidcpy(&o.oid, &stage_data->stages[1].oid);
+
+       a.mode = stage_data->stages[2].mode;
+       oidcpy(&a.oid, &stage_data->stages[2].oid);
+
+       b.mode = stage_data->stages[3].mode;
+       oidcpy(&b.oid, &stage_data->stages[3].oid);
+
+       return update_stages(opt, path,
+                            is_null_oid(&o.oid) ? NULL : &o,
+                            is_null_oid(&a.oid) ? NULL : &a,
+                            is_null_oid(&b.oid) ? NULL : &b);
+}
+
 static void update_entry(struct stage_data *entry,
                         struct diff_filespec *o,
                         struct diff_filespec *a,
@@ -765,6 +800,20 @@ static int would_lose_untracked(const char *path)
        return !was_tracked(path) && file_exists(path);
 }
 
+static int was_dirty(struct merge_options *o, const char *path)
+{
+       struct cache_entry *ce;
+       int dirty = 1;
+
+       if (o->call_depth || !was_tracked(path))
+               return !dirty;
+
+       ce = cache_file_exists(path, strlen(path), ignore_case);
+       dirty = (ce->ce_stat_data.sd_mtime.sec > 0 &&
+                verify_uptodate(ce, &o->unpack_opts) != 0);
+       return dirty;
+}
+
 static int make_room_for_path(struct merge_options *o, const char *path)
 {
        int status, i;
@@ -1114,6 +1163,38 @@ static int merge_file_one(struct merge_options *o,
        return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
 }
 
+static int conflict_rename_dir(struct merge_options *o,
+                              struct diff_filepair *pair,
+                              const char *rename_branch,
+                              const char *other_branch)
+{
+       const struct diff_filespec *dest = pair->two;
+
+       if (!o->call_depth && would_lose_untracked(dest->path)) {
+               char *alt_path = unique_path(o, dest->path, rename_branch);
+
+               output(o, 1, _("Error: Refusing to lose untracked file at %s; "
+                              "writing to %s instead."),
+                      dest->path, alt_path);
+               /*
+                * Write the file in worktree at alt_path, but not in the
+                * index.  Instead, write to dest->path for the index but
+                * only at the higher appropriate stage.
+                */
+               if (update_file(o, 0, &dest->oid, dest->mode, alt_path))
+                       return -1;
+               free(alt_path);
+               return update_stages(o, dest->path, NULL,
+                                    rename_branch == o->branch1 ? dest : NULL,
+                                    rename_branch == o->branch1 ? NULL : dest);
+       }
+
+       /* Update dest->path both in index and in worktree */
+       if (update_file(o, 1, &dest->oid, dest->mode, dest->path))
+               return -1;
+       return 0;
+}
+
 static int handle_change_delete(struct merge_options *o,
                                 const char *path, const char *old_path,
                                 const struct object_id *o_oid, int o_mode,
@@ -1127,7 +1208,8 @@ static int handle_change_delete(struct merge_options *o,
        const char *update_path = path;
        int ret = 0;
 
-       if (dir_in_way(path, !o->call_depth, 0)) {
+       if (dir_in_way(path, !o->call_depth, 0) ||
+           (!o->call_depth && would_lose_untracked(path))) {
                update_path = alt_path = unique_path(o, path, change_branch);
        }
 
@@ -1242,17 +1324,34 @@ static int handle_file(struct merge_options *o,
 
        add = filespec_from_entry(&other, dst_entry, stage ^ 1);
        if (add) {
+               int ren_src_was_dirty = was_dirty(o, rename->path);
                char *add_name = unique_path(o, rename->path, other_branch);
                if (update_file(o, 0, &add->oid, add->mode, add_name))
                        return -1;
 
-               remove_file(o, 0, rename->path, 0);
+               if (ren_src_was_dirty) {
+                       output(o, 1, _("Refusing to lose dirty file at %s"),
+                              rename->path);
+               }
+               /*
+                * Because the double negatives somehow keep confusing me...
+                *    1) update_wd iff !ren_src_was_dirty.
+                *    2) no_wd iff !update_wd
+                *    3) so, no_wd == !!ren_src_was_dirty == ren_src_was_dirty
+                */
+               remove_file(o, 0, rename->path, ren_src_was_dirty);
                dst_name = unique_path(o, rename->path, cur_branch);
        } else {
                if (dir_in_way(rename->path, !o->call_depth, 0)) {
                        dst_name = unique_path(o, rename->path, cur_branch);
                        output(o, 1, _("%s is a directory in %s adding as %s instead"),
                               rename->path, other_branch, dst_name);
+               } else if (!o->call_depth &&
+                          would_lose_untracked(rename->path)) {
+                       dst_name = unique_path(o, rename->path, cur_branch);
+                       output(o, 1, _("Refusing to lose untracked file at %s; "
+                                      "adding as %s instead"),
+                              rename->path, dst_name);
                }
        }
        if ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))
@@ -1378,11 +1477,43 @@ static int conflict_rename_rename_2to1(struct merge_options *o,
                char *new_path2 = unique_path(o, path, ci->branch2);
                output(o, 1, _("Renaming %s to %s and %s to %s instead"),
                       a->path, new_path1, b->path, new_path2);
-               remove_file(o, 0, path, 0);
+               if (was_dirty(o, path))
+                       output(o, 1, _("Refusing to lose dirty file at %s"),
+                              path);
+               else if (would_lose_untracked(path))
+                       /*
+                        * Only way we get here is if both renames were from
+                        * a directory rename AND user had an untracked file
+                        * at the location where both files end up after the
+                        * two directory renames.  See testcase 10d of t6043.
+                        */
+                       output(o, 1, _("Refusing to lose untracked file at "
+                                      "%s, even though it's in the way."),
+                              path);
+               else
+                       remove_file(o, 0, path, 0);
                ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
                if (!ret)
                        ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
                                          new_path2);
+               /*
+                * unpack_trees() actually populates the index for us for
+                * "normal" rename/rename(2to1) situtations so that the
+                * correct entries are at the higher stages, which would
+                * make the call below to update_stages_for_stage_data
+                * unnecessary.  However, if either of the renames came
+                * from a directory rename, then unpack_trees() will not
+                * have gotten the right data loaded into the index, so we
+                * need to do so now.  (While it'd be tempting to move this
+                * call to update_stages_for_stage_data() to
+                * apply_directory_rename_modifications(), that would break
+                * our intermediate calls to would_lose_untracked() since
+                * those rely on the current in-memory index.  See also the
+                * big "NOTE" in update_stages()).
+                */
+               if (update_stages_for_stage_data(o, path, ci->dst_entry1))
+                       ret = -1;
+
                free(new_path2);
                free(new_path1);
        }
@@ -1390,6 +1521,754 @@ static int conflict_rename_rename_2to1(struct merge_options *o,
        return ret;
 }
 
+/*
+ * Get the diff_filepairs changed between o_tree and tree.
+ */
+static struct diff_queue_struct *get_diffpairs(struct merge_options *o,
+                                              struct tree *o_tree,
+                                              struct tree *tree)
+{
+       struct diff_queue_struct *ret;
+       struct diff_options opts;
+
+       diff_setup(&opts);
+       opts.flags.recursive = 1;
+       opts.flags.rename_empty = 0;
+       opts.detect_rename = DIFF_DETECT_RENAME;
+       opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
+                           o->diff_rename_limit >= 0 ? o->diff_rename_limit :
+                           1000;
+       opts.rename_score = o->rename_score;
+       opts.show_rename_progress = o->show_rename_progress;
+       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       diff_setup_done(&opts);
+       diff_tree_oid(&o_tree->object.oid, &tree->object.oid, "", &opts);
+       diffcore_std(&opts);
+       if (opts.needed_rename_limit > o->needed_rename_limit)
+               o->needed_rename_limit = opts.needed_rename_limit;
+
+       ret = xmalloc(sizeof(*ret));
+       *ret = diff_queued_diff;
+
+       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       diff_queued_diff.nr = 0;
+       diff_queued_diff.queue = NULL;
+       diff_flush(&opts);
+       return ret;
+}
+
+static int tree_has_path(struct tree *tree, const char *path)
+{
+       unsigned char hashy[GIT_MAX_RAWSZ];
+       unsigned int mode_o;
+
+       return !get_tree_entry(tree->object.oid.hash, path,
+                              hashy, &mode_o);
+}
+
+/*
+ * Return a new string that replaces the beginning portion (which matches
+ * entry->dir), with entry->new_dir.  In perl-speak:
+ *   new_path_name = (old_path =~ s/entry->dir/entry->new_dir/);
+ * NOTE:
+ *   Caller must ensure that old_path starts with entry->dir + '/'.
+ */
+static char *apply_dir_rename(struct dir_rename_entry *entry,
+                             const char *old_path)
+{
+       struct strbuf new_path = STRBUF_INIT;
+       int oldlen, newlen;
+
+       if (entry->non_unique_new_dir)
+               return NULL;
+
+       oldlen = strlen(entry->dir);
+       newlen = entry->new_dir.len + (strlen(old_path) - oldlen) + 1;
+       strbuf_grow(&new_path, newlen);
+       strbuf_addbuf(&new_path, &entry->new_dir);
+       strbuf_addstr(&new_path, &old_path[oldlen]);
+
+       return strbuf_detach(&new_path, NULL);
+}
+
+static void get_renamed_dir_portion(const char *old_path, const char *new_path,
+                                   char **old_dir, char **new_dir)
+{
+       char *end_of_old, *end_of_new;
+       int old_len, new_len;
+
+       *old_dir = NULL;
+       *new_dir = NULL;
+
+       /*
+        * For
+        *    "a/b/c/d/e/foo.c" -> "a/b/some/thing/else/e/foo.c"
+        * the "e/foo.c" part is the same, we just want to know that
+        *    "a/b/c/d" was renamed to "a/b/some/thing/else"
+        * so, for this example, this function returns "a/b/c/d" in
+        * *old_dir and "a/b/some/thing/else" in *new_dir.
+        *
+        * Also, if the basename of the file changed, we don't care.  We
+        * want to know which portion of the directory, if any, changed.
+        */
+       end_of_old = strrchr(old_path, '/');
+       end_of_new = strrchr(new_path, '/');
+
+       if (end_of_old == NULL || end_of_new == NULL)
+               return;
+       while (*--end_of_new == *--end_of_old &&
+              end_of_old != old_path &&
+              end_of_new != new_path)
+               ; /* Do nothing; all in the while loop */
+       /*
+        * We've found the first non-matching character in the directory
+        * paths.  That means the current directory we were comparing
+        * represents the rename.  Move end_of_old and end_of_new back
+        * to the full directory name.
+        */
+       if (*end_of_old == '/')
+               end_of_old++;
+       if (*end_of_old != '/')
+               end_of_new++;
+       end_of_old = strchr(end_of_old, '/');
+       end_of_new = strchr(end_of_new, '/');
+
+       /*
+        * It may have been the case that old_path and new_path were the same
+        * directory all along.  Don't claim a rename if they're the same.
+        */
+       old_len = end_of_old - old_path;
+       new_len = end_of_new - new_path;
+
+       if (old_len != new_len || strncmp(old_path, new_path, old_len)) {
+               *old_dir = xstrndup(old_path, old_len);
+               *new_dir = xstrndup(new_path, new_len);
+       }
+}
+
+static void remove_hashmap_entries(struct hashmap *dir_renames,
+                                  struct string_list *items_to_remove)
+{
+       int i;
+       struct dir_rename_entry *entry;
+
+       for (i = 0; i < items_to_remove->nr; i++) {
+               entry = items_to_remove->items[i].util;
+               hashmap_remove(dir_renames, entry, NULL);
+       }
+       string_list_clear(items_to_remove, 0);
+}
+
+/*
+ * See if there is a directory rename for path, and if there are any file
+ * level conflicts for the renamed location.  If there is a rename and
+ * there are no conflicts, return the new name.  Otherwise, return NULL.
+ */
+static char *handle_path_level_conflicts(struct merge_options *o,
+                                        const char *path,
+                                        struct dir_rename_entry *entry,
+                                        struct hashmap *collisions,
+                                        struct tree *tree)
+{
+       char *new_path = NULL;
+       struct collision_entry *collision_ent;
+       int clean = 1;
+       struct strbuf collision_paths = STRBUF_INIT;
+
+       /*
+        * entry has the mapping of old directory name to new directory name
+        * that we want to apply to path.
+        */
+       new_path = apply_dir_rename(entry, path);
+
+       if (!new_path) {
+               /* This should only happen when entry->non_unique_new_dir set */
+               if (!entry->non_unique_new_dir)
+                       BUG("entry->non_unqiue_dir not set and !new_path");
+               output(o, 1, _("CONFLICT (directory rename split): "
+                              "Unclear where to place %s because directory "
+                              "%s was renamed to multiple other directories, "
+                              "with no destination getting a majority of the "
+                              "files."),
+                      path, entry->dir);
+               clean = 0;
+               return NULL;
+       }
+
+       /*
+        * The caller needs to have ensured that it has pre-populated
+        * collisions with all paths that map to new_path.  Do a quick check
+        * to ensure that's the case.
+        */
+       collision_ent = collision_find_entry(collisions, new_path);
+       if (collision_ent == NULL)
+               BUG("collision_ent is NULL");
+
+       /*
+        * Check for one-sided add/add/.../add conflicts, i.e.
+        * where implicit renames from the other side doing
+        * directory rename(s) can affect this side of history
+        * to put multiple paths into the same location.  Warn
+        * and bail on directory renames for such paths.
+        */
+       if (collision_ent->reported_already) {
+               clean = 0;
+       } else if (tree_has_path(tree, new_path)) {
+               collision_ent->reported_already = 1;
+               strbuf_add_separated_string_list(&collision_paths, ", ",
+                                                &collision_ent->source_files);
+               output(o, 1, _("CONFLICT (implicit dir rename): Existing "
+                              "file/dir at %s in the way of implicit "
+                              "directory rename(s) putting the following "
+                              "path(s) there: %s."),
+                      new_path, collision_paths.buf);
+               clean = 0;
+       } else if (collision_ent->source_files.nr > 1) {
+               collision_ent->reported_already = 1;
+               strbuf_add_separated_string_list(&collision_paths, ", ",
+                                                &collision_ent->source_files);
+               output(o, 1, _("CONFLICT (implicit dir rename): Cannot map "
+                              "more than one path to %s; implicit directory "
+                              "renames tried to put these paths there: %s"),
+                      new_path, collision_paths.buf);
+               clean = 0;
+       }
+
+       /* Free memory we no longer need */
+       strbuf_release(&collision_paths);
+       if (!clean && new_path) {
+               free(new_path);
+               return NULL;
+       }
+
+       return new_path;
+}
+
+/*
+ * There are a couple things we want to do at the directory level:
+ *   1. Check for both sides renaming to the same thing, in order to avoid
+ *      implicit renaming of files that should be left in place.  (See
+ *      testcase 6b in t6043 for details.)
+ *   2. Prune directory renames if there are still files left in the
+ *      the original directory.  These represent a partial directory rename,
+ *      i.e. a rename where only some of the files within the directory
+ *      were renamed elsewhere.  (Technically, this could be done earlier
+ *      in get_directory_renames(), except that would prevent us from
+ *      doing the previous check and thus failing testcase 6b.)
+ *   3. Check for rename/rename(1to2) conflicts (at the directory level).
+ *      In the future, we could potentially record this info as well and
+ *      omit reporting rename/rename(1to2) conflicts for each path within
+ *      the affected directories, thus cleaning up the merge output.
+ *   NOTE: We do NOT check for rename/rename(2to1) conflicts at the
+ *         directory level, because merging directories is fine.  If it
+ *         causes conflicts for files within those merged directories, then
+ *         that should be detected at the individual path level.
+ */
+static void handle_directory_level_conflicts(struct merge_options *o,
+                                            struct hashmap *dir_re_head,
+                                            struct tree *head,
+                                            struct hashmap *dir_re_merge,
+                                            struct tree *merge)
+{
+       struct hashmap_iter iter;
+       struct dir_rename_entry *head_ent;
+       struct dir_rename_entry *merge_ent;
+
+       struct string_list remove_from_head = STRING_LIST_INIT_NODUP;
+       struct string_list remove_from_merge = STRING_LIST_INIT_NODUP;
+
+       hashmap_iter_init(dir_re_head, &iter);
+       while ((head_ent = hashmap_iter_next(&iter))) {
+               merge_ent = dir_rename_find_entry(dir_re_merge, head_ent->dir);
+               if (merge_ent &&
+                   !head_ent->non_unique_new_dir &&
+                   !merge_ent->non_unique_new_dir &&
+                   !strbuf_cmp(&head_ent->new_dir, &merge_ent->new_dir)) {
+                       /* 1. Renamed identically; remove it from both sides */
+                       string_list_append(&remove_from_head,
+                                          head_ent->dir)->util = head_ent;
+                       strbuf_release(&head_ent->new_dir);
+                       string_list_append(&remove_from_merge,
+                                          merge_ent->dir)->util = merge_ent;
+                       strbuf_release(&merge_ent->new_dir);
+               } else if (tree_has_path(head, head_ent->dir)) {
+                       /* 2. This wasn't a directory rename after all */
+                       string_list_append(&remove_from_head,
+                                          head_ent->dir)->util = head_ent;
+                       strbuf_release(&head_ent->new_dir);
+               }
+       }
+
+       remove_hashmap_entries(dir_re_head, &remove_from_head);
+       remove_hashmap_entries(dir_re_merge, &remove_from_merge);
+
+       hashmap_iter_init(dir_re_merge, &iter);
+       while ((merge_ent = hashmap_iter_next(&iter))) {
+               head_ent = dir_rename_find_entry(dir_re_head, merge_ent->dir);
+               if (tree_has_path(merge, merge_ent->dir)) {
+                       /* 2. This wasn't a directory rename after all */
+                       string_list_append(&remove_from_merge,
+                                          merge_ent->dir)->util = merge_ent;
+               } else if (head_ent &&
+                          !head_ent->non_unique_new_dir &&
+                          !merge_ent->non_unique_new_dir) {
+                       /* 3. rename/rename(1to2) */
+                       /*
+                        * We can assume it's not rename/rename(1to1) because
+                        * that was case (1), already checked above.  So we
+                        * know that head_ent->new_dir and merge_ent->new_dir
+                        * are different strings.
+                        */
+                       output(o, 1, _("CONFLICT (rename/rename): "
+                                      "Rename directory %s->%s in %s. "
+                                      "Rename directory %s->%s in %s"),
+                              head_ent->dir, head_ent->new_dir.buf, o->branch1,
+                              head_ent->dir, merge_ent->new_dir.buf, o->branch2);
+                       string_list_append(&remove_from_head,
+                                          head_ent->dir)->util = head_ent;
+                       strbuf_release(&head_ent->new_dir);
+                       string_list_append(&remove_from_merge,
+                                          merge_ent->dir)->util = merge_ent;
+                       strbuf_release(&merge_ent->new_dir);
+               }
+       }
+
+       remove_hashmap_entries(dir_re_head, &remove_from_head);
+       remove_hashmap_entries(dir_re_merge, &remove_from_merge);
+}
+
+static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
+                                            struct tree *tree)
+{
+       struct hashmap *dir_renames;
+       struct hashmap_iter iter;
+       struct dir_rename_entry *entry;
+       int i;
+
+       /*
+        * Typically, we think of a directory rename as all files from a
+        * certain directory being moved to a target directory.  However,
+        * what if someone first moved two files from the original
+        * directory in one commit, and then renamed the directory
+        * somewhere else in a later commit?  At merge time, we just know
+        * that files from the original directory went to two different
+        * places, and that the bulk of them ended up in the same place.
+        * We want each directory rename to represent where the bulk of the
+        * files from that directory end up; this function exists to find
+        * where the bulk of the files went.
+        *
+        * The first loop below simply iterates through the list of file
+        * renames, finding out how often each directory rename pair
+        * possibility occurs.
+        */
+       dir_renames = xmalloc(sizeof(struct hashmap));
+       dir_rename_init(dir_renames);
+       for (i = 0; i < pairs->nr; ++i) {
+               struct string_list_item *item;
+               int *count;
+               struct diff_filepair *pair = pairs->queue[i];
+               char *old_dir, *new_dir;
+
+               /* File not part of directory rename if it wasn't renamed */
+               if (pair->status != 'R')
+                       continue;
+
+               get_renamed_dir_portion(pair->one->path, pair->two->path,
+                                       &old_dir,        &new_dir);
+               if (!old_dir)
+                       /* Directory didn't change at all; ignore this one. */
+                       continue;
+
+               entry = dir_rename_find_entry(dir_renames, old_dir);
+               if (!entry) {
+                       entry = xmalloc(sizeof(struct dir_rename_entry));
+                       dir_rename_entry_init(entry, old_dir);
+                       hashmap_put(dir_renames, entry);
+               } else {
+                       free(old_dir);
+               }
+               item = string_list_lookup(&entry->possible_new_dirs, new_dir);
+               if (!item) {
+                       item = string_list_insert(&entry->possible_new_dirs,
+                                                 new_dir);
+                       item->util = xcalloc(1, sizeof(int));
+               } else {
+                       free(new_dir);
+               }
+               count = item->util;
+               *count += 1;
+       }
+
+       /*
+        * For each directory with files moved out of it, we find out which
+        * target directory received the most files so we can declare it to
+        * be the "winning" target location for the directory rename.  This
+        * winner gets recorded in new_dir.  If there is no winner
+        * (multiple target directories received the same number of files),
+        * we set non_unique_new_dir.  Once we've determined the winner (or
+        * that there is no winner), we no longer need possible_new_dirs.
+        */
+       hashmap_iter_init(dir_renames, &iter);
+       while ((entry = hashmap_iter_next(&iter))) {
+               int max = 0;
+               int bad_max = 0;
+               char *best = NULL;
+
+               for (i = 0; i < entry->possible_new_dirs.nr; i++) {
+                       int *count = entry->possible_new_dirs.items[i].util;
+
+                       if (*count == max)
+                               bad_max = max;
+                       else if (*count > max) {
+                               max = *count;
+                               best = entry->possible_new_dirs.items[i].string;
+                       }
+               }
+               if (bad_max == max)
+                       entry->non_unique_new_dir = 1;
+               else {
+                       assert(entry->new_dir.len == 0);
+                       strbuf_addstr(&entry->new_dir, best);
+               }
+               /*
+                * The relevant directory sub-portion of the original full
+                * filepaths were xstrndup'ed before inserting into
+                * possible_new_dirs, and instead of manually iterating the
+                * list and free'ing each, just lie and tell
+                * possible_new_dirs that it did the strdup'ing so that it
+                * will free them for us.
+                */
+               entry->possible_new_dirs.strdup_strings = 1;
+               string_list_clear(&entry->possible_new_dirs, 1);
+       }
+
+       return dir_renames;
+}
+
+static struct dir_rename_entry *check_dir_renamed(const char *path,
+                                                 struct hashmap *dir_renames)
+{
+       char temp[PATH_MAX];
+       char *end;
+       struct dir_rename_entry *entry;
+
+       strcpy(temp, path);
+       while ((end = strrchr(temp, '/'))) {
+               *end = '\0';
+               entry = dir_rename_find_entry(dir_renames, temp);
+               if (entry)
+                       return entry;
+       }
+       return NULL;
+}
+
+static void compute_collisions(struct hashmap *collisions,
+                              struct hashmap *dir_renames,
+                              struct diff_queue_struct *pairs)
+{
+       int i;
+
+       /*
+        * Multiple files can be mapped to the same path due to directory
+        * renames done by the other side of history.  Since that other
+        * side of history could have merged multiple directories into one,
+        * if our side of history added the same file basename to each of
+        * those directories, then all N of them would get implicitly
+        * renamed by the directory rename detection into the same path,
+        * and we'd get an add/add/.../add conflict, and all those adds
+        * from *this* side of history.  This is not representable in the
+        * index, and users aren't going to easily be able to make sense of
+        * it.  So we need to provide a good warning about what's
+        * happening, and fall back to no-directory-rename detection
+        * behavior for those paths.
+        *
+        * See testcases 9e and all of section 5 from t6043 for examples.
+        */
+       collision_init(collisions);
+
+       for (i = 0; i < pairs->nr; ++i) {
+               struct dir_rename_entry *dir_rename_ent;
+               struct collision_entry *collision_ent;
+               char *new_path;
+               struct diff_filepair *pair = pairs->queue[i];
+
+               if (pair->status != 'A' && pair->status != 'R')
+                       continue;
+               dir_rename_ent = check_dir_renamed(pair->two->path,
+                                                  dir_renames);
+               if (!dir_rename_ent)
+                       continue;
+
+               new_path = apply_dir_rename(dir_rename_ent, pair->two->path);
+               if (!new_path)
+                       /*
+                        * dir_rename_ent->non_unique_new_path is true, which
+                        * means there is no directory rename for us to use,
+                        * which means it won't cause us any additional
+                        * collisions.
+                        */
+                       continue;
+               collision_ent = collision_find_entry(collisions, new_path);
+               if (!collision_ent) {
+                       collision_ent = xcalloc(1,
+                                               sizeof(struct collision_entry));
+                       hashmap_entry_init(collision_ent, strhash(new_path));
+                       hashmap_put(collisions, collision_ent);
+                       collision_ent->target_file = new_path;
+               } else {
+                       free(new_path);
+               }
+               string_list_insert(&collision_ent->source_files,
+                                  pair->two->path);
+       }
+}
+
+static char *check_for_directory_rename(struct merge_options *o,
+                                       const char *path,
+                                       struct tree *tree,
+                                       struct hashmap *dir_renames,
+                                       struct hashmap *dir_rename_exclusions,
+                                       struct hashmap *collisions,
+                                       int *clean_merge)
+{
+       char *new_path = NULL;
+       struct dir_rename_entry *entry = check_dir_renamed(path, dir_renames);
+       struct dir_rename_entry *oentry = NULL;
+
+       if (!entry)
+               return new_path;
+
+       /*
+        * This next part is a little weird.  We do not want to do an
+        * implicit rename into a directory we renamed on our side, because
+        * that will result in a spurious rename/rename(1to2) conflict.  An
+        * example:
+        *   Base commit: dumbdir/afile, otherdir/bfile
+        *   Side 1:      smrtdir/afile, otherdir/bfile
+        *   Side 2:      dumbdir/afile, dumbdir/bfile
+        * Here, while working on Side 1, we could notice that otherdir was
+        * renamed/merged to dumbdir, and change the diff_filepair for
+        * otherdir/bfile into a rename into dumbdir/bfile.  However, Side
+        * 2 will notice the rename from dumbdir to smrtdir, and do the
+        * transitive rename to move it from dumbdir/bfile to
+        * smrtdir/bfile.  That gives us bfile in dumbdir vs being in
+        * smrtdir, a rename/rename(1to2) conflict.  We really just want
+        * the file to end up in smrtdir.  And the way to achieve that is
+        * to not let Side1 do the rename to dumbdir, since we know that is
+        * the source of one of our directory renames.
+        *
+        * That's why oentry and dir_rename_exclusions is here.
+        *
+        * As it turns out, this also prevents N-way transient rename
+        * confusion; See testcases 9c and 9d of t6043.
+        */
+       oentry = dir_rename_find_entry(dir_rename_exclusions, entry->new_dir.buf);
+       if (oentry) {
+               output(o, 1, _("WARNING: Avoiding applying %s -> %s rename "
+                              "to %s, because %s itself was renamed."),
+                      entry->dir, entry->new_dir.buf, path, entry->new_dir.buf);
+       } else {
+               new_path = handle_path_level_conflicts(o, path, entry,
+                                                      collisions, tree);
+               *clean_merge &= (new_path != NULL);
+       }
+
+       return new_path;
+}
+
+static void apply_directory_rename_modifications(struct merge_options *o,
+                                                struct diff_filepair *pair,
+                                                char *new_path,
+                                                struct rename *re,
+                                                struct tree *tree,
+                                                struct tree *o_tree,
+                                                struct tree *a_tree,
+                                                struct tree *b_tree,
+                                                struct string_list *entries,
+                                                int *clean)
+{
+       struct string_list_item *item;
+       int stage = (tree == a_tree ? 2 : 3);
+       int update_wd;
+
+       /*
+        * In all cases where we can do directory rename detection,
+        * unpack_trees() will have read pair->two->path into the
+        * index and the working copy.  We need to remove it so that
+        * we can instead place it at new_path.  It is guaranteed to
+        * not be untracked (unpack_trees() would have errored out
+        * saying the file would have been overwritten), but it might
+        * be dirty, though.
+        */
+       update_wd = !was_dirty(o, pair->two->path);
+       if (!update_wd)
+               output(o, 1, _("Refusing to lose dirty file at %s"),
+                      pair->two->path);
+       remove_file(o, 1, pair->two->path, !update_wd);
+
+       /* Find or create a new re->dst_entry */
+       item = string_list_lookup(entries, new_path);
+       if (item) {
+               /*
+                * Since we're renaming on this side of history, and it's
+                * due to a directory rename on the other side of history
+                * (which we only allow when the directory in question no
+                * longer exists on the other side of history), the
+                * original entry for re->dst_entry is no longer
+                * necessary...
+                */
+               re->dst_entry->processed = 1;
+
+               /*
+                * ...because we'll be using this new one.
+                */
+               re->dst_entry = item->util;
+       } else {
+               /*
+                * re->dst_entry is for the before-dir-rename path, and we
+                * need it to hold information for the after-dir-rename
+                * path.  Before creating a new entry, we need to mark the
+                * old one as unnecessary (...unless it is shared by
+                * src_entry, i.e. this didn't use to be a rename, in which
+                * case we can just allow the normal processing to happen
+                * for it).
+                */
+               if (pair->status == 'R')
+                       re->dst_entry->processed = 1;
+
+               re->dst_entry = insert_stage_data(new_path,
+                                                 o_tree, a_tree, b_tree,
+                                                 entries);
+               item = string_list_insert(entries, new_path);
+               item->util = re->dst_entry;
+       }
+
+       /*
+        * Update the stage_data with the information about the path we are
+        * moving into place.  That slot will be empty and available for us
+        * to write to because of the collision checks in
+        * handle_path_level_conflicts().  In other words,
+        * re->dst_entry->stages[stage].oid will be the null_oid, so it's
+        * open for us to write to.
+        *
+        * It may be tempting to actually update the index at this point as
+        * well, using update_stages_for_stage_data(), but as per the big
+        * "NOTE" in update_stages(), doing so will modify the current
+        * in-memory index which will break calls to would_lose_untracked()
+        * that we need to make.  Instead, we need to just make sure that
+        * the various conflict_rename_*() functions update the index
+        * explicitly rather than relying on unpack_trees() to have done it.
+        */
+       get_tree_entry(tree->object.oid.hash,
+                      pair->two->path,
+                      re->dst_entry->stages[stage].oid.hash,
+                      &re->dst_entry->stages[stage].mode);
+
+       /* Update pair status */
+       if (pair->status == 'A') {
+               /*
+                * Recording rename information for this add makes it look
+                * like a rename/delete conflict.  Make sure we can
+                * correctly handle this as an add that was moved to a new
+                * directory instead of reporting a rename/delete conflict.
+                */
+               re->add_turned_into_rename = 1;
+       }
+       /*
+        * We don't actually look at pair->status again, but it seems
+        * pedagogically correct to adjust it.
+        */
+       pair->status = 'R';
+
+       /*
+        * Finally, record the new location.
+        */
+       pair->two->path = new_path;
+}
+
+/*
+ * Get information of all renames which occurred in 'pairs', making use of
+ * any implicit directory renames inferred from the other side of history.
+ * We need the three trees in the merge ('o_tree', 'a_tree' and 'b_tree')
+ * to be able to associate the correct cache entries with the rename
+ * information; tree is always equal to either a_tree or b_tree.
+ */
+static struct string_list *get_renames(struct merge_options *o,
+                                      struct diff_queue_struct *pairs,
+                                      struct hashmap *dir_renames,
+                                      struct hashmap *dir_rename_exclusions,
+                                      struct tree *tree,
+                                      struct tree *o_tree,
+                                      struct tree *a_tree,
+                                      struct tree *b_tree,
+                                      struct string_list *entries,
+                                      int *clean_merge)
+{
+       int i;
+       struct hashmap collisions;
+       struct hashmap_iter iter;
+       struct collision_entry *e;
+       struct string_list *renames;
+
+       compute_collisions(&collisions, dir_renames, pairs);
+       renames = xcalloc(1, sizeof(struct string_list));
+
+       for (i = 0; i < pairs->nr; ++i) {
+               struct string_list_item *item;
+               struct rename *re;
+               struct diff_filepair *pair = pairs->queue[i];
+               char *new_path; /* non-NULL only with directory renames */
+
+               if (pair->status != 'A' && pair->status != 'R') {
+                       diff_free_filepair(pair);
+                       continue;
+               }
+               new_path = check_for_directory_rename(o, pair->two->path, tree,
+                                                     dir_renames,
+                                                     dir_rename_exclusions,
+                                                     &collisions,
+                                                     clean_merge);
+               if (pair->status != 'R' && !new_path) {
+                       diff_free_filepair(pair);
+                       continue;
+               }
+
+               re = xmalloc(sizeof(*re));
+               re->processed = 0;
+               re->add_turned_into_rename = 0;
+               re->pair = pair;
+               item = string_list_lookup(entries, re->pair->one->path);
+               if (!item)
+                       re->src_entry = insert_stage_data(re->pair->one->path,
+                                       o_tree, a_tree, b_tree, entries);
+               else
+                       re->src_entry = item->util;
+
+               item = string_list_lookup(entries, re->pair->two->path);
+               if (!item)
+                       re->dst_entry = insert_stage_data(re->pair->two->path,
+                                       o_tree, a_tree, b_tree, entries);
+               else
+                       re->dst_entry = item->util;
+               item = string_list_insert(renames, pair->one->path);
+               item->util = re;
+               if (new_path)
+                       apply_directory_rename_modifications(o, pair, new_path,
+                                                            re, tree, o_tree,
+                                                            a_tree, b_tree,
+                                                            entries,
+                                                            clean_merge);
+       }
+
+       hashmap_iter_init(&collisions, &iter);
+       while ((e = hashmap_iter_next(&iter))) {
+               free(e->target_file);
+               string_list_clear(&e->source_files, 0);
+       }
+       hashmap_free(&collisions, 1);
+       return renames;
+}
+
 static int process_renames(struct merge_options *o,
                           struct string_list *a_renames,
                           struct string_list *b_renames)
@@ -1548,7 +2427,19 @@ static int process_renames(struct merge_options *o,
                        dst_other.mode = ren1->dst_entry->stages[other_stage].mode;
                        try_merge = 0;
 
-                       if (oid_eq(&src_other.oid, &null_oid)) {
+                       if (oid_eq(&src_other.oid, &null_oid) &&
+                           ren1->add_turned_into_rename) {
+                               setup_rename_conflict_info(RENAME_DIR,
+                                                          ren1->pair,
+                                                          NULL,
+                                                          branch1,
+                                                          branch2,
+                                                          ren1->dst_entry,
+                                                          NULL,
+                                                          o,
+                                                          NULL,
+                                                          NULL);
+                       } else if (oid_eq(&src_other.oid, &null_oid)) {
                                setup_rename_conflict_info(RENAME_DELETE,
                                                           ren1->pair,
                                                           NULL,
@@ -1645,6 +2536,105 @@ static int process_renames(struct merge_options *o,
        return clean_merge;
 }
 
+struct rename_info {
+       struct string_list *head_renames;
+       struct string_list *merge_renames;
+};
+
+static void initial_cleanup_rename(struct diff_queue_struct *pairs,
+                                  struct hashmap *dir_renames)
+{
+       struct hashmap_iter iter;
+       struct dir_rename_entry *e;
+
+       hashmap_iter_init(dir_renames, &iter);
+       while ((e = hashmap_iter_next(&iter))) {
+               free(e->dir);
+               strbuf_release(&e->new_dir);
+               /* possible_new_dirs already cleared in get_directory_renames */
+       }
+       hashmap_free(dir_renames, 1);
+       free(dir_renames);
+
+       free(pairs->queue);
+       free(pairs);
+}
+
+static int handle_renames(struct merge_options *o,
+                         struct tree *common,
+                         struct tree *head,
+                         struct tree *merge,
+                         struct string_list *entries,
+                         struct rename_info *ri)
+{
+       struct diff_queue_struct *head_pairs, *merge_pairs;
+       struct hashmap *dir_re_head, *dir_re_merge;
+       int clean = 1;
+
+       ri->head_renames = NULL;
+       ri->merge_renames = NULL;
+
+       if (!o->detect_rename)
+               return 1;
+
+       head_pairs = get_diffpairs(o, common, head);
+       merge_pairs = get_diffpairs(o, common, merge);
+
+       dir_re_head = get_directory_renames(head_pairs, head);
+       dir_re_merge = get_directory_renames(merge_pairs, merge);
+
+       handle_directory_level_conflicts(o,
+                                        dir_re_head, head,
+                                        dir_re_merge, merge);
+
+       ri->head_renames  = get_renames(o, head_pairs,
+                                       dir_re_merge, dir_re_head, head,
+                                       common, head, merge, entries,
+                                       &clean);
+       if (clean < 0)
+               goto cleanup;
+       ri->merge_renames = get_renames(o, merge_pairs,
+                                       dir_re_head, dir_re_merge, merge,
+                                       common, head, merge, entries,
+                                       &clean);
+       if (clean < 0)
+               goto cleanup;
+       clean &= process_renames(o, ri->head_renames, ri->merge_renames);
+
+cleanup:
+       /*
+        * Some cleanup is deferred until cleanup_renames() because the
+        * data structures are still needed and referenced in
+        * process_entry().  But there are a few things we can free now.
+        */
+       initial_cleanup_rename(head_pairs, dir_re_head);
+       initial_cleanup_rename(merge_pairs, dir_re_merge);
+
+       return clean;
+}
+
+static void final_cleanup_rename(struct string_list *rename)
+{
+       const struct rename *re;
+       int i;
+
+       if (rename == NULL)
+               return;
+
+       for (i = 0; i < rename->nr; i++) {
+               re = rename->items[i].util;
+               diff_free_filepair(re->pair);
+       }
+       string_list_clear(rename, 1);
+       free(rename);
+}
+
+static void final_cleanup_renames(struct rename_info *re_info)
+{
+       final_cleanup_rename(re_info->head_renames);
+       final_cleanup_rename(re_info->merge_renames);
+}
+
 static struct object_id *stage_oid(const struct object_id *oid, unsigned mode)
 {
        return (is_null_oid(oid) || mode == 0) ? NULL: (struct object_id *)oid;
@@ -1735,6 +2725,7 @@ static int handle_modify_delete(struct merge_options *o,
 
 static int merge_content(struct merge_options *o,
                         const char *path,
+                        int file_in_way,
                         struct object_id *o_oid, int o_mode,
                         struct object_id *a_oid, int a_mode,
                         struct object_id *b_oid, int b_mode,
@@ -1782,7 +2773,6 @@ static int merge_content(struct merge_options *o,
 
        if (mfi.clean && !df_conflict_remains &&
            oid_eq(&mfi.oid, a_oid) && mfi.mode == a_mode) {
-               int path_renamed_outside_HEAD;
                output(o, 3, _("Skipped %s (merged same as existing)"), path);
                /*
                 * The content merge resulted in the same file contents we
@@ -1790,8 +2780,7 @@ static int merge_content(struct merge_options *o,
                 * are recorded at the correct path (which may not be true
                 * if the merge involves a rename).
                 */
-               path_renamed_outside_HEAD = !path2 || !strcmp(path, path2);
-               if (!path_renamed_outside_HEAD) {
+               if (was_tracked(path)) {
                        add_cacheinfo(o, mfi.mode, &mfi.oid, path,
                                      0, (!o->call_depth), 0);
                        return mfi.clean;
@@ -1809,7 +2798,7 @@ static int merge_content(struct merge_options *o,
                                return -1;
        }
 
-       if (df_conflict_remains) {
+       if (df_conflict_remains || file_in_way) {
                char *new_path;
                if (o->call_depth) {
                        remove_file_from_cache(path);
@@ -1843,6 +2832,30 @@ static int merge_content(struct merge_options *o,
        return mfi.clean;
 }
 
+static int conflict_rename_normal(struct merge_options *o,
+                                 const char *path,
+                                 struct object_id *o_oid, unsigned int o_mode,
+                                 struct object_id *a_oid, unsigned int a_mode,
+                                 struct object_id *b_oid, unsigned int b_mode,
+                                 struct rename_conflict_info *ci)
+{
+       int clean_merge;
+       int file_in_the_way = 0;
+
+       if (was_dirty(o, path)) {
+               file_in_the_way = 1;
+               output(o, 1, _("Refusing to lose dirty file at %s"), path);
+       }
+
+       /* Merge the content and write it out */
+       clean_merge = merge_content(o, path, file_in_the_way,
+                                   o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
+                                   ci);
+       if (clean_merge > 0 && file_in_the_way)
+               clean_merge = 0;
+       return clean_merge;
+}
+
 /* Per entry merge function */
 static int process_entry(struct merge_options *o,
                         const char *path, struct stage_data *entry)
@@ -1862,9 +2875,20 @@ static int process_entry(struct merge_options *o,
                switch (conflict_info->rename_type) {
                case RENAME_NORMAL:
                case RENAME_ONE_FILE_TO_ONE:
-                       clean_merge = merge_content(o, path,
-                                                   o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
-                                                   conflict_info);
+                       clean_merge = conflict_rename_normal(o,
+                                                            path,
+                                                            o_oid, o_mode,
+                                                            a_oid, a_mode,
+                                                            b_oid, b_mode,
+                                                            conflict_info);
+                       break;
+               case RENAME_DIR:
+                       clean_merge = 1;
+                       if (conflict_rename_dir(o,
+                                               conflict_info->pair1,
+                                               conflict_info->branch1,
+                                               conflict_info->branch2))
+                               clean_merge = -1;
                        break;
                case RENAME_DELETE:
                        clean_merge = 0;
@@ -1952,7 +2976,7 @@ static int process_entry(struct merge_options *o,
        } else if (a_oid && b_oid) {
                /* Case C: Added in both (check for same permissions) and */
                /* case D: Modified in both, but differently. */
-               clean_merge = merge_content(o, path,
+               clean_merge = merge_content(o, path, 0 /* file_in_way */,
                                            o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
                                            NULL);
        } else if (!o_oid && !a_oid && !b_oid) {
@@ -1993,7 +3017,7 @@ int merge_trees(struct merge_options *o,
                return 1;
        }
 
-       code = git_merge_trees(o->call_depth, common, head, merge);
+       code = git_merge_trees(o, common, head, merge);
 
        if (code != 0) {
                if (show(o, 4) || o->call_depth)
@@ -2004,7 +3028,8 @@ int merge_trees(struct merge_options *o,
        }
 
        if (unmerged_cache()) {
-               struct string_list *entries, *re_head, *re_merge;
+               struct string_list *entries;
+               struct rename_info re_info;
                int i;
                /*
                 * Only need the hashmap while processing entries, so
@@ -2018,9 +3043,8 @@ int merge_trees(struct merge_options *o,
                get_files_dirs(o, merge);
 
                entries = get_unmerged();
-               re_head  = get_renames(o, head, common, head, merge, entries);
-               re_merge = get_renames(o, merge, common, head, merge, entries);
-               clean = process_renames(o, re_head, re_merge);
+               clean = handle_renames(o, common, head, merge, entries,
+                                      &re_info);
                record_df_conflict_files(o, entries);
                if (clean < 0)
                        goto cleanup;
@@ -2045,16 +3069,13 @@ int merge_trees(struct merge_options *o,
                }
 
 cleanup:
-               string_list_clear(re_merge, 0);
-               string_list_clear(re_head, 0);
+               final_cleanup_renames(&re_info);
+
                string_list_clear(entries, 1);
+               free(entries);
 
                hashmap_free(&o->current_file_dir_set, 1);
 
-               free(re_merge);
-               free(re_head);
-               free(entries);
-
                if (clean < 0)
                        return clean;
        }
@@ -2223,10 +3244,9 @@ int merge_recursive_generic(struct merge_options *o,
                return clean;
        }
 
-       if (active_cache_changed &&
-           write_locked_index(&the_index, &lock, COMMIT_LOCK))
+       if (write_locked_index(&the_index, &lock,
+                              COMMIT_LOCK | SKIP_IF_UNCHANGED))
                return err(o, _("Unable to write index."));
-       rollback_lock_file(&lock);
 
        return clean ? 0 : 1;
 }
index 80d69d140195cc3ba1054050569e56bfc0277b56..d863cf88676ef321aad85d395f964efe5673a805 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef MERGE_RECURSIVE_H
 #define MERGE_RECURSIVE_H
 
+#include "unpack-trees.h"
 #include "string-list.h"
 
 struct merge_options {
@@ -27,6 +28,32 @@ struct merge_options {
        struct strbuf obuf;
        struct hashmap current_file_dir_set;
        struct string_list df_conflict_file_set;
+       struct unpack_trees_options unpack_opts;
+};
+
+/*
+ * For dir_rename_entry, directory names are stored as a full path from the
+ * toplevel of the repository and do not include a trailing '/'.  Also:
+ *
+ *   dir:                original name of directory being renamed
+ *   non_unique_new_dir: if true, could not determine new_dir
+ *   new_dir:            final name of directory being renamed
+ *   possible_new_dirs:  temporary used to help determine new_dir; see comments
+ *                       in get_directory_renames() for details
+ */
+struct dir_rename_entry {
+       struct hashmap_entry ent; /* must be the first member! */
+       char *dir;
+       unsigned non_unique_new_dir:1;
+       struct strbuf new_dir;
+       struct string_list possible_new_dirs;
+};
+
+struct collision_entry {
+       struct hashmap_entry ent; /* must be the first member! */
+       char *target_file;
+       struct string_list source_files;
+       unsigned reported_already:1;
 };
 
 /* merge_trees() but with recursive ancestor consolidation */
diff --git a/perl/FromCPAN/.gitattributes b/perl/FromCPAN/.gitattributes
new file mode 100644 (file)
index 0000000..8b64fc5
--- /dev/null
@@ -0,0 +1 @@
+/Error.pm whitespace=-blank-at-eof
diff --git a/perl/FromCPAN/Error.pm b/perl/FromCPAN/Error.pm
new file mode 100644 (file)
index 0000000..8b95e2d
--- /dev/null
@@ -0,0 +1,1040 @@
+# Error.pm
+#
+# Copyright (c) 1997-8 Graham Barr <gbarr@ti.com>. All rights reserved.
+# This program is free software; you can redistribute it and/or
+# modify it under the same terms as Perl itself.
+#
+# Based on my original Error.pm, and Exceptions.pm by Peter Seibel
+# <peter@weblogic.com> and adapted by Jesse Glick <jglick@sig.bsh.com>.
+#
+# but modified ***significantly***
+
+package Error;
+
+use strict;
+use warnings;
+
+use vars qw($VERSION);
+use 5.004;
+
+$VERSION = "0.17025";
+
+use overload (
+       '""'       =>   'stringify',
+       '0+'       =>   'value',
+       'bool'     =>   sub { return 1; },
+       'fallback' =>   1
+);
+
+$Error::Depth = 0;     # Depth to pass to caller()
+$Error::Debug = 0;     # Generate verbose stack traces
+@Error::STACK = ();    # Clause stack for try
+$Error::THROWN = undef;        # last error thrown, a workaround until die $ref works
+
+my $LAST;              # Last error created
+my %ERROR;             # Last error associated with package
+
+sub _throw_Error_Simple
+{
+    my $args = shift;
+    return Error::Simple->new($args->{'text'});
+}
+
+$Error::ObjectifyCallback = \&_throw_Error_Simple;
+
+
+# Exported subs are defined in Error::subs
+
+use Scalar::Util ();
+
+sub import {
+    shift;
+    my @tags = @_;
+    local $Exporter::ExportLevel = $Exporter::ExportLevel + 1;
+
+    @tags = grep {
+       if( $_ eq ':warndie' ) {
+          Error::WarnDie->import();
+          0;
+       }
+       else {
+          1;
+       }
+    } @tags;
+
+    Error::subs->import(@tags);
+}
+
+# I really want to use last for the name of this method, but it is a keyword
+# which prevent the syntax  last Error
+
+sub prior {
+    shift; # ignore
+
+    return $LAST unless @_;
+
+    my $pkg = shift;
+    return exists $ERROR{$pkg} ? $ERROR{$pkg} : undef
+       unless ref($pkg);
+
+    my $obj = $pkg;
+    my $err = undef;
+    if($obj->isa('HASH')) {
+       $err = $obj->{'__Error__'}
+           if exists $obj->{'__Error__'};
+    }
+    elsif($obj->isa('GLOB')) {
+       $err = ${*$obj}{'__Error__'}
+           if exists ${*$obj}{'__Error__'};
+    }
+
+    $err;
+}
+
+sub flush {
+    shift; #ignore
+
+    unless (@_) {
+       $LAST = undef;
+       return;
+    }
+
+    my $pkg = shift;
+    return unless ref($pkg);
+
+    undef $ERROR{$pkg} if defined $ERROR{$pkg};
+}
+
+# Return as much information as possible about where the error
+# happened. The -stacktrace element only exists if $Error::DEBUG
+# was set when the error was created
+
+sub stacktrace {
+    my $self = shift;
+
+    return $self->{'-stacktrace'}
+       if exists $self->{'-stacktrace'};
+
+    my $text = exists $self->{'-text'} ? $self->{'-text'} : "Died";
+
+    $text .= sprintf(" at %s line %d.\n", $self->file, $self->line)
+       unless($text =~ /\n$/s);
+
+    $text;
+}
+
+
+sub associate {
+    my $err = shift;
+    my $obj = shift;
+
+    return unless ref($obj);
+
+    if($obj->isa('HASH')) {
+       $obj->{'__Error__'} = $err;
+    }
+    elsif($obj->isa('GLOB')) {
+       ${*$obj}{'__Error__'} = $err;
+    }
+    $obj = ref($obj);
+    $ERROR{ ref($obj) } = $err;
+
+    return;
+}
+
+
+sub new {
+    my $self = shift;
+    my($pkg,$file,$line) = caller($Error::Depth);
+
+    my $err = bless {
+       '-package' => $pkg,
+       '-file'    => $file,
+       '-line'    => $line,
+       @_
+    }, $self;
+
+    $err->associate($err->{'-object'})
+       if(exists $err->{'-object'});
+
+    # To always create a stacktrace would be very inefficient, so
+    # we only do it if $Error::Debug is set
+
+    if($Error::Debug) {
+       require Carp;
+       local $Carp::CarpLevel = $Error::Depth;
+       my $text = defined($err->{'-text'}) ? $err->{'-text'} : "Error";
+       my $trace = Carp::longmess($text);
+       # Remove try calls from the trace
+       $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
+       $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::run_clauses[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
+       $err->{'-stacktrace'} = $trace
+    }
+
+    $@ = $LAST = $ERROR{$pkg} = $err;
+}
+
+# Throw an error. this contains some very gory code.
+
+sub throw {
+    my $self = shift;
+    local $Error::Depth = $Error::Depth + 1;
+
+    # if we are not rethrow-ing then create the object to throw
+    $self = $self->new(@_) unless ref($self);
+
+    die $Error::THROWN = $self;
+}
+
+# syntactic sugar for
+#
+#    die with Error( ... );
+
+sub with {
+    my $self = shift;
+    local $Error::Depth = $Error::Depth + 1;
+
+    $self->new(@_);
+}
+
+# syntactic sugar for
+#
+#    record Error( ... ) and return;
+
+sub record {
+    my $self = shift;
+    local $Error::Depth = $Error::Depth + 1;
+
+    $self->new(@_);
+}
+
+# catch clause for
+#
+# try { ... } catch CLASS with { ... }
+
+sub catch {
+    my $pkg = shift;
+    my $code = shift;
+    my $clauses = shift || {};
+    my $catch = $clauses->{'catch'} ||= [];
+
+    unshift @$catch,  $pkg, $code;
+
+    $clauses;
+}
+
+# Object query methods
+
+sub object {
+    my $self = shift;
+    exists $self->{'-object'} ? $self->{'-object'} : undef;
+}
+
+sub file {
+    my $self = shift;
+    exists $self->{'-file'} ? $self->{'-file'} : undef;
+}
+
+sub line {
+    my $self = shift;
+    exists $self->{'-line'} ? $self->{'-line'} : undef;
+}
+
+sub text {
+    my $self = shift;
+    exists $self->{'-text'} ? $self->{'-text'} : undef;
+}
+
+# overload methods
+
+sub stringify {
+    my $self = shift;
+    defined $self->{'-text'} ? $self->{'-text'} : "Died";
+}
+
+sub value {
+    my $self = shift;
+    exists $self->{'-value'} ? $self->{'-value'} : undef;
+}
+
+package Error::Simple;
+
+use vars qw($VERSION);
+
+$VERSION = "0.17025";
+
+@Error::Simple::ISA = qw(Error);
+
+sub new {
+    my $self  = shift;
+    my $text  = "" . shift;
+    my $value = shift;
+    my(@args) = ();
+
+    local $Error::Depth = $Error::Depth + 1;
+
+    @args = ( -file => $1, -line => $2)
+       if($text =~ s/\s+at\s+(\S+)\s+line\s+(\d+)(?:,\s*<[^>]*>\s+line\s+\d+)?\.?\n?$//s);
+    push(@args, '-value', 0 + $value)
+       if defined($value);
+
+    $self->SUPER::new(-text => $text, @args);
+}
+
+sub stringify {
+    my $self = shift;
+    my $text = $self->SUPER::stringify;
+    $text .= sprintf(" at %s line %d.\n", $self->file, $self->line)
+       unless($text =~ /\n$/s);
+    $text;
+}
+
+##########################################################################
+##########################################################################
+
+# Inspired by code from Jesse Glick <jglick@sig.bsh.com> and
+# Peter Seibel <peter@weblogic.com>
+
+package Error::subs;
+
+use Exporter ();
+use vars qw(@EXPORT_OK @ISA %EXPORT_TAGS);
+
+@EXPORT_OK   = qw(try with finally except otherwise);
+%EXPORT_TAGS = (try => \@EXPORT_OK);
+
+@ISA = qw(Exporter);
+
+sub run_clauses ($$$\@) {
+    my($clauses,$err,$wantarray,$result) = @_;
+    my $code = undef;
+
+    $err = $Error::ObjectifyCallback->({'text' =>$err}) unless ref($err);
+
+    CATCH: {
+
+       # catch
+       my $catch;
+       if(defined($catch = $clauses->{'catch'})) {
+           my $i = 0;
+
+           CATCHLOOP:
+           for( ; $i < @$catch ; $i += 2) {
+               my $pkg = $catch->[$i];
+               unless(defined $pkg) {
+                   #except
+                   splice(@$catch,$i,2,$catch->[$i+1]->($err));
+                   $i -= 2;
+                   next CATCHLOOP;
+               }
+               elsif(Scalar::Util::blessed($err) && $err->isa($pkg)) {
+                   $code = $catch->[$i+1];
+                   while(1) {
+                       my $more = 0;
+                       local($Error::THROWN, $@);
+                       my $ok = eval {
+                           $@ = $err;
+                           if($wantarray) {
+                               @{$result} = $code->($err,\$more);
+                           }
+                           elsif(defined($wantarray)) {
+                               @{$result} = ();
+                               $result->[0] = $code->($err,\$more);
+                           }
+                           else {
+                               $code->($err,\$more);
+                           }
+                           1;
+                       };
+                       if( $ok ) {
+                           next CATCHLOOP if $more;
+                           undef $err;
+                       }
+                       else {
+                           $err = $@ || $Error::THROWN;
+                               $err = $Error::ObjectifyCallback->({'text' =>$err})
+                                       unless ref($err);
+                       }
+                       last CATCH;
+                   };
+               }
+           }
+       }
+
+       # otherwise
+       my $owise;
+       if(defined($owise = $clauses->{'otherwise'})) {
+           my $code = $clauses->{'otherwise'};
+           my $more = 0;
+        local($Error::THROWN, $@);
+           my $ok = eval {
+               $@ = $err;
+               if($wantarray) {
+                   @{$result} = $code->($err,\$more);
+               }
+               elsif(defined($wantarray)) {
+                   @{$result} = ();
+                   $result->[0] = $code->($err,\$more);
+               }
+               else {
+                   $code->($err,\$more);
+               }
+               1;
+           };
+           if( $ok ) {
+               undef $err;
+           }
+           else {
+               $err = $@ || $Error::THROWN;
+
+               $err = $Error::ObjectifyCallback->({'text' =>$err})
+                       unless ref($err);
+           }
+       }
+    }
+    $err;
+}
+
+sub try (&;$) {
+    my $try = shift;
+    my $clauses = @_ ? shift : {};
+    my $ok = 0;
+    my $err = undef;
+    my @result = ();
+
+    unshift @Error::STACK, $clauses;
+
+    my $wantarray = wantarray();
+
+    do {
+       local $Error::THROWN = undef;
+       local $@ = undef;
+
+       $ok = eval {
+           if($wantarray) {
+               @result = $try->();
+           }
+           elsif(defined $wantarray) {
+               $result[0] = $try->();
+           }
+           else {
+               $try->();
+           }
+           1;
+       };
+
+       $err = $@ || $Error::THROWN
+           unless $ok;
+    };
+
+    shift @Error::STACK;
+
+    $err = run_clauses($clauses,$err,wantarray,@result)
+    unless($ok);
+
+    $clauses->{'finally'}->()
+       if(defined($clauses->{'finally'}));
+
+    if (defined($err))
+    {
+        if (Scalar::Util::blessed($err) && $err->can('throw'))
+        {
+            throw $err;
+        }
+        else
+        {
+            die $err;
+        }
+    }
+
+    wantarray ? @result : $result[0];
+}
+
+# Each clause adds a sub to the list of clauses. The finally clause is
+# always the last, and the otherwise clause is always added just before
+# the finally clause.
+#
+# All clauses, except the finally clause, add a sub which takes one argument
+# this argument will be the error being thrown. The sub will return a code ref
+# if that clause can handle that error, otherwise undef is returned.
+#
+# The otherwise clause adds a sub which unconditionally returns the users
+# code reference, this is why it is forced to be last.
+#
+# The catch clause is defined in Error.pm, as the syntax causes it to
+# be called as a method
+
+sub with (&;$) {
+    @_
+}
+
+sub finally (&) {
+    my $code = shift;
+    my $clauses = { 'finally' => $code };
+    $clauses;
+}
+
+# The except clause is a block which returns a hashref or a list of
+# key-value pairs, where the keys are the classes and the values are subs.
+
+sub except (&;$) {
+    my $code = shift;
+    my $clauses = shift || {};
+    my $catch = $clauses->{'catch'} ||= [];
+
+    my $sub = sub {
+       my $ref;
+       my(@array) = $code->($_[0]);
+       if(@array == 1 && ref($array[0])) {
+           $ref = $array[0];
+           $ref = [ %$ref ]
+               if(UNIVERSAL::isa($ref,'HASH'));
+       }
+       else {
+           $ref = \@array;
+       }
+       @$ref
+    };
+
+    unshift @{$catch}, undef, $sub;
+
+    $clauses;
+}
+
+sub otherwise (&;$) {
+    my $code = shift;
+    my $clauses = shift || {};
+
+    if(exists $clauses->{'otherwise'}) {
+       require Carp;
+       Carp::croak("Multiple otherwise clauses");
+    }
+
+    $clauses->{'otherwise'} = $code;
+
+    $clauses;
+}
+
+1;
+
+package Error::WarnDie;
+
+sub gen_callstack($)
+{
+    my ( $start ) = @_;
+
+    require Carp;
+    local $Carp::CarpLevel = $start;
+    my $trace = Carp::longmess("");
+    # Remove try calls from the trace
+    $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
+    $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::run_clauses[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
+    my @callstack = split( m/\n/, $trace );
+    return @callstack;
+}
+
+my $old_DIE;
+my $old_WARN;
+
+sub DEATH
+{
+    my ( $e ) = @_;
+
+    local $SIG{__DIE__} = $old_DIE if( defined $old_DIE );
+
+    die @_ if $^S;
+
+    my ( $etype, $message, $location, @callstack );
+    if ( ref($e) && $e->isa( "Error" ) ) {
+        $etype = "exception of type " . ref( $e );
+        $message = $e->text;
+        $location = $e->file . ":" . $e->line;
+        @callstack = split( m/\n/, $e->stacktrace );
+    }
+    else {
+        # Don't apply subsequent layer of message formatting
+        die $e if( $e =~ m/^\nUnhandled perl error caught at toplevel:\n\n/ );
+        $etype = "perl error";
+        my $stackdepth = 0;
+        while( caller( $stackdepth ) =~ m/^Error(?:$|::)/ ) {
+            $stackdepth++
+        }
+
+        @callstack = gen_callstack( $stackdepth + 1 );
+
+        $message = "$e";
+        chomp $message;
+
+        if ( $message =~ s/ at (.*?) line (\d+)\.$// ) {
+            $location = $1 . ":" . $2;
+        }
+        else {
+            my @caller = caller( $stackdepth );
+            $location = $caller[1] . ":" . $caller[2];
+        }
+    }
+
+    shift @callstack;
+    # Do it this way in case there are no elements; we don't print a spurious \n
+    my $callstack = join( "", map { "$_\n"} @callstack );
+
+    die "\nUnhandled $etype caught at toplevel:\n\n  $message\n\nThrown from: $location\n\nFull stack trace:\n\n$callstack\n";
+}
+
+sub TAXES
+{
+    my ( $message ) = @_;
+
+    local $SIG{__WARN__} = $old_WARN if( defined $old_WARN );
+
+    $message =~ s/ at .*? line \d+\.$//;
+    chomp $message;
+
+    my @callstack = gen_callstack( 1 );
+    my $location = shift @callstack;
+
+    # $location already starts in a leading space
+    $message .= $location;
+
+    # Do it this way in case there are no elements; we don't print a spurious \n
+    my $callstack = join( "", map { "$_\n"} @callstack );
+
+    warn "$message:\n$callstack";
+}
+
+sub import
+{
+    $old_DIE  = $SIG{__DIE__};
+    $old_WARN = $SIG{__WARN__};
+
+    $SIG{__DIE__}  = \&DEATH;
+    $SIG{__WARN__} = \&TAXES;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Error - Error/exception handling in an OO-ish way
+
+=head1 WARNING
+
+Using the "Error" module is B<no longer recommended> due to the black-magical
+nature of its syntactic sugar, which often tends to break. Its maintainers
+have stopped actively writing code that uses it, and discourage people
+from doing so. See the "SEE ALSO" section below for better recommendations.
+
+=head1 SYNOPSIS
+
+    use Error qw(:try);
+
+    throw Error::Simple( "A simple error");
+
+    sub xyz {
+        ...
+       record Error::Simple("A simple error")
+           and return;
+    }
+
+    unlink($file) or throw Error::Simple("$file: $!",$!);
+
+    try {
+       do_some_stuff();
+       die "error!" if $condition;
+       throw Error::Simple "Oops!" if $other_condition;
+    }
+    catch Error::IO with {
+       my $E = shift;
+       print STDERR "File ", $E->{'-file'}, " had a problem\n";
+    }
+    except {
+       my $E = shift;
+       my $general_handler=sub {send_message $E->{-description}};
+       return {
+           UserException1 => $general_handler,
+           UserException2 => $general_handler
+       };
+    }
+    otherwise {
+       print STDERR "Well I don't know what to say\n";
+    }
+    finally {
+       close_the_garage_door_already(); # Should be reliable
+    }; # Don't forget the trailing ; or you might be surprised
+
+=head1 DESCRIPTION
+
+The C<Error> package provides two interfaces. Firstly C<Error> provides
+a procedural interface to exception handling. Secondly C<Error> is a
+base class for errors/exceptions that can either be thrown, for
+subsequent catch, or can simply be recorded.
+
+Errors in the class C<Error> should not be thrown directly, but the
+user should throw errors from a sub-class of C<Error>.
+
+=head1 PROCEDURAL INTERFACE
+
+C<Error> exports subroutines to perform exception handling. These will
+be exported if the C<:try> tag is used in the C<use> line.
+
+=over 4
+
+=item try BLOCK CLAUSES
+
+C<try> is the main subroutine called by the user. All other subroutines
+exported are clauses to the try subroutine.
+
+The BLOCK will be evaluated and, if no error is throw, try will return
+the result of the block.
+
+C<CLAUSES> are the subroutines below, which describe what to do in the
+event of an error being thrown within BLOCK.
+
+=item catch CLASS with BLOCK
+
+This clauses will cause all errors that satisfy C<$err-E<gt>isa(CLASS)>
+to be caught and handled by evaluating C<BLOCK>.
+
+C<BLOCK> will be passed two arguments. The first will be the error
+being thrown. The second is a reference to a scalar variable. If this
+variable is set by the catch block then, on return from the catch
+block, try will continue processing as if the catch block was never
+found. The error will also be available in C<$@>.
+
+To propagate the error the catch block may call C<$err-E<gt>throw>
+
+If the scalar reference by the second argument is not set, and the
+error is not thrown. Then the current try block will return with the
+result from the catch block.
+
+=item except BLOCK
+
+When C<try> is looking for a handler, if an except clause is found
+C<BLOCK> is evaluated. The return value from this block should be a
+HASHREF or a list of key-value pairs, where the keys are class names
+and the values are CODE references for the handler of errors of that
+type.
+
+=item otherwise BLOCK
+
+Catch any error by executing the code in C<BLOCK>
+
+When evaluated C<BLOCK> will be passed one argument, which will be the
+error being processed. The error will also be available in C<$@>.
+
+Only one otherwise block may be specified per try block
+
+=item finally BLOCK
+
+Execute the code in C<BLOCK> either after the code in the try block has
+successfully completed, or if the try block throws an error then
+C<BLOCK> will be executed after the handler has completed.
+
+If the handler throws an error then the error will be caught, the
+finally block will be executed and the error will be re-thrown.
+
+Only one finally block may be specified per try block
+
+=back
+
+=head1 COMPATIBILITY
+
+L<Moose> exports a keyword called C<with> which clashes with Error's. This
+example returns a prototype mismatch error:
+
+    package MyTest;
+
+    use warnings;
+    use Moose;
+    use Error qw(:try);
+
+(Thanks to C<maik.hentsche@amd.com> for the report.).
+
+=head1 CLASS INTERFACE
+
+=head2 CONSTRUCTORS
+
+The C<Error> object is implemented as a HASH. This HASH is initialized
+with the arguments that are passed to it's constructor. The elements
+that are used by, or are retrievable by the C<Error> class are listed
+below, other classes may add to these.
+
+       -file
+       -line
+       -text
+       -value
+       -object
+
+If C<-file> or C<-line> are not specified in the constructor arguments
+then these will be initialized with the file name and line number where
+the constructor was called from.
+
+If the error is associated with an object then the object should be
+passed as the C<-object> argument. This will allow the C<Error> package
+to associate the error with the object.
+
+The C<Error> package remembers the last error created, and also the
+last error associated with a package. This could either be the last
+error created by a sub in that package, or the last error which passed
+an object blessed into that package as the C<-object> argument.
+
+=over 4
+
+=item Error->new()
+
+See the Error::Simple documentation.
+
+=item throw ( [ ARGS ] )
+
+Create a new C<Error> object and throw an error, which will be caught
+by a surrounding C<try> block, if there is one. Otherwise it will cause
+the program to exit.
+
+C<throw> may also be called on an existing error to re-throw it.
+
+=item with ( [ ARGS ] )
+
+Create a new C<Error> object and returns it. This is defined for
+syntactic sugar, eg
+
+    die with Some::Error ( ... );
+
+=item record ( [ ARGS ] )
+
+Create a new C<Error> object and returns it. This is defined for
+syntactic sugar, eg
+
+    record Some::Error ( ... )
+       and return;
+
+=back
+
+=head2 STATIC METHODS
+
+=over 4
+
+=item prior ( [ PACKAGE ] )
+
+Return the last error created, or the last error associated with
+C<PACKAGE>
+
+=item flush ( [ PACKAGE ] )
+
+Flush the last error created, or the last error associated with
+C<PACKAGE>.It is necessary to clear the error stack before exiting the
+package or uncaught errors generated using C<record> will be reported.
+
+     $Error->flush;
+
+=cut
+
+=back
+
+=head2 OBJECT METHODS
+
+=over 4
+
+=item stacktrace
+
+If the variable C<$Error::Debug> was non-zero when the error was
+created, then C<stacktrace> returns a string created by calling
+C<Carp::longmess>. If the variable was zero the C<stacktrace> returns
+the text of the error appended with the filename and line number of
+where the error was created, providing the text does not end with a
+newline.
+
+=item object
+
+The object this error was associated with
+
+=item file
+
+The file where the constructor of this error was called from
+
+=item line
+
+The line where the constructor of this error was called from
+
+=item text
+
+The text of the error
+
+=item $err->associate($obj)
+
+Associates an error with an object to allow error propagation. I.e:
+
+    $ber->encode(...) or
+        return Error->prior($ber)->associate($ldap);
+
+=back
+
+=head2 OVERLOAD METHODS
+
+=over 4
+
+=item stringify
+
+A method that converts the object into a string. This method may simply
+return the same as the C<text> method, or it may append more
+information. For example the file name and line number.
+
+By default this method returns the C<-text> argument that was passed to
+the constructor, or the string C<"Died"> if none was given.
+
+=item value
+
+A method that will return a value that can be associated with the
+error. For example if an error was created due to the failure of a
+system call, then this may return the numeric value of C<$!> at the
+time.
+
+By default this method returns the C<-value> argument that was passed
+to the constructor.
+
+=back
+
+=head1 PRE-DEFINED ERROR CLASSES
+
+=head2 Error::Simple
+
+This class can be used to hold simple error strings and values. It's
+constructor takes two arguments. The first is a text value, the second
+is a numeric value. These values are what will be returned by the
+overload methods.
+
+If the text value ends with C<at file line 1> as $@ strings do, then
+this information will be used to set the C<-file> and C<-line> arguments
+of the error object.
+
+This class is used internally if an eval'd block die's with an error
+that is a plain string. (Unless C<$Error::ObjectifyCallback> is modified)
+
+
+=head1 $Error::ObjectifyCallback
+
+This variable holds a reference to a subroutine that converts errors that
+are plain strings to objects. It is used by Error.pm to convert textual
+errors to objects, and can be overridden by the user.
+
+It accepts a single argument which is a hash reference to named parameters.
+Currently the only named parameter passed is C<'text'> which is the text
+of the error, but others may be available in the future.
+
+For example the following code will cause Error.pm to throw objects of the
+class MyError::Bar by default:
+
+    sub throw_MyError_Bar
+    {
+        my $args = shift;
+        my $err = MyError::Bar->new();
+        $err->{'MyBarText'} = $args->{'text'};
+        return $err;
+    }
+
+    {
+        local $Error::ObjectifyCallback = \&throw_MyError_Bar;
+
+        # Error handling here.
+    }
+
+=cut
+
+=head1 MESSAGE HANDLERS
+
+C<Error> also provides handlers to extend the output of the C<warn()> perl
+function, and to handle the printing of a thrown C<Error> that is not caught
+or otherwise handled. These are not installed by default, but are requested
+using the C<:warndie> tag in the C<use> line.
+
+ use Error qw( :warndie );
+
+These new error handlers are installed in C<$SIG{__WARN__}> and
+C<$SIG{__DIE__}>. If these handlers are already defined when the tag is
+imported, the old values are stored, and used during the new code. Thus, to
+arrange for custom handling of warnings and errors, you will need to perform
+something like the following:
+
+ BEGIN {
+   $SIG{__WARN__} = sub {
+     print STDERR "My special warning handler: $_[0]"
+   };
+ }
+
+ use Error qw( :warndie );
+
+Note that setting C<$SIG{__WARN__}> after the C<:warndie> tag has been
+imported will overwrite the handler that C<Error> provides. If this cannot be
+avoided, then the tag can be explicitly C<import>ed later
+
+ use Error;
+
+ $SIG{__WARN__} = ...;
+
+ import Error qw( :warndie );
+
+=head2 EXAMPLE
+
+The C<__DIE__> handler turns messages such as
+
+ Can't call method "foo" on an undefined value at examples/warndie.pl line 16.
+
+into
+
+ Unhandled perl error caught at toplevel:
+
+   Can't call method "foo" on an undefined value
+
+ Thrown from: examples/warndie.pl:16
+
+ Full stack trace:
+
+         main::inner('undef') called at examples/warndie.pl line 20
+         main::outer('undef') called at examples/warndie.pl line 23
+
+=cut
+
+=head1 SEE ALSO
+
+See L<Exception::Class> for a different module providing Object-Oriented
+exception handling, along with a convenient syntax for declaring hierarchies
+for them. It doesn't provide Error's syntactic sugar of C<try { ... }>,
+C<catch { ... }>, etc. which may be a good thing or a bad thing based
+on what you want. (Because Error's syntactic sugar tends to break.)
+
+L<Error::Exception> aims to combine L<Error> and L<Exception::Class>
+"with correct stringification".
+
+L<TryCatch> and L<Try::Tiny> are similar in concept to Error.pm only providing
+a syntax that hopefully breaks less.
+
+=head1 KNOWN BUGS
+
+None, but that does not mean there are not any.
+
+=head1 AUTHORS
+
+Graham Barr <gbarr@pobox.com>
+
+The code that inspired me to write this was originally written by
+Peter Seibel <peter@weblogic.com> and adapted by Jesse Glick
+<jglick@sig.bsh.com>.
+
+C<:warndie> handlers added by Paul Evans <leonerd@leonerd.org.uk>
+
+=head1 MAINTAINER
+
+Shlomi Fish, L<http://www.shlomifish.org/> .
+
+=head1 PAST MAINTAINERS
+
+Arun Kumar U <u_arunkumar@yahoo.com>
+
+=head1 COPYRIGHT
+
+Copyright (c) 1997-8  Graham Barr. All rights reserved.
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+=cut
diff --git a/perl/FromCPAN/Mail/Address.pm b/perl/FromCPAN/Mail/Address.pm
new file mode 100644 (file)
index 0000000..683d490
--- /dev/null
@@ -0,0 +1,280 @@
+# Copyrights 1995-2018 by [Mark Overmeer].
+#  For other contributors see ChangeLog.
+# See the manual pages for details on the licensing terms.
+# Pod stripped from pm file by OODoc 2.02.
+# This code is part of the bundle MailTools.  Meta-POD processed with
+# OODoc into POD and HTML manual-pages.  See README.md for Copyright.
+# Licensed under the same terms as Perl itself.
+
+package Mail::Address;
+use vars '$VERSION';
+$VERSION = '2.20';
+
+use strict;
+
+use Carp;
+
+# use locale;   removed in version 1.78, because it causes taint problems
+
+sub Version { our $VERSION }
+
+
+
+# given a comment, attempt to extract a person's name
+sub _extract_name
+{   # This function can be called as method as well
+    my $self = @_ && ref $_[0] ? shift : undef;
+
+    local $_ = shift
+        or return '';
+
+    # Using encodings, too hard. See Mail::Message::Field::Full.
+    return '' if m/\=\?.*?\?\=/;
+
+    # trim whitespace
+    s/^\s+//;
+    s/\s+$//;
+    s/\s+/ /;
+
+    # Disregard numeric names (e.g. 123456.1234@compuserve.com)
+    return "" if /^[\d ]+$/;
+
+    s/^\((.*)\)$/$1/; # remove outermost parenthesis
+    s/^"(.*)"$/$1/;   # remove outer quotation marks
+    s/\(.*?\)//g;     # remove minimal embedded comments
+    s/\\//g;          # remove all escapes
+    s/^"(.*)"$/$1/;   # remove internal quotation marks
+    s/^([^\s]+) ?, ?(.*)$/$2 $1/; # reverse "Last, First M." if applicable
+    s/,.*//;
+
+    # Change casing only when the name contains only upper or only
+    # lower cased characters.
+    unless( m/[A-Z]/ && m/[a-z]/ )
+    {   # Set the case of the name to first char upper rest lower
+        s/\b(\w+)/\L\u$1/igo;  # Upcase first letter on name
+        s/\bMc(\w)/Mc\u$1/igo; # Scottish names such as 'McLeod'
+        s/\bo'(\w)/O'\u$1/igo; # Irish names such as 'O'Malley, O'Reilly'
+        s/\b(x*(ix)?v*(iv)?i*)\b/\U$1/igo; # Roman numerals, eg 'Level III Support'
+    }
+
+    # some cleanup
+    s/\[[^\]]*\]//g;
+    s/(^[\s'"]+|[\s'"]+$)//g;
+    s/\s{2,}/ /g;
+
+    $_;
+}
+
+sub _tokenise
+{   local $_ = join ',', @_;
+    my (@words,$snippet,$field);
+
+    s/\A\s+//;
+    s/[\r\n]+/ /g;
+
+    while ($_ ne '')
+    {   $field = '';
+        if(s/^\s*\(/(/ )    # (...)
+        {   my $depth = 0;
+
+     PAREN: while(s/^(\(([^\(\)\\]|\\.)*)//)
+            {   $field .= $1;
+                $depth++;
+                while(s/^(([^\(\)\\]|\\.)*\)\s*)//)
+                {   $field .= $1;
+                    last PAREN unless --$depth;
+                   $field .= $1 if s/^(([^\(\)\\]|\\.)+)//;
+                }
+            }
+
+            carp "Unmatched () '$field' '$_'"
+                if $depth;
+
+            $field =~ s/\s+\Z//;
+            push @words, $field;
+
+            next;
+        }
+
+        if( s/^("(?:[^"\\]+|\\.)*")\s*//       # "..."
+         || s/^(\[(?:[^\]\\]+|\\.)*\])\s*//    # [...]
+         || s/^([^\s()<>\@,;:\\".[\]]+)\s*//
+         || s/^([()<>\@,;:\\".[\]])\s*//
+          )
+        {   push @words, $1;
+            next;
+        }
+
+        croak "Unrecognised line: $_";
+    }
+
+    push @words, ",";
+    \@words;
+}
+
+sub _find_next
+{   my ($idx, $tokens, $len) = @_;
+
+    while($idx < $len)
+    {   my $c = $tokens->[$idx];
+        return $c if $c eq ',' || $c eq ';' || $c eq '<';
+        $idx++;
+    }
+
+    "";
+}
+
+sub _complete
+{   my ($class, $phrase, $address, $comment) = @_;
+
+    @$phrase || @$comment || @$address
+       or return undef;
+
+    my $o = $class->new(join(" ",@$phrase), join("",@$address), join(" ",@$comment));
+    @$phrase = @$address = @$comment = ();
+    $o;
+}
+
+#------------
+
+sub new(@)
+{   my $class = shift;
+    bless [@_], $class;
+}
+
+
+sub parse(@)
+{   my $class = shift;
+    my @line  = grep {defined} @_;
+    my $line  = join '', @line;
+
+    my (@phrase, @comment, @address, @objs);
+    my ($depth, $idx) = (0, 0);
+
+    my $tokens  = _tokenise @line;
+    my $len     = @$tokens;
+    my $next    = _find_next $idx, $tokens, $len;
+
+    local $_;
+    for(my $idx = 0; $idx < $len; $idx++)
+    {   $_ = $tokens->[$idx];
+
+        if(substr($_,0,1) eq '(') { push @comment, $_ }
+        elsif($_ eq '<')    { $depth++ }
+        elsif($_ eq '>')    { $depth-- if $depth }
+        elsif($_ eq ',' || $_ eq ';')
+        {   warn "Unmatched '<>' in $line" if $depth;
+            my $o = $class->_complete(\@phrase, \@address, \@comment);
+            push @objs, $o if defined $o;
+            $depth = 0;
+            $next = _find_next $idx+1, $tokens, $len;
+        }
+        elsif($depth)       { push @address, $_ }
+        elsif($next eq '<') { push @phrase,  $_ }
+        elsif( /^[.\@:;]$/ || !@address || $address[-1] =~ /^[.\@:;]$/ )
+        {   push @address, $_ }
+        else
+        {   warn "Unmatched '<>' in $line" if $depth;
+            my $o = $class->_complete(\@phrase, \@address, \@comment);
+            push @objs, $o if defined $o;
+            $depth = 0;
+            push @address, $_;
+        }
+    }
+    @objs;
+}
+
+#------------
+
+sub phrase  { shift->set_or_get(0, @_) }
+sub address { shift->set_or_get(1, @_) }
+sub comment { shift->set_or_get(2, @_) }
+
+sub set_or_get($)
+{   my ($self, $i) = (shift, shift);
+    @_ or return $self->[$i];
+
+    my $val = $self->[$i];
+    $self->[$i] = shift if @_;
+    $val;
+}
+
+
+my $atext = '[\-\w !#$%&\'*+/=?^`{|}~]';
+sub format
+{   my @addrs;
+
+    foreach (@_)
+    {   my ($phrase, $email, $comment) = @$_;
+        my @addr;
+
+        if(defined $phrase && length $phrase)
+        {   push @addr
+              , $phrase =~ /^(?:\s*$atext\s*)+$/o ? $phrase
+              : $phrase =~ /(?<!\\)"/             ? $phrase
+              :                                    qq("$phrase");
+
+            push @addr, "<$email>"
+                if defined $email && length $email;
+        }
+        elsif(defined $email && length $email)
+        {   push @addr, $email;
+        }
+
+        if(defined $comment && $comment =~ /\S/)
+        {   $comment =~ s/^\s*\(?/(/;
+            $comment =~ s/\)?\s*$/)/;
+        }
+
+        push @addr, $comment
+            if defined $comment && length $comment;
+
+        push @addrs, join(" ", @addr)
+            if @addr;
+    }
+
+    join ", ", @addrs;
+}
+
+#------------
+
+sub name
+{   my $self   = shift;
+    my $phrase = $self->phrase;
+    my $addr   = $self->address;
+
+    $phrase    = $self->comment
+        unless defined $phrase && length $phrase;
+
+    my $name   = $self->_extract_name($phrase);
+
+    # first.last@domain address
+    if($name eq '' && $addr =~ /([^\%\.\@_]+([\._][^\%\.\@_]+)+)[\@\%]/)
+    {   ($name  = $1) =~ s/[\._]+/ /g;
+       $name   = _extract_name $name;
+    }
+
+    if($name eq '' && $addr =~ m#/g=#i)    # X400 style address
+    {   my ($f) = $addr =~ m#g=([^/]*)#i;
+       my ($l) = $addr =~ m#s=([^/]*)#i;
+       $name   = _extract_name "$f $l";
+    }
+
+    length $name ? $name : undef;
+}
+
+
+sub host
+{   my $addr = shift->address || '';
+    my $i    = rindex $addr, '@';
+    $i >= 0 ? substr($addr, $i+1) : undef;
+}
+
+
+sub user
+{   my $addr = shift->address || '';
+    my $i    = rindex $addr, '@';
+    $i >= 0 ? substr($addr,0,$i) : $addr;
+}
+
+1;
index a7440a1f096aa193a21688414669300425146382..16ebcc612ce4acb4fba6511d5b388184934cb22a 100644 (file)
@@ -9,7 +9,10 @@ package Git;
 
 use 5.008;
 use strict;
+use warnings;
 
+use File::Temp ();
+use File::Spec ();
 
 BEGIN {
 
@@ -101,7 +104,7 @@ =head1 DESCRIPTION
 
 
 use Carp qw(carp croak); # but croak is bad - throw instead
-use Git::Error qw(:try);
+use Git::LoadCPAN::Error qw(:try);
 use Cwd qw(abs_path cwd);
 use IPC::Open2 qw(open2);
 use Fcntl qw(SEEK_SET SEEK_CUR);
@@ -189,7 +192,6 @@ sub repository {
                };
 
                if ($dir) {
-                       _verify_require();
                        File::Spec->file_name_is_absolute($dir) or $dir = $opts{Directory} . '/' . $dir;
                        $opts{Repository} = abs_path($dir);
 
@@ -1290,8 +1292,6 @@ sub temp_release {
 sub _temp_cache {
        my ($self, $name) = _maybe_self(@_);
 
-       _verify_require();
-
        my $temp_fd = \$TEMP_FILEMAP{$name};
        if (defined $$temp_fd and $$temp_fd->opened) {
                if ($TEMP_FILES{$$temp_fd}{locked}) {
@@ -1325,11 +1325,6 @@ sub _temp_cache {
        $$temp_fd;
 }
 
-sub _verify_require {
-       eval { require File::Temp; require File::Spec; };
-       $@ and throw Error::Simple($@);
-}
-
 =item temp_reset ( FILEHANDLE )
 
 Truncates and resets the position of the C<FILEHANDLE>.
@@ -1694,7 +1689,6 @@ sub DESTROY {
 # Pipe implementation for ActiveState Perl.
 
 package Git::activestate_pipe;
-use strict;
 
 sub TIEHANDLE {
        my ($class, @params) = @_;
diff --git a/perl/Git/Error.pm b/perl/Git/Error.pm
deleted file mode 100644 (file)
index 09bbc97..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package Git::Error;
-use 5.008;
-use strict;
-use warnings;
-
-=head1 NAME
-
-Git::Error - Wrapper for the L<Error> module, in case it's not installed
-
-=head1 DESCRIPTION
-
-Wraps the import function for the L<Error> module.
-
-This module is only intended to be used for code shipping in the
-C<git.git> repository. Use it for anything else at your peril!
-
-=cut
-
-sub import {
-    shift;
-    my $caller = caller;
-
-    eval {
-       require Error;
-       1;
-    } or do {
-       my $error = $@ || "Zombie Error";
-
-       my $Git_Error_pm_path = $INC{"Git/Error.pm"} || die "BUG: Should have our own path from %INC!";
-
-       require File::Basename;
-       my $Git_Error_pm_root = File::Basename::dirname($Git_Error_pm_path) || die "BUG: Can't figure out lib/Git dirname from '$Git_Error_pm_path'!";
-
-       require File::Spec;
-       my $Git_pm_FromCPAN_root = File::Spec->catdir($Git_Error_pm_root, 'FromCPAN');
-       die "BUG: '$Git_pm_FromCPAN_root' should be a directory!" unless -d $Git_pm_FromCPAN_root;
-
-       local @INC = ($Git_pm_FromCPAN_root, @INC);
-       require Error;
-    };
-
-    unshift @_, $caller;
-    goto &Error::import;
-}
-
-1;
diff --git a/perl/Git/FromCPAN/Error.pm b/perl/Git/FromCPAN/Error.pm
deleted file mode 100644 (file)
index 6098135..0000000
+++ /dev/null
@@ -1,827 +0,0 @@
-# Error.pm
-#
-# Copyright (c) 1997-8 Graham Barr <gbarr@ti.com>. All rights reserved.
-# This program is free software; you can redistribute it and/or
-# modify it under the same terms as Perl itself.
-#
-# Based on my original Error.pm, and Exceptions.pm by Peter Seibel
-# <peter@weblogic.com> and adapted by Jesse Glick <jglick@sig.bsh.com>.
-#
-# but modified ***significantly***
-
-package Error;
-
-use strict;
-use vars qw($VERSION);
-use 5.004;
-
-$VERSION = "0.15009";
-
-use overload (
-       '""'       =>   'stringify',
-       '0+'       =>   'value',
-       'bool'     =>   sub { return 1; },
-       'fallback' =>   1
-);
-
-$Error::Depth = 0;     # Depth to pass to caller()
-$Error::Debug = 0;     # Generate verbose stack traces
-@Error::STACK = ();    # Clause stack for try
-$Error::THROWN = undef;        # last error thrown, a workaround until die $ref works
-
-my $LAST;              # Last error created
-my %ERROR;             # Last error associated with package
-
-sub throw_Error_Simple
-{
-    my $args = shift;
-    return Error::Simple->new($args->{'text'});
-}
-
-$Error::ObjectifyCallback = \&throw_Error_Simple;
-
-
-# Exported subs are defined in Error::subs
-
-sub import {
-    shift;
-    local $Exporter::ExportLevel = $Exporter::ExportLevel + 1;
-    Error::subs->import(@_);
-}
-
-# I really want to use last for the name of this method, but it is a keyword
-# which prevent the syntax  last Error
-
-sub prior {
-    shift; # ignore
-
-    return $LAST unless @_;
-
-    my $pkg = shift;
-    return exists $ERROR{$pkg} ? $ERROR{$pkg} : undef
-       unless ref($pkg);
-
-    my $obj = $pkg;
-    my $err = undef;
-    if($obj->isa('HASH')) {
-       $err = $obj->{'__Error__'}
-           if exists $obj->{'__Error__'};
-    }
-    elsif($obj->isa('GLOB')) {
-       $err = ${*$obj}{'__Error__'}
-           if exists ${*$obj}{'__Error__'};
-    }
-
-    $err;
-}
-
-sub flush {
-    shift; #ignore
-
-    unless (@_) {
-       $LAST = undef;
-       return;
-    }
-
-    my $pkg = shift;
-    return unless ref($pkg);
-
-    undef $ERROR{$pkg} if defined $ERROR{$pkg};
-}
-
-# Return as much information as possible about where the error
-# happened. The -stacktrace element only exists if $Error::DEBUG
-# was set when the error was created
-
-sub stacktrace {
-    my $self = shift;
-
-    return $self->{'-stacktrace'}
-       if exists $self->{'-stacktrace'};
-
-    my $text = exists $self->{'-text'} ? $self->{'-text'} : "Died";
-
-    $text .= sprintf(" at %s line %d.\n", $self->file, $self->line)
-       unless($text =~ /\n$/s);
-
-    $text;
-}
-
-# Allow error propagation, ie
-#
-# $ber->encode(...) or
-#    return Error->prior($ber)->associate($ldap);
-
-sub associate {
-    my $err = shift;
-    my $obj = shift;
-
-    return unless ref($obj);
-
-    if($obj->isa('HASH')) {
-       $obj->{'__Error__'} = $err;
-    }
-    elsif($obj->isa('GLOB')) {
-       ${*$obj}{'__Error__'} = $err;
-    }
-    $obj = ref($obj);
-    $ERROR{ ref($obj) } = $err;
-
-    return;
-}
-
-sub new {
-    my $self = shift;
-    my($pkg,$file,$line) = caller($Error::Depth);
-
-    my $err = bless {
-       '-package' => $pkg,
-       '-file'    => $file,
-       '-line'    => $line,
-       @_
-    }, $self;
-
-    $err->associate($err->{'-object'})
-       if(exists $err->{'-object'});
-
-    # To always create a stacktrace would be very inefficient, so
-    # we only do it if $Error::Debug is set
-
-    if($Error::Debug) {
-       require Carp;
-       local $Carp::CarpLevel = $Error::Depth;
-       my $text = defined($err->{'-text'}) ? $err->{'-text'} : "Error";
-       my $trace = Carp::longmess($text);
-       # Remove try calls from the trace
-       $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
-       $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::run_clauses[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
-       $err->{'-stacktrace'} = $trace
-    }
-
-    $@ = $LAST = $ERROR{$pkg} = $err;
-}
-
-# Throw an error. this contains some very gory code.
-
-sub throw {
-    my $self = shift;
-    local $Error::Depth = $Error::Depth + 1;
-
-    # if we are not rethrow-ing then create the object to throw
-    $self = $self->new(@_) unless ref($self);
-
-    die $Error::THROWN = $self;
-}
-
-# syntactic sugar for
-#
-#    die with Error( ... );
-
-sub with {
-    my $self = shift;
-    local $Error::Depth = $Error::Depth + 1;
-
-    $self->new(@_);
-}
-
-# syntactic sugar for
-#
-#    record Error( ... ) and return;
-
-sub record {
-    my $self = shift;
-    local $Error::Depth = $Error::Depth + 1;
-
-    $self->new(@_);
-}
-
-# catch clause for
-#
-# try { ... } catch CLASS with { ... }
-
-sub catch {
-    my $pkg = shift;
-    my $code = shift;
-    my $clauses = shift || {};
-    my $catch = $clauses->{'catch'} ||= [];
-
-    unshift @$catch,  $pkg, $code;
-
-    $clauses;
-}
-
-# Object query methods
-
-sub object {
-    my $self = shift;
-    exists $self->{'-object'} ? $self->{'-object'} : undef;
-}
-
-sub file {
-    my $self = shift;
-    exists $self->{'-file'} ? $self->{'-file'} : undef;
-}
-
-sub line {
-    my $self = shift;
-    exists $self->{'-line'} ? $self->{'-line'} : undef;
-}
-
-sub text {
-    my $self = shift;
-    exists $self->{'-text'} ? $self->{'-text'} : undef;
-}
-
-# overload methods
-
-sub stringify {
-    my $self = shift;
-    defined $self->{'-text'} ? $self->{'-text'} : "Died";
-}
-
-sub value {
-    my $self = shift;
-    exists $self->{'-value'} ? $self->{'-value'} : undef;
-}
-
-package Error::Simple;
-
-@Error::Simple::ISA = qw(Error);
-
-sub new {
-    my $self  = shift;
-    my $text  = "" . shift;
-    my $value = shift;
-    my(@args) = ();
-
-    local $Error::Depth = $Error::Depth + 1;
-
-    @args = ( -file => $1, -line => $2)
-       if($text =~ s/\s+at\s+(\S+)\s+line\s+(\d+)(?:,\s*<[^>]*>\s+line\s+\d+)?\.?\n?$//s);
-    push(@args, '-value', 0 + $value)
-       if defined($value);
-
-    $self->SUPER::new(-text => $text, @args);
-}
-
-sub stringify {
-    my $self = shift;
-    my $text = $self->SUPER::stringify;
-    $text .= sprintf(" at %s line %d.\n", $self->file, $self->line)
-       unless($text =~ /\n$/s);
-    $text;
-}
-
-##########################################################################
-##########################################################################
-
-# Inspired by code from Jesse Glick <jglick@sig.bsh.com> and
-# Peter Seibel <peter@weblogic.com>
-
-package Error::subs;
-
-use Exporter ();
-use vars qw(@EXPORT_OK @ISA %EXPORT_TAGS);
-
-@EXPORT_OK   = qw(try with finally except otherwise);
-%EXPORT_TAGS = (try => \@EXPORT_OK);
-
-@ISA = qw(Exporter);
-
-
-sub blessed {
-       my $item = shift;
-       local $@; # don't kill an outer $@
-       ref $item and eval { $item->can('can') };
-}
-
-
-sub run_clauses ($$$\@) {
-    my($clauses,$err,$wantarray,$result) = @_;
-    my $code = undef;
-
-    $err = $Error::ObjectifyCallback->({'text' =>$err}) unless ref($err);
-
-    CATCH: {
-
-       # catch
-       my $catch;
-       if(defined($catch = $clauses->{'catch'})) {
-           my $i = 0;
-
-           CATCHLOOP:
-           for( ; $i < @$catch ; $i += 2) {
-               my $pkg = $catch->[$i];
-               unless(defined $pkg) {
-                   #except
-                   splice(@$catch,$i,2,$catch->[$i+1]->());
-                   $i -= 2;
-                   next CATCHLOOP;
-               }
-               elsif(blessed($err) && $err->isa($pkg)) {
-                   $code = $catch->[$i+1];
-                   while(1) {
-                       my $more = 0;
-                       local($Error::THROWN);
-                       my $ok = eval {
-                           if($wantarray) {
-                               @{$result} = $code->($err,\$more);
-                           }
-                           elsif(defined($wantarray)) {
-                               @{$result} = ();
-                               $result->[0] = $code->($err,\$more);
-                           }
-                           else {
-                               $code->($err,\$more);
-                           }
-                           1;
-                       };
-                       if( $ok ) {
-                           next CATCHLOOP if $more;
-                           undef $err;
-                       }
-                       else {
-                           $err = defined($Error::THROWN)
-                                   ? $Error::THROWN : $@;
-                $err = $Error::ObjectifyCallback->({'text' =>$err})
-                    unless ref($err);
-                       }
-                       last CATCH;
-                   };
-               }
-           }
-       }
-
-       # otherwise
-       my $owise;
-       if(defined($owise = $clauses->{'otherwise'})) {
-           my $code = $clauses->{'otherwise'};
-           my $more = 0;
-           my $ok = eval {
-               if($wantarray) {
-                   @{$result} = $code->($err,\$more);
-               }
-               elsif(defined($wantarray)) {
-                   @{$result} = ();
-                   $result->[0] = $code->($err,\$more);
-               }
-               else {
-                   $code->($err,\$more);
-               }
-               1;
-           };
-           if( $ok ) {
-               undef $err;
-           }
-           else {
-               $err = defined($Error::THROWN)
-                       ? $Error::THROWN : $@;
-
-        $err = $Error::ObjectifyCallback->({'text' =>$err})
-            unless ref($err);
-           }
-       }
-    }
-    $err;
-}
-
-sub try (&;$) {
-    my $try = shift;
-    my $clauses = @_ ? shift : {};
-    my $ok = 0;
-    my $err = undef;
-    my @result = ();
-
-    unshift @Error::STACK, $clauses;
-
-    my $wantarray = wantarray();
-
-    do {
-       local $Error::THROWN = undef;
-    local $@ = undef;
-
-       $ok = eval {
-           if($wantarray) {
-               @result = $try->();
-           }
-           elsif(defined $wantarray) {
-               $result[0] = $try->();
-           }
-           else {
-               $try->();
-           }
-           1;
-       };
-
-       $err = defined($Error::THROWN) ? $Error::THROWN : $@
-           unless $ok;
-    };
-
-    shift @Error::STACK;
-
-    $err = run_clauses($clauses,$err,wantarray,@result)
-       unless($ok);
-
-    $clauses->{'finally'}->()
-       if(defined($clauses->{'finally'}));
-
-    if (defined($err))
-    {
-        if (blessed($err) && $err->can('throw'))
-        {
-            throw $err;
-        }
-        else
-        {
-            die $err;
-        }
-    }
-
-    wantarray ? @result : $result[0];
-}
-
-# Each clause adds a sub to the list of clauses. The finally clause is
-# always the last, and the otherwise clause is always added just before
-# the finally clause.
-#
-# All clauses, except the finally clause, add a sub which takes one argument
-# this argument will be the error being thrown. The sub will return a code ref
-# if that clause can handle that error, otherwise undef is returned.
-#
-# The otherwise clause adds a sub which unconditionally returns the users
-# code reference, this is why it is forced to be last.
-#
-# The catch clause is defined in Error.pm, as the syntax causes it to
-# be called as a method
-
-sub with (&;$) {
-    @_
-}
-
-sub finally (&) {
-    my $code = shift;
-    my $clauses = { 'finally' => $code };
-    $clauses;
-}
-
-# The except clause is a block which returns a hashref or a list of
-# key-value pairs, where the keys are the classes and the values are subs.
-
-sub except (&;$) {
-    my $code = shift;
-    my $clauses = shift || {};
-    my $catch = $clauses->{'catch'} ||= [];
-
-    my $sub = sub {
-       my $ref;
-       my(@array) = $code->($_[0]);
-       if(@array == 1 && ref($array[0])) {
-           $ref = $array[0];
-           $ref = [ %$ref ]
-               if(UNIVERSAL::isa($ref,'HASH'));
-       }
-       else {
-           $ref = \@array;
-       }
-       @$ref
-    };
-
-    unshift @{$catch}, undef, $sub;
-
-    $clauses;
-}
-
-sub otherwise (&;$) {
-    my $code = shift;
-    my $clauses = shift || {};
-
-    if(exists $clauses->{'otherwise'}) {
-       require Carp;
-       Carp::croak("Multiple otherwise clauses");
-    }
-
-    $clauses->{'otherwise'} = $code;
-
-    $clauses;
-}
-
-1;
-__END__
-
-=head1 NAME
-
-Error - Error/exception handling in an OO-ish way
-
-=head1 SYNOPSIS
-
-    use Error qw(:try);
-
-    throw Error::Simple( "A simple error");
-
-    sub xyz {
-        ...
-       record Error::Simple("A simple error")
-           and return;
-    }
-
-    unlink($file) or throw Error::Simple("$file: $!",$!);
-
-    try {
-       do_some_stuff();
-       die "error!" if $condition;
-       throw Error::Simple -text => "Oops!" if $other_condition;
-    }
-    catch Error::IO with {
-       my $E = shift;
-       print STDERR "File ", $E->{'-file'}, " had a problem\n";
-    }
-    except {
-       my $E = shift;
-       my $general_handler=sub {send_message $E->{-description}};
-       return {
-           UserException1 => $general_handler,
-           UserException2 => $general_handler
-       };
-    }
-    otherwise {
-       print STDERR "Well I don't know what to say\n";
-    }
-    finally {
-       close_the_garage_door_already(); # Should be reliable
-    }; # Don't forget the trailing ; or you might be surprised
-
-=head1 DESCRIPTION
-
-The C<Error> package provides two interfaces. Firstly C<Error> provides
-a procedural interface to exception handling. Secondly C<Error> is a
-base class for errors/exceptions that can either be thrown, for
-subsequent catch, or can simply be recorded.
-
-Errors in the class C<Error> should not be thrown directly, but the
-user should throw errors from a sub-class of C<Error>.
-
-=head1 PROCEDURAL INTERFACE
-
-C<Error> exports subroutines to perform exception handling. These will
-be exported if the C<:try> tag is used in the C<use> line.
-
-=over 4
-
-=item try BLOCK CLAUSES
-
-C<try> is the main subroutine called by the user. All other subroutines
-exported are clauses to the try subroutine.
-
-The BLOCK will be evaluated and, if no error is throw, try will return
-the result of the block.
-
-C<CLAUSES> are the subroutines below, which describe what to do in the
-event of an error being thrown within BLOCK.
-
-=item catch CLASS with BLOCK
-
-This clauses will cause all errors that satisfy C<$err-E<gt>isa(CLASS)>
-to be caught and handled by evaluating C<BLOCK>.
-
-C<BLOCK> will be passed two arguments. The first will be the error
-being thrown. The second is a reference to a scalar variable. If this
-variable is set by the catch block then, on return from the catch
-block, try will continue processing as if the catch block was never
-found.
-
-To propagate the error the catch block may call C<$err-E<gt>throw>
-
-If the scalar reference by the second argument is not set, and the
-error is not thrown. Then the current try block will return with the
-result from the catch block.
-
-=item except BLOCK
-
-When C<try> is looking for a handler, if an except clause is found
-C<BLOCK> is evaluated. The return value from this block should be a
-HASHREF or a list of key-value pairs, where the keys are class names
-and the values are CODE references for the handler of errors of that
-type.
-
-=item otherwise BLOCK
-
-Catch any error by executing the code in C<BLOCK>
-
-When evaluated C<BLOCK> will be passed one argument, which will be the
-error being processed.
-
-Only one otherwise block may be specified per try block
-
-=item finally BLOCK
-
-Execute the code in C<BLOCK> either after the code in the try block has
-successfully completed, or if the try block throws an error then
-C<BLOCK> will be executed after the handler has completed.
-
-If the handler throws an error then the error will be caught, the
-finally block will be executed and the error will be re-thrown.
-
-Only one finally block may be specified per try block
-
-=back
-
-=head1 CLASS INTERFACE
-
-=head2 CONSTRUCTORS
-
-The C<Error> object is implemented as a HASH. This HASH is initialized
-with the arguments that are passed to its constructor. The elements
-that are used by, or are retrievable by the C<Error> class are listed
-below, other classes may add to these.
-
-       -file
-       -line
-       -text
-       -value
-       -object
-
-If C<-file> or C<-line> are not specified in the constructor arguments
-then these will be initialized with the file name and line number where
-the constructor was called from.
-
-If the error is associated with an object then the object should be
-passed as the C<-object> argument. This will allow the C<Error> package
-to associate the error with the object.
-
-The C<Error> package remembers the last error created, and also the
-last error associated with a package. This could either be the last
-error created by a sub in that package, or the last error which passed
-an object blessed into that package as the C<-object> argument.
-
-=over 4
-
-=item throw ( [ ARGS ] )
-
-Create a new C<Error> object and throw an error, which will be caught
-by a surrounding C<try> block, if there is one. Otherwise it will cause
-the program to exit.
-
-C<throw> may also be called on an existing error to re-throw it.
-
-=item with ( [ ARGS ] )
-
-Create a new C<Error> object and returns it. This is defined for
-syntactic sugar, eg
-
-    die with Some::Error ( ... );
-
-=item record ( [ ARGS ] )
-
-Create a new C<Error> object and returns it. This is defined for
-syntactic sugar, eg
-
-    record Some::Error ( ... )
-       and return;
-
-=back
-
-=head2 STATIC METHODS
-
-=over 4
-
-=item prior ( [ PACKAGE ] )
-
-Return the last error created, or the last error associated with
-C<PACKAGE>
-
-=item flush ( [ PACKAGE ] )
-
-Flush the last error created, or the last error associated with
-C<PACKAGE>.It is necessary to clear the error stack before exiting the
-package or uncaught errors generated using C<record> will be reported.
-
-     $Error->flush;
-
-=cut
-
-=back
-
-=head2 OBJECT METHODS
-
-=over 4
-
-=item stacktrace
-
-If the variable C<$Error::Debug> was non-zero when the error was
-created, then C<stacktrace> returns a string created by calling
-C<Carp::longmess>. If the variable was zero the C<stacktrace> returns
-the text of the error appended with the filename and line number of
-where the error was created, providing the text does not end with a
-newline.
-
-=item object
-
-The object this error was associated with
-
-=item file
-
-The file where the constructor of this error was called from
-
-=item line
-
-The line where the constructor of this error was called from
-
-=item text
-
-The text of the error
-
-=back
-
-=head2 OVERLOAD METHODS
-
-=over 4
-
-=item stringify
-
-A method that converts the object into a string. This method may simply
-return the same as the C<text> method, or it may append more
-information. For example the file name and line number.
-
-By default this method returns the C<-text> argument that was passed to
-the constructor, or the string C<"Died"> if none was given.
-
-=item value
-
-A method that will return a value that can be associated with the
-error. For example if an error was created due to the failure of a
-system call, then this may return the numeric value of C<$!> at the
-time.
-
-By default this method returns the C<-value> argument that was passed
-to the constructor.
-
-=back
-
-=head1 PRE-DEFINED ERROR CLASSES
-
-=over 4
-
-=item Error::Simple
-
-This class can be used to hold simple error strings and values. Its
-constructor takes two arguments. The first is a text value, the second
-is a numeric value. These values are what will be returned by the
-overload methods.
-
-If the text value ends with C<at file line 1> as $@ strings do, then
-this information will be used to set the C<-file> and C<-line> arguments
-of the error object.
-
-This class is used internally if an eval'd block die's with an error
-that is a plain string. (Unless C<$Error::ObjectifyCallback> is modified)
-
-=back
-
-=head1 $Error::ObjectifyCallback
-
-This variable holds a reference to a subroutine that converts errors that
-are plain strings to objects. It is used by Error.pm to convert textual
-errors to objects, and can be overridden by the user.
-
-It accepts a single argument which is a hash reference to named parameters.
-Currently the only named parameter passed is C<'text'> which is the text
-of the error, but others may be available in the future.
-
-For example the following code will cause Error.pm to throw objects of the
-class MyError::Bar by default:
-
-    sub throw_MyError_Bar
-    {
-        my $args = shift;
-        my $err = MyError::Bar->new();
-        $err->{'MyBarText'} = $args->{'text'};
-        return $err;
-    }
-
-    {
-        local $Error::ObjectifyCallback = \&throw_MyError_Bar;
-
-        # Error handling here.
-    }
-
-=head1 KNOWN BUGS
-
-None, but that does not mean there are not any.
-
-=head1 AUTHORS
-
-Graham Barr <gbarr@pobox.com>
-
-The code that inspired me to write this was originally written by
-Peter Seibel <peter@weblogic.com> and adapted by Jesse Glick
-<jglick@sig.bsh.com>.
-
-=head1 MAINTAINER
-
-Shlomi Fish <shlomif@iglu.org.il>
-
-=head1 PAST MAINTAINERS
-
-Arun Kumar U <u_arunkumar@yahoo.com>
-
-=cut
diff --git a/perl/Git/FromCPAN/Mail/Address.pm b/perl/Git/FromCPAN/Mail/Address.pm
deleted file mode 100644 (file)
index 13b2ff7..0000000
+++ /dev/null
@@ -1,276 +0,0 @@
-# Copyrights 1995-2017 by [Mark Overmeer <perl@overmeer.net>].
-#  For other contributors see ChangeLog.
-# See the manual pages for details on the licensing terms.
-# Pod stripped from pm file by OODoc 2.02.
-package Mail::Address;
-use vars '$VERSION';
-$VERSION = '2.19';
-
-use strict;
-
-use Carp;
-
-# use locale;   removed in version 1.78, because it causes taint problems
-
-sub Version { our $VERSION }
-
-
-
-# given a comment, attempt to extract a person's name
-sub _extract_name
-{   # This function can be called as method as well
-    my $self = @_ && ref $_[0] ? shift : undef;
-
-    local $_ = shift
-        or return '';
-
-    # Using encodings, too hard. See Mail::Message::Field::Full.
-    return '' if m/\=\?.*?\?\=/;
-
-    # trim whitespace
-    s/^\s+//;
-    s/\s+$//;
-    s/\s+/ /;
-
-    # Disregard numeric names (e.g. 123456.1234@compuserve.com)
-    return "" if /^[\d ]+$/;
-
-    s/^\((.*)\)$/$1/; # remove outermost parenthesis
-    s/^"(.*)"$/$1/;   # remove outer quotation marks
-    s/\(.*?\)//g;     # remove minimal embedded comments
-    s/\\//g;          # remove all escapes
-    s/^"(.*)"$/$1/;   # remove internal quotation marks
-    s/^([^\s]+) ?, ?(.*)$/$2 $1/; # reverse "Last, First M." if applicable
-    s/,.*//;
-
-    # Change casing only when the name contains only upper or only
-    # lower cased characters.
-    unless( m/[A-Z]/ && m/[a-z]/ )
-    {   # Set the case of the name to first char upper rest lower
-        s/\b(\w+)/\L\u$1/igo;  # Upcase first letter on name
-        s/\bMc(\w)/Mc\u$1/igo; # Scottish names such as 'McLeod'
-        s/\bo'(\w)/O'\u$1/igo; # Irish names such as 'O'Malley, O'Reilly'
-        s/\b(x*(ix)?v*(iv)?i*)\b/\U$1/igo; # Roman numerals, eg 'Level III Support'
-    }
-
-    # some cleanup
-    s/\[[^\]]*\]//g;
-    s/(^[\s'"]+|[\s'"]+$)//g;
-    s/\s{2,}/ /g;
-
-    $_;
-}
-
-sub _tokenise
-{   local $_ = join ',', @_;
-    my (@words,$snippet,$field);
-
-    s/\A\s+//;
-    s/[\r\n]+/ /g;
-
-    while ($_ ne '')
-    {   $field = '';
-        if(s/^\s*\(/(/ )    # (...)
-        {   my $depth = 0;
-
-     PAREN: while(s/^(\(([^\(\)\\]|\\.)*)//)
-            {   $field .= $1;
-                $depth++;
-                while(s/^(([^\(\)\\]|\\.)*\)\s*)//)
-                {   $field .= $1;
-                    last PAREN unless --$depth;
-                   $field .= $1 if s/^(([^\(\)\\]|\\.)+)//;
-                }
-            }
-
-            carp "Unmatched () '$field' '$_'"
-                if $depth;
-
-            $field =~ s/\s+\Z//;
-            push @words, $field;
-
-            next;
-        }
-
-        if( s/^("(?:[^"\\]+|\\.)*")\s*//       # "..."
-         || s/^(\[(?:[^\]\\]+|\\.)*\])\s*//    # [...]
-         || s/^([^\s()<>\@,;:\\".[\]]+)\s*//
-         || s/^([()<>\@,;:\\".[\]])\s*//
-          )
-        {   push @words, $1;
-            next;
-        }
-
-        croak "Unrecognised line: $_";
-    }
-
-    push @words, ",";
-    \@words;
-}
-
-sub _find_next
-{   my ($idx, $tokens, $len) = @_;
-
-    while($idx < $len)
-    {   my $c = $tokens->[$idx];
-        return $c if $c eq ',' || $c eq ';' || $c eq '<';
-        $idx++;
-    }
-
-    "";
-}
-
-sub _complete
-{   my ($class, $phrase, $address, $comment) = @_;
-
-    @$phrase || @$comment || @$address
-       or return undef;
-
-    my $o = $class->new(join(" ",@$phrase), join("",@$address), join(" ",@$comment));
-    @$phrase = @$address = @$comment = ();
-    $o;
-}
-
-#------------
-
-sub new(@)
-{   my $class = shift;
-    bless [@_], $class;
-}
-
-
-sub parse(@)
-{   my $class = shift;
-    my @line  = grep {defined} @_;
-    my $line  = join '', @line;
-
-    my (@phrase, @comment, @address, @objs);
-    my ($depth, $idx) = (0, 0);
-
-    my $tokens  = _tokenise @line;
-    my $len     = @$tokens;
-    my $next    = _find_next $idx, $tokens, $len;
-
-    local $_;
-    for(my $idx = 0; $idx < $len; $idx++)
-    {   $_ = $tokens->[$idx];
-
-        if(substr($_,0,1) eq '(') { push @comment, $_ }
-        elsif($_ eq '<')    { $depth++ }
-        elsif($_ eq '>')    { $depth-- if $depth }
-        elsif($_ eq ',' || $_ eq ';')
-        {   warn "Unmatched '<>' in $line" if $depth;
-            my $o = $class->_complete(\@phrase, \@address, \@comment);
-            push @objs, $o if defined $o;
-            $depth = 0;
-            $next = _find_next $idx+1, $tokens, $len;
-        }
-        elsif($depth)       { push @address, $_ }
-        elsif($next eq '<') { push @phrase,  $_ }
-        elsif( /^[.\@:;]$/ || !@address || $address[-1] =~ /^[.\@:;]$/ )
-        {   push @address, $_ }
-        else
-        {   warn "Unmatched '<>' in $line" if $depth;
-            my $o = $class->_complete(\@phrase, \@address, \@comment);
-            push @objs, $o if defined $o;
-            $depth = 0;
-            push @address, $_;
-        }
-    }
-    @objs;
-}
-
-#------------
-
-sub phrase  { shift->set_or_get(0, @_) }
-sub address { shift->set_or_get(1, @_) }
-sub comment { shift->set_or_get(2, @_) }
-
-sub set_or_get($)
-{   my ($self, $i) = (shift, shift);
-    @_ or return $self->[$i];
-
-    my $val = $self->[$i];
-    $self->[$i] = shift if @_;
-    $val;
-}
-
-
-my $atext = '[\-\w !#$%&\'*+/=?^`{|}~]';
-sub format
-{   my @addrs;
-
-    foreach (@_)
-    {   my ($phrase, $email, $comment) = @$_;
-        my @addr;
-
-        if(defined $phrase && length $phrase)
-        {   push @addr
-              , $phrase =~ /^(?:\s*$atext\s*)+$/o ? $phrase
-              : $phrase =~ /(?<!\\)"/             ? $phrase
-              :                                    qq("$phrase");
-
-            push @addr, "<$email>"
-                if defined $email && length $email;
-        }
-        elsif(defined $email && length $email)
-        {   push @addr, $email;
-        }
-
-        if(defined $comment && $comment =~ /\S/)
-        {   $comment =~ s/^\s*\(?/(/;
-            $comment =~ s/\)?\s*$/)/;
-        }
-
-        push @addr, $comment
-            if defined $comment && length $comment;
-
-        push @addrs, join(" ", @addr)
-            if @addr;
-    }
-
-    join ", ", @addrs;
-}
-
-#------------
-
-sub name
-{   my $self   = shift;
-    my $phrase = $self->phrase;
-    my $addr   = $self->address;
-
-    $phrase    = $self->comment
-        unless defined $phrase && length $phrase;
-
-    my $name   = $self->_extract_name($phrase);
-
-    # first.last@domain address
-    if($name eq '' && $addr =~ /([^\%\.\@_]+([\._][^\%\.\@_]+)+)[\@\%]/)
-    {   ($name  = $1) =~ s/[\._]+/ /g;
-       $name   = _extract_name $name;
-    }
-
-    if($name eq '' && $addr =~ m#/g=#i)    # X400 style address
-    {   my ($f) = $addr =~ m#g=([^/]*)#i;
-       my ($l) = $addr =~ m#s=([^/]*)#i;
-       $name   = _extract_name "$f $l";
-    }
-
-    length $name ? $name : undef;
-}
-
-
-sub host
-{   my $addr = shift->address || '';
-    my $i    = rindex $addr, '@';
-    $i >= 0 ? substr($addr, $i+1) : undef;
-}
-
-
-sub user
-{   my $addr = shift->address || '';
-    my $i    = rindex $addr, '@';
-    $i >= 0 ? substr($addr,0,$i) : $addr;
-}
-
-1;
diff --git a/perl/Git/LoadCPAN.pm b/perl/Git/LoadCPAN.pm
new file mode 100644 (file)
index 0000000..e5585e7
--- /dev/null
@@ -0,0 +1,104 @@
+package Git::LoadCPAN;
+use 5.008;
+use strict;
+use warnings;
+
+=head1 NAME
+
+Git::LoadCPAN - Wrapper for loading modules from the CPAN (OS) or Git's own copy
+
+=head1 DESCRIPTION
+
+The Perl code in Git depends on some modules from the CPAN, but we
+don't want to make those a hard requirement for anyone building from
+source.
+
+Therefore the L<Git::LoadCPAN> namespace shipped with Git contains
+wrapper modules like C<Git::LoadCPAN::Module::Name> that will first
+attempt to load C<Module::Name> from the OS, and if that doesn't work
+will fall back on C<FromCPAN::Module::Name> shipped with Git itself.
+
+Usually distributors will not ship with Git's Git::FromCPAN tree at
+all via the C<NO_PERL_CPAN_FALLBACKS> option, preferring to use their
+own packaging of CPAN modules instead.
+
+This module is only intended to be used for code shipping in the
+C<git.git> repository. Use it for anything else at your peril!
+
+=cut
+
+# NO_PERL_CPAN_FALLBACKS_STR evades the sed search-replace from the
+# Makefile, and allows for detecting whether the module is loaded from
+# perl/Git as opposed to perl/build/Git, which is useful for one-off
+# testing without having Error.pm et al installed.
+use constant NO_PERL_CPAN_FALLBACKS_STR => '@@' . 'NO_PERL_CPAN_FALLBACKS' . '@@';
+use constant NO_PERL_CPAN_FALLBACKS => (
+       q[@@NO_PERL_CPAN_FALLBACKS@@] ne ''
+       and
+       q[@@NO_PERL_CPAN_FALLBACKS@@] ne NO_PERL_CPAN_FALLBACKS_STR
+);
+
+sub import {
+       shift;
+       my $caller = caller;
+       my %args = @_;
+       my $module = exists $args{module} ? delete $args{module} : die "BUG: Expected 'module' parameter!";
+       my $import = exists $args{import} ? delete $args{import} : die "BUG: Expected 'import' parameter!";
+       die "BUG: Too many arguments!" if keys %args;
+
+       # Foo::Bar to Foo/Bar.pm
+       my $package_pm = $module;
+       $package_pm =~ s[::][/]g;
+       $package_pm .= '.pm';
+
+       eval {
+               require $package_pm;
+               1;
+       } or do {
+               my $error = $@ || "Zombie Error";
+
+               if (NO_PERL_CPAN_FALLBACKS) {
+                       chomp(my $error = sprintf <<'THEY_PROMISED', $module);
+BUG: The '%s' module is not here, but NO_PERL_CPAN_FALLBACKS was set!
+
+Git needs this Perl module from the CPAN, and will by default ship
+with a copy of it. This Git was built with NO_PERL_CPAN_FALLBACKS,
+meaning that whoever built it promised to provide this module.
+
+You're seeing this error because they broke that promise, and we can't
+load our fallback version, since we were asked not to install it.
+
+If you're seeing this error and didn't package Git yourself the
+package you're using is broken, or your system is broken. This error
+won't appear if Git is built without NO_PERL_CPAN_FALLBACKS (instead
+we'll use our fallback version of the module).
+THEY_PROMISED
+                       die $error;
+               }
+
+               my $Git_LoadCPAN_pm_path = $INC{"Git/LoadCPAN.pm"} || die "BUG: Should have our own path from %INC!";
+
+               require File::Basename;
+               my $Git_LoadCPAN_pm_root = File::Basename::dirname($Git_LoadCPAN_pm_path) || die "BUG: Can't figure out lib/Git dirname from '$Git_LoadCPAN_pm_path'!";
+
+               require File::Spec;
+               my $Git_pm_FromCPAN_root = File::Spec->catdir($Git_LoadCPAN_pm_root, '..', 'FromCPAN');
+               die "BUG: '$Git_pm_FromCPAN_root' should be a directory!" unless -d $Git_pm_FromCPAN_root;
+
+               local @INC = ($Git_pm_FromCPAN_root, @INC);
+               require $package_pm;
+       };
+
+       if ($import) {
+               no strict 'refs';
+               *{"${caller}::import"} = sub {
+                       shift;
+                       use strict 'refs';
+                       unshift @_, $module;
+                       goto &{"${module}::import"};
+               };
+               use strict 'refs';
+       }
+}
+
+1;
diff --git a/perl/Git/LoadCPAN/Error.pm b/perl/Git/LoadCPAN/Error.pm
new file mode 100644 (file)
index 0000000..c6d2c45
--- /dev/null
@@ -0,0 +1,10 @@
+package Git::LoadCPAN::Error;
+use 5.008;
+use strict;
+use warnings;
+use Git::LoadCPAN (
+       module => 'Error',
+       import => 1,
+);
+
+1;
diff --git a/perl/Git/LoadCPAN/Mail/Address.pm b/perl/Git/LoadCPAN/Mail/Address.pm
new file mode 100644 (file)
index 0000000..f70a4f0
--- /dev/null
@@ -0,0 +1,10 @@
+package Git::LoadCPAN::Mail::Address;
+use 5.008;
+use strict;
+use warnings;
+use Git::LoadCPAN (
+       module => 'Mail::Address',
+       import => 0,
+);
+
+1;
diff --git a/perl/Git/Mail/Address.pm b/perl/Git/Mail/Address.pm
deleted file mode 100755 (executable)
index 2ce3e84..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-package Git::Mail::Address;
-use 5.008;
-use strict;
-use warnings;
-
-=head1 NAME
-
-Git::Mail::Address - Wrapper for the L<Mail::Address> module, in case it's not installed
-
-=head1 DESCRIPTION
-
-This module is only intended to be used for code shipping in the
-C<git.git> repository. Use it for anything else at your peril!
-
-=cut
-
-eval {
-    require Mail::Address;
-    1;
-} or do {
-    require Git::FromCPAN::Mail::Address;
-};
-
-1;
index 977921d90c65ea94b40cb7f8332246bf77c0f215..d05eb725b533d3b70daca910df60d27de9ce947c 100644 (file)
@@ -2538,6 +2538,12 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
        int new_shared_index, ret;
        struct split_index *si = istate->split_index;
 
+       if ((flags & SKIP_IF_UNCHANGED) && !istate->cache_changed) {
+               if (flags & COMMIT_LOCK)
+                       rollback_lock_file(lock);
+               return 0;
+       }
+
        if (istate->fsmonitor_last_update)
                fill_fsmonitor_bitmap(istate);
 
diff --git a/refs.c b/refs.c
index 20ba82b4343ff2ef72cea32deec8a8d7fbd6def7..e8b84c189ff636e06970bbef13eaa92bb872bc94 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)
 static int is_per_worktree_ref(const char *refname)
 {
        return !strcmp(refname, "HEAD") ||
-               starts_with(refname, "refs/bisect/");
+               starts_with(refname, "refs/bisect/") ||
+               starts_with(refname, "refs/rewritten/");
 }
 
 static int is_pseudoref_syntax(const char *refname)
index 4ffbe9bc94edc18314cb49c945038e2f20a40922..62f52f47fcaed32c4f14a9f76eb1e66e13d4046a 100644 (file)
@@ -4,64 +4,68 @@
 #include "submodule-config.h"
 
 /* The main repository */
-static struct repository the_repo = {
-       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &the_index, &hash_algos[GIT_HASH_SHA1], 0, 0
-};
-struct repository *the_repository = &the_repo;
+static struct repository the_repo;
+struct repository *the_repository;
 
-static char *git_path_from_env(const char *envvar, const char *git_dir,
-                              const char *path, int fromenv)
+void initialize_the_repository(void)
 {
-       if (fromenv) {
-               const char *value = getenv(envvar);
-               if (value)
-                       return xstrdup(value);
-       }
+       the_repository = &the_repo;
 
-       return xstrfmt("%s/%s", git_dir, path);
+       the_repo.index = &the_index;
+       repo_set_hash_algo(&the_repo, GIT_HASH_SHA1);
 }
 
-static int find_common_dir(struct strbuf *sb, const char *gitdir, int fromenv)
+static void expand_base_dir(char **out, const char *in,
+                           const char *base_dir, const char *def_in)
 {
-       if (fromenv) {
-               const char *value = getenv(GIT_COMMON_DIR_ENVIRONMENT);
-               if (value) {
-                       strbuf_addstr(sb, value);
-                       return 1;
-               }
-       }
-
-       return get_common_dir_noenv(sb, gitdir);
+       free(*out);
+       if (in)
+               *out = xstrdup(in);
+       else
+               *out = xstrfmt("%s/%s", base_dir, def_in);
 }
 
-static void repo_setup_env(struct repository *repo)
+static void repo_set_commondir(struct repository *repo,
+                              const char *commondir)
 {
        struct strbuf sb = STRBUF_INIT;
 
-       repo->different_commondir = find_common_dir(&sb, repo->gitdir,
-                                                   !repo->ignore_env);
        free(repo->commondir);
+
+       if (commondir) {
+               repo->different_commondir = 1;
+               repo->commondir = xstrdup(commondir);
+               return;
+       }
+
+       repo->different_commondir = get_common_dir_noenv(&sb, repo->gitdir);
        repo->commondir = strbuf_detach(&sb, NULL);
-       free(repo->objectdir);
-       repo->objectdir = git_path_from_env(DB_ENVIRONMENT, repo->commondir,
-                                           "objects", !repo->ignore_env);
-       free(repo->graft_file);
-       repo->graft_file = git_path_from_env(GRAFT_ENVIRONMENT, repo->commondir,
-                                            "info/grafts", !repo->ignore_env);
-       free(repo->index_file);
-       repo->index_file = git_path_from_env(INDEX_ENVIRONMENT, repo->gitdir,
-                                            "index", !repo->ignore_env);
 }
 
-void repo_set_gitdir(struct repository *repo, const char *path)
+void repo_set_gitdir(struct repository *repo,
+                    const char *root,
+                    const struct set_gitdir_args *o)
 {
-       const char *gitfile = read_gitfile(path);
+       const char *gitfile = read_gitfile(root);
+       /*
+        * repo->gitdir is saved because the caller could pass "root"
+        * that also points to repo->gitdir. We want to keep it alive
+        * until after xstrdup(root). Then we can free it.
+        */
        char *old_gitdir = repo->gitdir;
 
-       repo->gitdir = xstrdup(gitfile ? gitfile : path);
-       repo_setup_env(repo);
-
+       repo->gitdir = xstrdup(gitfile ? gitfile : root);
        free(old_gitdir);
+
+       repo_set_commondir(repo, o->commondir);
+       expand_base_dir(&repo->objectdir, o->object_dir,
+                       repo->commondir, "objects");
+       free(repo->alternate_db);
+       repo->alternate_db = xstrdup_or_null(o->alternate_db);
+       expand_base_dir(&repo->graft_file, o->graft_file,
+                       repo->commondir, "info/grafts");
+       expand_base_dir(&repo->index_file, o->index_file,
+                       repo->gitdir, "index");
 }
 
 void repo_set_hash_algo(struct repository *repo, int hash_algo)
@@ -79,6 +83,7 @@ static int repo_init_gitdir(struct repository *repo, const char *gitdir)
        int error = 0;
        char *abspath = NULL;
        const char *resolved_gitdir;
+       struct set_gitdir_args args = { NULL };
 
        abspath = real_pathdup(gitdir, 0);
        if (!abspath) {
@@ -93,7 +98,7 @@ static int repo_init_gitdir(struct repository *repo, const char *gitdir)
                goto out;
        }
 
-       repo_set_gitdir(repo, resolved_gitdir);
+       repo_set_gitdir(repo, resolved_gitdir, &args);
 
 out:
        free(abspath);
@@ -128,13 +133,13 @@ static int read_and_verify_repository_format(struct repository_format *format,
  * Initialize 'repo' based on the provided 'gitdir'.
  * Return 0 upon success and a non-zero value upon failure.
  */
-int repo_init(struct repository *repo, const char *gitdir, const char *worktree)
+static int repo_init(struct repository *repo,
+                    const char *gitdir,
+                    const char *worktree)
 {
        struct repository_format format;
        memset(repo, 0, sizeof(*repo));
 
-       repo->ignore_env = 1;
-
        if (repo_init_gitdir(repo, gitdir))
                goto error;
 
@@ -210,6 +215,7 @@ void repo_clear(struct repository *repo)
        FREE_AND_NULL(repo->gitdir);
        FREE_AND_NULL(repo->commondir);
        FREE_AND_NULL(repo->objectdir);
+       FREE_AND_NULL(repo->alternate_db);
        FREE_AND_NULL(repo->graft_file);
        FREE_AND_NULL(repo->index_file);
        FREE_AND_NULL(repo->worktree);
index 0329e40c7f5e72dad3ba46328a8e3d6c29ed8e58..e7127baffba019812e458477a34a4d9a80a67cdc 100644 (file)
@@ -26,6 +26,9 @@ struct repository {
         */
        char *objectdir;
 
+       /* Path to extra alternate object database if not NULL */
+       char *alternate_db;
+
        /*
         * Path to the repository's graft file.
         * Cannot be NULL after initialization.
@@ -72,15 +75,6 @@ struct repository {
        const struct git_hash_algo *hash_algo;
 
        /* Configurations */
-       /*
-        * Bit used during initialization to indicate if repository state (like
-        * the location of the 'objectdir') should be read from the
-        * environment.  By default this bit will be set at the begining of
-        * 'repo_init()' so that all repositories will ignore the environment.
-        * The exception to this is 'the_repository', which doesn't go through
-        * the normal 'repo_init()' process.
-        */
-       unsigned ignore_env:1;
 
        /* Indicate if a repository has a different 'commondir' from 'gitdir' */
        unsigned different_commondir:1;
@@ -88,10 +82,20 @@ struct repository {
 
 extern struct repository *the_repository;
 
-extern void repo_set_gitdir(struct repository *repo, const char *path);
+struct set_gitdir_args {
+       const char *commondir;
+       const char *object_dir;
+       const char *graft_file;
+       const char *index_file;
+       const char *alternate_db;
+};
+
+extern void repo_set_gitdir(struct repository *repo,
+                           const char *root,
+                           const struct set_gitdir_args *optional);
 extern void repo_set_worktree(struct repository *repo, const char *path);
 extern void repo_set_hash_algo(struct repository *repo, int algo);
-extern int repo_init(struct repository *repo, const char *gitdir, const char *worktree);
+extern void initialize_the_repository(void);
 extern int repo_submodule_init(struct repository *submodule,
                               struct repository *superproject,
                               const char *path);
index 79203c6c1eae0db5515030b138e9e40e219af5f5..ea24d4c2f47ab6495d97f38245dd420a1ad38391 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -719,11 +719,9 @@ static void update_paths(struct string_list *update)
                        item->string);
        }
 
-       if (active_cache_changed) {
-               if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
-                       die("Unable to write new index file");
-       } else
-               rollback_lock_file(&index_lock);
+       if (write_locked_index(&the_index, &index_lock,
+                              COMMIT_LOCK | SKIP_IF_UNCHANGED))
+               die("Unable to write new index file");
 }
 
 static void remove_variant(struct rerere_id *id)
index 091bd6bda5c55c27e2d0688a5e12988e22e6e431..d34ffd392c07e38882dcf9145421bb0766e753cc 100644 (file)
 #include "hashmap.h"
 #include "notes-utils.h"
 #include "sigchain.h"
+#include "unpack-trees.h"
+#include "worktree.h"
+#include "oidmap.h"
+#include "oidset.h"
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
@@ -120,6 +124,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")
 static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list")
 static GIT_PATH_FUNC(rebase_path_rewritten_pending,
        "rebase-merge/rewritten-pending")
+
+/*
+ * The path of the file listing refs that need to be deleted after the rebase
+ * finishes. This is used by the `label` command to record the need for cleanup.
+ */
+static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
+
 /*
  * The following files are written by git-rebase just after parsing the
  * command-line (and are only consumed, not modified, by the sequencer).
@@ -244,18 +255,33 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)
 
 int sequencer_remove_state(struct replay_opts *opts)
 {
-       struct strbuf dir = STRBUF_INIT;
+       struct strbuf buf = STRBUF_INIT;
        int i;
 
+       if (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {
+               char *p = buf.buf;
+               while (*p) {
+                       char *eol = strchr(p, '\n');
+                       if (eol)
+                               *eol = '\0';
+                       if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0)
+                               warning(_("could not delete '%s'"), p);
+                       if (!eol)
+                               break;
+                       p = eol + 1;
+               }
+       }
+
        free(opts->gpg_sign);
        free(opts->strategy);
        for (i = 0; i < opts->xopts_nr; i++)
                free(opts->xopts[i]);
        free(opts->xopts);
 
-       strbuf_addstr(&dir, get_dir(opts));
-       remove_dir_recursively(&dir, 0);
-       strbuf_release(&dir);
+       strbuf_reset(&buf);
+       strbuf_addstr(&buf, get_dir(opts));
+       remove_dir_recursively(&buf, 0);
+       strbuf_release(&buf);
 
        return 0;
 }
@@ -345,12 +371,14 @@ static int write_message(const void *buf, size_t len, const char *filename,
        if (msg_fd < 0)
                return error_errno(_("could not lock '%s'"), filename);
        if (write_in_full(msg_fd, buf, len) < 0) {
+               error_errno(_("could not write to '%s'"), filename);
                rollback_lock_file(&msg_file);
-               return error_errno(_("could not write to '%s'"), filename);
+               return -1;
        }
        if (append_eol && write(msg_fd, "\n", 1) < 0) {
+               error_errno(_("could not write eol to '%s'"), filename);
                rollback_lock_file(&msg_file);
-               return error_errno(_("could not write eol to '%s'"), filename);
+               return -1;
        }
        if (commit_lock_file(&msg_file) < 0)
                return error(_("failed to finalize '%s'"), filename);
@@ -517,15 +545,14 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
                return clean;
        }
 
-       if (active_cache_changed &&
-           write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
+       if (write_locked_index(&the_index, &index_lock,
+                              COMMIT_LOCK | SKIP_IF_UNCHANGED))
                /*
                 * TRANSLATORS: %s will be "revert", "cherry-pick" or
                 * "rebase -i".
                 */
                return error(_("%s: Unable to write new index file"),
                        _(action_name(opts)));
-       rollback_lock_file(&index_lock);
 
        if (!clean)
                append_conflicts_hint(msgbuf);
@@ -1278,6 +1305,10 @@ enum todo_command {
        TODO_SQUASH,
        /* commands that do something else than handling a single commit */
        TODO_EXEC,
+       TODO_LABEL,
+       TODO_RESET,
+       TODO_MERGE,
+       TODO_MERGE_AND_EDIT,
        /* commands that do nothing but are counted for reporting progress */
        TODO_NOOP,
        TODO_DROP,
@@ -1296,6 +1327,10 @@ static struct {
        { 'f', "fixup" },
        { 's', "squash" },
        { 'x', "exec" },
+       { 'l', "label" },
+       { 't', "reset" },
+       { 'm', "merge" },
+       { 0, "merge" }, /* MERGE_AND_EDIT */
        { 0,   "noop" },
        { 'd', "drop" },
        { 0,   NULL }
@@ -1713,13 +1748,13 @@ static int read_and_refresh_cache(struct replay_opts *opts)
                        _(action_name(opts)));
        }
        refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
-       if (the_index.cache_changed && index_fd >= 0) {
-               if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) {
+       if (index_fd >= 0) {
+               if (write_locked_index(&the_index, &index_lock,
+                                      COMMIT_LOCK | SKIP_IF_UNCHANGED)) {
                        return error(_("git %s: failed to refresh the index"),
                                _(action_name(opts)));
                }
        }
-       rollback_lock_file(&index_lock);
        return 0;
 }
 
@@ -1801,13 +1836,29 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
                return error(_("missing arguments for %s"),
                             command_to_string(item->command));
 
-       if (item->command == TODO_EXEC) {
+       if (item->command == TODO_EXEC || item->command == TODO_LABEL ||
+           item->command == TODO_RESET) {
                item->commit = NULL;
                item->arg = bol;
                item->arg_len = (int)(eol - bol);
                return 0;
        }
 
+       if (item->command == TODO_MERGE) {
+               if (skip_prefix(bol, "-C", &bol))
+                       bol += strspn(bol, " \t");
+               else if (skip_prefix(bol, "-c", &bol)) {
+                       bol += strspn(bol, " \t");
+                       item->command = TODO_MERGE_AND_EDIT;
+               } else {
+                       item->command = TODO_MERGE_AND_EDIT;
+                       item->commit = NULL;
+                       item->arg = bol;
+                       item->arg_len = (int)(eol - bol);
+                       return 0;
+               }
+       }
+
        end_of_object_name = (char *) bol + strcspn(bol, " \t\n");
        saved = *end_of_object_name;
        *end_of_object_name = '\0';
@@ -2120,9 +2171,9 @@ static int save_head(const char *head)
        written = write_in_full(fd, buf.buf, buf.len);
        strbuf_release(&buf);
        if (written < 0) {
+               error_errno(_("could not write to '%s'"), git_path_head_file());
                rollback_lock_file(&head_lock);
-               return error_errno(_("could not write to '%s'"),
-                                  git_path_head_file());
+               return -1;
        }
        if (commit_lock_file(&head_lock) < 0)
                return error(_("failed to finalize '%s'"), git_path_head_file());
@@ -2449,6 +2500,304 @@ static int do_exec(const char *command_line)
        return status;
 }
 
+static int safe_append(const char *filename, const char *fmt, ...)
+{
+       va_list ap;
+       struct lock_file lock = LOCK_INIT;
+       int fd = hold_lock_file_for_update(&lock, filename,
+                                          LOCK_REPORT_ON_ERROR);
+       struct strbuf buf = STRBUF_INIT;
+
+       if (fd < 0)
+               return -1;
+
+       if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT)
+               return error_errno(_("could not read '%s'"), filename);
+       strbuf_complete(&buf, '\n');
+       va_start(ap, fmt);
+       strbuf_vaddf(&buf, fmt, ap);
+       va_end(ap);
+
+       if (write_in_full(fd, buf.buf, buf.len) < 0) {
+               error_errno(_("could not write to '%s'"), filename);
+               strbuf_release(&buf);
+               rollback_lock_file(&lock);
+               return -1;
+       }
+       if (commit_lock_file(&lock) < 0) {
+               strbuf_release(&buf);
+               rollback_lock_file(&lock);
+               return error(_("failed to finalize '%s'"), filename);
+       }
+
+       strbuf_release(&buf);
+       return 0;
+}
+
+static int do_label(const char *name, int len)
+{
+       struct ref_store *refs = get_main_ref_store();
+       struct ref_transaction *transaction;
+       struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;
+       struct strbuf msg = STRBUF_INIT;
+       int ret = 0;
+       struct object_id head_oid;
+
+       if (len == 1 && *name == '#')
+               return error("Illegal label name: '%.*s'", len, name);
+
+       strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
+       strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name);
+
+       transaction = ref_store_transaction_begin(refs, &err);
+       if (!transaction) {
+               error("%s", err.buf);
+               ret = -1;
+       } else if (get_oid("HEAD", &head_oid)) {
+               error(_("could not read HEAD"));
+               ret = -1;
+       } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
+                                         NULL, 0, msg.buf, &err) < 0 ||
+                  ref_transaction_commit(transaction, &err)) {
+               error("%s", err.buf);
+               ret = -1;
+       }
+       ref_transaction_free(transaction);
+       strbuf_release(&err);
+       strbuf_release(&msg);
+
+       if (!ret)
+               ret = safe_append(rebase_path_refs_to_delete(),
+                                 "%s\n", ref_name.buf);
+       strbuf_release(&ref_name);
+
+       return ret;
+}
+
+static int do_reset(const char *name, int len, struct replay_opts *opts)
+{
+       struct strbuf ref_name = STRBUF_INIT;
+       struct object_id oid;
+       struct lock_file lock = LOCK_INIT;
+       struct tree_desc desc;
+       struct tree *tree;
+       struct unpack_trees_options unpack_tree_opts;
+       int ret = 0, i;
+
+       if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)
+               return -1;
+
+       /* Determine the length of the label */
+       for (i = 0; i < len; i++)
+               if (isspace(name[i]))
+                       len = i;
+
+       strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
+       if (get_oid(ref_name.buf, &oid) &&
+           get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
+               error(_("could not read '%s'"), ref_name.buf);
+               rollback_lock_file(&lock);
+               strbuf_release(&ref_name);
+               return -1;
+       }
+
+       memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
+       unpack_tree_opts.head_idx = 1;
+       unpack_tree_opts.src_index = &the_index;
+       unpack_tree_opts.dst_index = &the_index;
+       unpack_tree_opts.fn = oneway_merge;
+       unpack_tree_opts.merge = 1;
+       unpack_tree_opts.update = 1;
+       unpack_tree_opts.reset = 1;
+
+       if (read_cache_unmerged()) {
+               rollback_lock_file(&lock);
+               strbuf_release(&ref_name);
+               return error_resolve_conflict(_(action_name(opts)));
+       }
+
+       if (!fill_tree_descriptor(&desc, &oid)) {
+               error(_("failed to find tree of %s"), oid_to_hex(&oid));
+               rollback_lock_file(&lock);
+               free((void *)desc.buffer);
+               strbuf_release(&ref_name);
+               return -1;
+       }
+
+       if (unpack_trees(1, &desc, &unpack_tree_opts)) {
+               rollback_lock_file(&lock);
+               free((void *)desc.buffer);
+               strbuf_release(&ref_name);
+               return -1;
+       }
+
+       tree = parse_tree_indirect(&oid);
+       prime_cache_tree(&the_index, tree);
+
+       if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)
+               ret = error(_("could not write index"));
+       free((void *)desc.buffer);
+
+       if (!ret) {
+               struct strbuf msg = STRBUF_INIT;
+
+               strbuf_addf(&msg, "(rebase -i) reset '%.*s'", len, name);
+               ret = update_ref(msg.buf, "HEAD", &oid, NULL, 0,
+                                UPDATE_REFS_MSG_ON_ERR);
+               strbuf_release(&msg);
+       }
+
+       strbuf_release(&ref_name);
+       return ret;
+}
+
+static int do_merge(struct commit *commit, const char *arg, int arg_len,
+                   int run_commit_flags, struct replay_opts *opts)
+{
+       int merge_arg_len;
+       struct strbuf ref_name = STRBUF_INIT;
+       struct commit *head_commit, *merge_commit, *i;
+       struct commit_list *common, *j, *reversed = NULL;
+       struct merge_options o;
+       int can_fast_forward, ret;
+       static struct lock_file lock;
+
+       for (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)
+               if (isspace(arg[merge_arg_len]))
+                       break;
+
+       if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)
+               return -1;
+
+       head_commit = lookup_commit_reference_by_name("HEAD");
+       if (!head_commit) {
+               rollback_lock_file(&lock);
+               return error(_("cannot merge without a current revision"));
+       }
+
+       if (commit) {
+               const char *message = get_commit_buffer(commit, NULL);
+               const char *body;
+               int len;
+
+               if (!message) {
+                       rollback_lock_file(&lock);
+                       return error(_("could not get commit message of '%s'"),
+                                    oid_to_hex(&commit->object.oid));
+               }
+               write_author_script(message);
+               find_commit_subject(message, &body);
+               len = strlen(body);
+               if (write_message(body, len, git_path_merge_msg(), 0) < 0) {
+                       error_errno(_("could not write '%s'"),
+                                   git_path_merge_msg());
+                       unuse_commit_buffer(commit, message);
+                       rollback_lock_file(&lock);
+                       return -1;
+               }
+               unuse_commit_buffer(commit, message);
+       } else {
+               const char *p = arg + merge_arg_len;
+               struct strbuf buf = STRBUF_INIT;
+               int len;
+
+               strbuf_addf(&buf, "author %s", git_author_info(0));
+               write_author_script(buf.buf);
+               strbuf_reset(&buf);
+
+               p += strspn(p, " \t");
+               if (*p == '#' && isspace(p[1]))
+                       p += 1 + strspn(p + 1, " \t");
+               if (*p)
+                       len = strlen(p);
+               else {
+                       strbuf_addf(&buf, "Merge branch '%.*s'",
+                                   merge_arg_len, arg);
+                       p = buf.buf;
+                       len = buf.len;
+               }
+
+               if (write_message(p, len, git_path_merge_msg(), 0) < 0) {
+                       error_errno(_("could not write '%s'"),
+                                   git_path_merge_msg());
+                       strbuf_release(&buf);
+                       rollback_lock_file(&lock);
+                       return -1;
+               }
+               strbuf_release(&buf);
+       }
+
+       /*
+        * If HEAD is not identical to the parent of the original merge commit,
+        * we cannot fast-forward.
+        */
+       can_fast_forward = opts->allow_ff && commit && commit->parents &&
+               !oidcmp(&commit->parents->item->object.oid,
+                       &head_commit->object.oid);
+
+       strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg);
+       merge_commit = lookup_commit_reference_by_name(ref_name.buf);
+       if (!merge_commit) {
+               /* fall back to non-rewritten ref or commit */
+               strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0);
+               merge_commit = lookup_commit_reference_by_name(ref_name.buf);
+       }
+       if (!merge_commit) {
+               error(_("could not resolve '%s'"), ref_name.buf);
+               strbuf_release(&ref_name);
+               rollback_lock_file(&lock);
+               return -1;
+       }
+
+       if (can_fast_forward && commit->parents->next &&
+           !commit->parents->next->next &&
+           !oidcmp(&commit->parents->next->item->object.oid,
+                   &merge_commit->object.oid)) {
+               strbuf_release(&ref_name);
+               rollback_lock_file(&lock);
+               return fast_forward_to(&commit->object.oid,
+                                      &head_commit->object.oid, 0, opts);
+       }
+
+       write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
+                     git_path_merge_head(), 0);
+       write_message("no-ff", 5, git_path_merge_mode(), 0);
+
+       common = get_merge_bases(head_commit, merge_commit);
+       for (j = common; j; j = j->next)
+               commit_list_insert(j->item, &reversed);
+       free_commit_list(common);
+
+       read_cache();
+       init_merge_options(&o);
+       o.branch1 = "HEAD";
+       o.branch2 = ref_name.buf;
+       o.buffer_output = 2;
+
+       ret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);
+       if (ret <= 0)
+               fputs(o.obuf.buf, stdout);
+       strbuf_release(&o.obuf);
+       if (ret < 0) {
+               strbuf_release(&ref_name);
+               rollback_lock_file(&lock);
+               return error(_("conflicts while merging '%.*s'"),
+                            merge_arg_len, arg);
+       }
+
+       if (active_cache_changed &&
+           write_locked_index(&the_index, &lock, COMMIT_LOCK)) {
+               strbuf_release(&ref_name);
+               return error(_("merge: Unable to write new index file"));
+       }
+       rollback_lock_file(&lock);
+
+       ret = run_git_commit(git_path_merge_msg(), opts, run_commit_flags);
+       strbuf_release(&ref_name);
+
+       return ret;
+}
+
 static int is_final_fixup(struct todo_list *todo_list)
 {
        int i = todo_list->current;
@@ -2633,6 +2982,18 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                                /* `current` will be incremented below */
                                todo_list->current = -1;
                        }
+               } else if (item->command == TODO_LABEL)
+                       res = do_label(item->arg, item->arg_len);
+               else if (item->command == TODO_RESET)
+                       res = do_reset(item->arg, item->arg_len, opts);
+               else if (item->command == TODO_MERGE ||
+                        item->command == TODO_MERGE_AND_EDIT) {
+                       res = do_merge(item->commit, item->arg, item->arg_len,
+                                      item->command == TODO_MERGE_AND_EDIT ?
+                                      EDIT_MSG | VERIFY_MSG : 0, opts);
+                       if (item->commit)
+                               record_in_rewritten(&item->commit->object.oid,
+                                                   peek_command(todo_list, 1));
                } else if (!is_noop(item->command))
                        return error(_("unknown command %d"), item->command);
 
@@ -2987,6 +3348,345 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
        strbuf_release(&sob);
 }
 
+struct labels_entry {
+       struct hashmap_entry entry;
+       char label[FLEX_ARRAY];
+};
+
+static int labels_cmp(const void *fndata, const struct labels_entry *a,
+                     const struct labels_entry *b, const void *key)
+{
+       return key ? strcmp(a->label, key) : strcmp(a->label, b->label);
+}
+
+struct string_entry {
+       struct oidmap_entry entry;
+       char string[FLEX_ARRAY];
+};
+
+struct label_state {
+       struct oidmap commit2label;
+       struct hashmap labels;
+       struct strbuf buf;
+};
+
+static const char *label_oid(struct object_id *oid, const char *label,
+                            struct label_state *state)
+{
+       struct labels_entry *labels_entry;
+       struct string_entry *string_entry;
+       struct object_id dummy;
+       size_t len;
+       int i;
+
+       string_entry = oidmap_get(&state->commit2label, oid);
+       if (string_entry)
+               return string_entry->string;
+
+       /*
+        * For "uninteresting" commits, i.e. commits that are not to be
+        * rebased, and which can therefore not be labeled, we use a unique
+        * abbreviation of the commit name. This is slightly more complicated
+        * than calling find_unique_abbrev() because we also need to make
+        * sure that the abbreviation does not conflict with any other
+        * label.
+        *
+        * We disallow "interesting" commits to be labeled by a string that
+        * is a valid full-length hash, to ensure that we always can find an
+        * abbreviation for any uninteresting commit's names that does not
+        * clash with any other label.
+        */
+       if (!label) {
+               char *p;
+
+               strbuf_reset(&state->buf);
+               strbuf_grow(&state->buf, GIT_SHA1_HEXSZ);
+               label = p = state->buf.buf;
+
+               find_unique_abbrev_r(p, oid->hash, default_abbrev);
+
+               /*
+                * We may need to extend the abbreviated hash so that there is
+                * no conflicting label.
+                */
+               if (hashmap_get_from_hash(&state->labels, strihash(p), p)) {
+                       size_t i = strlen(p) + 1;
+
+                       oid_to_hex_r(p, oid);
+                       for (; i < GIT_SHA1_HEXSZ; i++) {
+                               char save = p[i];
+                               p[i] = '\0';
+                               if (!hashmap_get_from_hash(&state->labels,
+                                                          strihash(p), p))
+                                       break;
+                               p[i] = save;
+                       }
+               }
+       } else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&
+                   !get_oid_hex(label, &dummy)) ||
+                  (len == 1 && *label == '#') ||
+                  hashmap_get_from_hash(&state->labels,
+                                        strihash(label), label)) {
+               /*
+                * If the label already exists, or if the label is a valid full
+                * OID, or the label is a '#' (which we use as a separator
+                * between merge heads and oneline), we append a dash and a
+                * number to make it unique.
+                */
+               struct strbuf *buf = &state->buf;
+
+               strbuf_reset(buf);
+               strbuf_add(buf, label, len);
+
+               for (i = 2; ; i++) {
+                       strbuf_setlen(buf, len);
+                       strbuf_addf(buf, "-%d", i);
+                       if (!hashmap_get_from_hash(&state->labels,
+                                                  strihash(buf->buf),
+                                                  buf->buf))
+                               break;
+               }
+
+               label = buf->buf;
+       }
+
+       FLEX_ALLOC_STR(labels_entry, label, label);
+       hashmap_entry_init(labels_entry, strihash(label));
+       hashmap_add(&state->labels, labels_entry);
+
+       FLEX_ALLOC_STR(string_entry, string, label);
+       oidcpy(&string_entry->entry.oid, oid);
+       oidmap_put(&state->commit2label, string_entry);
+
+       return string_entry->string;
+}
+
+static int make_script_with_merges(struct pretty_print_context *pp,
+                                  struct rev_info *revs, FILE *out,
+                                  unsigned flags)
+{
+       int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
+       int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
+       struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
+       struct strbuf label = STRBUF_INIT;
+       struct commit_list *commits = NULL, **tail = &commits, *iter;
+       struct commit_list *tips = NULL, **tips_tail = &tips;
+       struct commit *commit;
+       struct oidmap commit2todo = OIDMAP_INIT;
+       struct string_entry *entry;
+       struct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,
+               shown = OIDSET_INIT;
+       struct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };
+
+       int abbr = flags & TODO_LIST_ABBREVIATE_CMDS;
+       const char *cmd_pick = abbr ? "p" : "pick",
+               *cmd_label = abbr ? "l" : "label",
+               *cmd_reset = abbr ? "t" : "reset",
+               *cmd_merge = abbr ? "m" : "merge";
+
+       oidmap_init(&commit2todo, 0);
+       oidmap_init(&state.commit2label, 0);
+       hashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);
+       strbuf_init(&state.buf, 32);
+
+       if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {
+               struct object_id *oid = &revs->cmdline.rev[0].item->oid;
+               FLEX_ALLOC_STR(entry, string, "onto");
+               oidcpy(&entry->entry.oid, oid);
+               oidmap_put(&state.commit2label, entry);
+       }
+
+       /*
+        * First phase:
+        * - get onelines for all commits
+        * - gather all branch tips (i.e. 2nd or later parents of merges)
+        * - label all branch tips
+        */
+       while ((commit = get_revision(revs))) {
+               struct commit_list *to_merge;
+               int is_octopus;
+               const char *p1, *p2;
+               struct object_id *oid;
+
+               tail = &commit_list_insert(commit, tail)->next;
+               oidset_insert(&interesting, &commit->object.oid);
+
+               if ((commit->object.flags & PATCHSAME))
+                       continue;
+
+               strbuf_reset(&oneline);
+               pretty_print_commit(pp, commit, &oneline);
+
+               to_merge = commit->parents ? commit->parents->next : NULL;
+               if (!to_merge) {
+                       /* non-merge commit: easy case */
+                       strbuf_reset(&buf);
+                       if (!keep_empty && is_original_commit_empty(commit))
+                               strbuf_addf(&buf, "%c ", comment_line_char);
+                       strbuf_addf(&buf, "%s %s %s", cmd_pick,
+                                   oid_to_hex(&commit->object.oid),
+                                   oneline.buf);
+
+                       FLEX_ALLOC_STR(entry, string, buf.buf);
+                       oidcpy(&entry->entry.oid, &commit->object.oid);
+                       oidmap_put(&commit2todo, entry);
+
+                       continue;
+               }
+
+               is_octopus = to_merge && to_merge->next;
+
+               if (is_octopus)
+                       BUG("Octopus merges not yet supported");
+
+               /* Create a label */
+               strbuf_reset(&label);
+               if (skip_prefix(oneline.buf, "Merge ", &p1) &&
+                   (p1 = strchr(p1, '\'')) &&
+                   (p2 = strchr(++p1, '\'')))
+                       strbuf_add(&label, p1, p2 - p1);
+               else if (skip_prefix(oneline.buf, "Merge pull request ",
+                                    &p1) &&
+                        (p1 = strstr(p1, " from ")))
+                       strbuf_addstr(&label, p1 + strlen(" from "));
+               else
+                       strbuf_addbuf(&label, &oneline);
+
+               for (p1 = label.buf; *p1; p1++)
+                       if (isspace(*p1))
+                               *(char *)p1 = '-';
+
+               strbuf_reset(&buf);
+               strbuf_addf(&buf, "%s -C %s",
+                           cmd_merge, oid_to_hex(&commit->object.oid));
+
+               /* label the tip of merged branch */
+               oid = &to_merge->item->object.oid;
+               strbuf_addch(&buf, ' ');
+
+               if (!oidset_contains(&interesting, oid))
+                       strbuf_addstr(&buf, label_oid(oid, NULL, &state));
+               else {
+                       tips_tail = &commit_list_insert(to_merge->item,
+                                                       tips_tail)->next;
+
+                       strbuf_addstr(&buf, label_oid(oid, label.buf, &state));
+               }
+               strbuf_addf(&buf, " # %s", oneline.buf);
+
+               FLEX_ALLOC_STR(entry, string, buf.buf);
+               oidcpy(&entry->entry.oid, &commit->object.oid);
+               oidmap_put(&commit2todo, entry);
+       }
+
+       /*
+        * Second phase:
+        * - label branch points
+        * - add HEAD to the branch tips
+        */
+       for (iter = commits; iter; iter = iter->next) {
+               struct commit_list *parent = iter->item->parents;
+               for (; parent; parent = parent->next) {
+                       struct object_id *oid = &parent->item->object.oid;
+                       if (!oidset_contains(&interesting, oid))
+                               continue;
+                       if (!oidset_contains(&child_seen, oid))
+                               oidset_insert(&child_seen, oid);
+                       else
+                               label_oid(oid, "branch-point", &state);
+               }
+
+               /* Add HEAD as implict "tip of branch" */
+               if (!iter->next)
+                       tips_tail = &commit_list_insert(iter->item,
+                                                       tips_tail)->next;
+       }
+
+       /*
+        * Third phase: output the todo list. This is a bit tricky, as we
+        * want to avoid jumping back and forth between revisions. To
+        * accomplish that goal, we walk backwards from the branch tips,
+        * gathering commits not yet shown, reversing the list on the fly,
+        * then outputting that list (labeling revisions as needed).
+        */
+       fprintf(out, "%s onto\n", cmd_label);
+       for (iter = tips; iter; iter = iter->next) {
+               struct commit_list *list = NULL, *iter2;
+
+               commit = iter->item;
+               if (oidset_contains(&shown, &commit->object.oid))
+                       continue;
+               entry = oidmap_get(&state.commit2label, &commit->object.oid);
+
+               if (entry)
+                       fprintf(out, "\n# Branch %s\n", entry->string);
+               else
+                       fprintf(out, "\n");
+
+               while (oidset_contains(&interesting, &commit->object.oid) &&
+                      !oidset_contains(&shown, &commit->object.oid)) {
+                       commit_list_insert(commit, &list);
+                       if (!commit->parents) {
+                               commit = NULL;
+                               break;
+                       }
+                       commit = commit->parents->item;
+               }
+
+               if (!commit)
+                       fprintf(out, "%s onto\n", cmd_reset);
+               else {
+                       const char *to = NULL;
+
+                       entry = oidmap_get(&state.commit2label,
+                                          &commit->object.oid);
+                       if (entry)
+                               to = entry->string;
+                       else if (!rebase_cousins)
+                               to = label_oid(&commit->object.oid, NULL,
+                                              &state);
+
+                       if (!to || !strcmp(to, "onto"))
+                               fprintf(out, "%s onto\n", cmd_reset);
+                       else {
+                               strbuf_reset(&oneline);
+                               pretty_print_commit(pp, commit, &oneline);
+                               fprintf(out, "%s %s # %s\n",
+                                       cmd_reset, to, oneline.buf);
+                       }
+               }
+
+               for (iter2 = list; iter2; iter2 = iter2->next) {
+                       struct object_id *oid = &iter2->item->object.oid;
+                       entry = oidmap_get(&commit2todo, oid);
+                       /* only show if not already upstream */
+                       if (entry)
+                               fprintf(out, "%s\n", entry->string);
+                       entry = oidmap_get(&state.commit2label, oid);
+                       if (entry)
+                               fprintf(out, "%s %s\n",
+                                       cmd_label, entry->string);
+                       oidset_insert(&shown, oid);
+               }
+
+               free_commit_list(list);
+       }
+
+       free_commit_list(commits);
+       free_commit_list(tips);
+
+       strbuf_release(&label);
+       strbuf_release(&oneline);
+       strbuf_release(&buf);
+
+       oidmap_free(&commit2todo, 1);
+       oidmap_free(&state.commit2label, 1);
+       hashmap_free(&state.labels, 1);
+       strbuf_release(&state.buf);
+
+       return 0;
+}
+
 int sequencer_make_script(FILE *out, int argc, const char **argv,
                          unsigned flags)
 {
@@ -2997,11 +3697,16 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,
        struct commit *commit;
        int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
        const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
+       int recreate_merges = flags & TODO_LIST_RECREATE_MERGES;
 
        init_revisions(&revs, NULL);
        revs.verbose_header = 1;
-       revs.max_parents = 1;
-       revs.cherry_pick = 1;
+       if (recreate_merges)
+               revs.cherry_mark = 1;
+       else {
+               revs.max_parents = 1;
+               revs.cherry_pick = 1;
+       }
        revs.limited = 1;
        revs.reverse = 1;
        revs.right_only = 1;
@@ -3025,6 +3730,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,
        if (prepare_revision_walk(&revs) < 0)
                return error(_("make_script: error preparing revisions"));
 
+       if (recreate_merges)
+               return make_script_with_merges(&pp, &revs, out, flags);
+
        while ((commit = get_revision(&revs))) {
                strbuf_reset(&buf);
                if (!keep_empty && is_original_commit_empty(commit))
@@ -3114,8 +3822,14 @@ int transform_todos(unsigned flags)
                                          short_commit_name(item->commit) :
                                          oid_to_hex(&item->commit->object.oid);
 
+                       if (item->command == TODO_MERGE)
+                               strbuf_addstr(&buf, " -C");
+                       else if (item->command == TODO_MERGE_AND_EDIT)
+                               strbuf_addstr(&buf, " -c");
+
                        strbuf_addf(&buf, " %s", oid);
                }
+
                /* add all the rest */
                if (!item->arg_len)
                        strbuf_addch(&buf, '\n');
@@ -3391,7 +4105,7 @@ int rearrange_squash(void)
                struct subject2item_entry *entry;
 
                next[i] = tail[i] = -1;
-               if (item->command >= TODO_EXEC) {
+               if (!item->commit || item->command == TODO_DROP) {
                        subjects[i] = NULL;
                        continue;
                }
index e45b178dfc41d723bf186f20674c4515d7c7fa00..739dd0fa92b263f7c05f9c1d41f840b2f9e41dcd 100644 (file)
@@ -59,6 +59,13 @@ int sequencer_remove_state(struct replay_opts *opts);
 #define TODO_LIST_KEEP_EMPTY (1U << 0)
 #define TODO_LIST_SHORTEN_IDS (1U << 1)
 #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
+#define TODO_LIST_RECREATE_MERGES (1U << 3)
+/*
+ * When recreating merges, commits that do have the base commit as ancestor
+ * ("cousins") are *not* rebased onto the new base by default. If those
+ * commits should be rebased onto the new base, this flag needs to be passed.
+ */
+#define TODO_LIST_REBASE_COUSINS (1U << 4)
 int sequencer_make_script(FILE *out, int argc, const char **argv,
                          unsigned flags);
 
diff --git a/setup.c b/setup.c
index 72877796420b06213665f5d357decb202e71fa91..664453fcef7f3b75f56d000dc52f42a0aff42fb6 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -1116,8 +1116,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
                        const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
                        if (!gitdir)
                                gitdir = DEFAULT_GIT_DIR_ENVIRONMENT;
-                       repo_set_gitdir(the_repository, gitdir);
-                       setup_git_env();
+                       setup_git_env(gitdir);
                }
                if (startup_info->have_repository)
                        repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
index 1b94f39c4c5653b090d5dbecf0cf7a785b0556bb..6ec3f8d5734683942f518f53944e104de41f32b8 100644 (file)
@@ -665,15 +665,11 @@ int foreach_alt_odb(alt_odb_fn fn, void *cb)
 
 void prepare_alt_odb(void)
 {
-       const char *alt;
-
        if (alt_odb_tail)
                return;
 
-       alt = getenv(ALTERNATE_DB_ENVIRONMENT);
-
        alt_odb_tail = &alt_odb_list;
-       link_alt_odb_entries(alt, PATH_SEP, NULL, 0);
+       link_alt_odb_entries(the_repository->alternate_db, PATH_SEP, NULL, 0);
 
        read_info_alternates(get_object_directory(), 0);
 }
@@ -1262,14 +1258,19 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi,
                if (find_pack_entry(real, &e))
                        break;
 
+               if (flags & OBJECT_INFO_IGNORE_LOOSE)
+                       return -1;
+
                /* Most likely it's a loose object. */
                if (!sha1_loose_object_info(real, oi, flags))
                        return 0;
 
                /* Not a loose object; someone else may have just packed it. */
-               reprepare_packed_git();
-               if (find_pack_entry(real, &e))
-                       break;
+               if (!(flags & OBJECT_INFO_QUICK)) {
+                       reprepare_packed_git();
+                       if (find_pack_entry(real, &e))
+                               break;
+               }
 
                /* Check if it is a missing object */
                if (fetch_if_missing && repository_format_partial_clone &&
index 0759590b3e56de0fab6ff34f25206fb4cdeca4c5..013fc673fa421d6c5abdc40d3ce3c48528464724 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "refs.h"
+#include "string-list.h"
 #include "utf8.h"
 
 int starts_with(const char *str, const char *prefix)
@@ -171,6 +172,21 @@ struct strbuf **strbuf_split_buf(const char *str, size_t slen,
        return ret;
 }
 
+void strbuf_add_separated_string_list(struct strbuf *str,
+                                     const char *sep,
+                                     struct string_list *slist)
+{
+       struct string_list_item *item;
+       int sep_needed = 0;
+
+       for_each_string_list_item(item, slist) {
+               if (sep_needed)
+                       strbuf_addstr(str, sep);
+               strbuf_addstr(str, item->string);
+               sep_needed = 1;
+       }
+}
+
 void strbuf_list_free(struct strbuf **sbs)
 {
        struct strbuf **s = sbs;
index e6cae5f4398c8eb4c4a53c7db226a920e32bfde9..652f17392c15993272db5891227ec25b3c78d8bd 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -1,6 +1,8 @@
 #ifndef STRBUF_H
 #define STRBUF_H
 
+struct string_list;
+
 /**
  * strbuf's are meant to be used with all the usual C string and memory
  * APIs. Given that the length of the buffer is known, it's often better to
@@ -531,6 +533,20 @@ static inline struct strbuf **strbuf_split(const struct strbuf *sb,
        return strbuf_split_max(sb, terminator, 0);
 }
 
+/*
+ * Adds all strings of a string list to the strbuf, separated by the given
+ * separator.  For example, if sep is
+ *   ', '
+ * and slist contains
+ *   ['element1', 'element2', ..., 'elementN'],
+ * then write:
+ *   'element1, element2, ..., elementN'
+ * to str.  If only one element, just write "element1" to str.
+ */
+extern void strbuf_add_separated_string_list(struct strbuf *str,
+                                            const char *sep,
+                                            struct string_list *slist);
+
 /**
  * Free a NULL-terminated list of strbufs (for example, the return
  * values of the strbuf_split*() functions).
diff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh
new file mode 100755 (executable)
index 0000000..f2f44d2
--- /dev/null
@@ -0,0 +1,208 @@
+#!/bin/sh
+#
+# Copyright (c) 2017 Johannes E. Schindelin
+#
+
+test_description='git rebase -i --recreate-merges
+
+This test runs git rebase "interactively", retaining the branch structure by
+recreating merge commits.
+
+Initial setup:
+
+    -- B --                   (first)
+   /       \
+ A - C - D - E - H            (master)
+       \       /
+        F - G                (second)
+'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+test_expect_success 'setup' '
+       write_script replace-editor.sh <<-\EOF &&
+       mv "$1" "$(git rev-parse --git-path ORIGINAL-TODO)"
+       cp script-from-scratch "$1"
+       EOF
+
+       test_commit A &&
+       git checkout -b first &&
+       test_commit B &&
+       git checkout master &&
+       test_commit C &&
+       test_commit D &&
+       git merge --no-commit B &&
+       test_tick &&
+       git commit -m E &&
+       git tag -m E E &&
+       git checkout -b second C &&
+       test_commit F &&
+       test_commit G &&
+       git checkout master &&
+       git merge --no-commit G &&
+       test_tick &&
+       git commit -m H &&
+       git tag -m H H
+'
+
+cat >script-from-scratch <<\EOF
+label onto
+
+# onebranch
+pick G
+pick D
+label onebranch
+
+# second
+reset onto
+pick B
+label second
+
+reset onto
+merge -C H second
+merge onebranch # Merge the topic branch 'onebranch'
+EOF
+
+test_cmp_graph () {
+       cat >expect &&
+       git log --graph --boundary --format=%s "$@" >output &&
+       sed "s/ *$//" <output >output.trimmed &&
+       test_cmp expect output.trimmed
+}
+
+test_expect_success 'create completely different structure' '
+       test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
+       test_tick &&
+       git rebase -i --recreate-merges A &&
+       test_cmp_graph <<-\EOF
+       *   Merge the topic branch '\''onebranch'\''
+       |\
+       | * D
+       | * G
+       * |   H
+       |\ \
+       | |/
+       |/|
+       | * B
+       |/
+       * A
+       EOF
+'
+
+test_expect_success 'generate correct todo list' '
+       cat >expect <<-\EOF &&
+       label onto
+
+       reset onto
+       pick d9df450 B
+       label E
+
+       reset onto
+       pick 5dee784 C
+       label branch-point
+       pick ca2c861 F
+       pick 088b00a G
+       label H
+
+       reset branch-point # C
+       pick 12bd07b D
+       merge -C 2051b56 E # E
+       merge -C 233d48a H # H
+
+       EOF
+
+       grep -v "^#" <.git/ORIGINAL-TODO >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'with a branch tip that was cherry-picked already' '
+       git checkout -b already-upstream master &&
+       base="$(git rev-parse --verify HEAD)" &&
+
+       test_commit A1 &&
+       test_commit A2 &&
+       git reset --hard $base &&
+       test_commit B1 &&
+       test_tick &&
+       git merge -m "Merge branch A" A2 &&
+
+       git checkout -b upstream-with-a2 $base &&
+       test_tick &&
+       git cherry-pick A2 &&
+
+       git checkout already-upstream &&
+       test_tick &&
+       git rebase -i --recreate-merges upstream-with-a2 &&
+       test_cmp_graph upstream-with-a2.. <<-\EOF
+       *   Merge branch A
+       |\
+       | * A1
+       * | B1
+       |/
+       o A2
+       EOF
+'
+
+test_expect_success 'do not rebase cousins unless asked for' '
+       write_script copy-editor.sh <<-\EOF &&
+       cp "$1" "$(git rev-parse --git-path ORIGINAL-TODO)"
+       EOF
+
+       test_config sequence.editor \""$PWD"/copy-editor.sh\" &&
+       git checkout -b cousins master &&
+       before="$(git rev-parse --verify HEAD)" &&
+       test_tick &&
+       git rebase -i --recreate-merges HEAD^ &&
+       test_cmp_rev HEAD $before &&
+       test_tick &&
+       git rebase -i --recreate-merges=rebase-cousins HEAD^ &&
+       test_cmp_graph HEAD^.. <<-\EOF
+       *   Merge the topic branch '\''onebranch'\''
+       |\
+       | * D
+       | * G
+       |/
+       o H
+       EOF
+'
+
+test_expect_success 'refs/rewritten/* is worktree-local' '
+       git worktree add wt &&
+       cat >wt/script-from-scratch <<-\EOF &&
+       label xyz
+       exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :
+       exec git rev-parse --verify refs/rewritten/xyz >b
+       EOF
+
+       test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
+       git -C wt rebase -i HEAD &&
+       test_must_be_empty wt/a &&
+       test_cmp_rev HEAD "$(cat wt/b)"
+'
+
+test_expect_success 'post-rewrite hook and fixups work for merges' '
+       git checkout -b post-rewrite &&
+       test_commit same1 &&
+       git reset --hard HEAD^ &&
+       test_commit same2 &&
+       git merge -m "to fix up" same1 &&
+       echo same old same old >same2.t &&
+       test_tick &&
+       git commit --fixup HEAD same2.t &&
+       fixup="$(git rev-parse HEAD)" &&
+
+       mkdir -p .git/hooks &&
+       test_when_finished "rm .git/hooks/post-rewrite" &&
+       echo "cat >actual" | write_script .git/hooks/post-rewrite &&
+
+       test_tick &&
+       git rebase -i --autosquash --recreate-merges HEAD^^^ &&
+       printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \
+               $fixup^^2 HEAD^2 \
+               $fixup^^ HEAD^ \
+               $fixup^ HEAD \
+               $fixup HEAD) &&
+       test_cmp expect actual
+'
+
+test_done
index 783bdbf59db07ebf2afac83165d31b86fd6b95db..0d89f6d0f651e46251c9c3c610f711353c701893 100755 (executable)
@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick "-" works with arguments' '
        test_cmp expect actual
 '
 
-test_expect_failure 'cherry-pick works with dirty renamed file' '
+test_expect_success 'cherry-pick works with dirty renamed file' '
        test_commit to-rename &&
        git checkout -b unrelated &&
        test_commit unrelated &&
diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh
new file mode 100755 (executable)
index 0000000..2e28f29
--- /dev/null
@@ -0,0 +1,3998 @@
+#!/bin/sh
+
+test_description="recursive merge with directory renames"
+# includes checking of many corner cases, with a similar methodology to:
+#   t6042: corner cases with renames but not criss-cross merges
+#   t6036: corner cases with both renames and criss-cross merges
+#
+# The setup for all of them, pictorially, is:
+#
+#      A
+#      o
+#     / \
+#  O o   ?
+#     \ /
+#      o
+#      B
+#
+# To help make it easier to follow the flow of tests, they have been
+# divided into sections and each test will start with a quick explanation
+# of what commits O, A, and B contain.
+#
+# Notation:
+#    z/{b,c}   means  files z/b and z/c both exist
+#    x/d_1     means  file x/d exists with content d1.  (Purpose of the
+#                     underscore notation is to differentiate different
+#                     files that might be renamed into each other's paths.)
+
+. ./test-lib.sh
+
+
+###########################################################################
+# SECTION 1: Basic cases we should be able to handle
+###########################################################################
+
+# Testcase 1a, Basic directory rename.
+#   Commit O: z/{b,c}
+#   Commit A: y/{b,c}
+#   Commit B: z/{b,c,d,e/f}
+#   Expected: y/{b,c,d,e/f}
+
+test_expect_success '1a-setup: Simple directory rename detection' '
+       test_create_repo 1a &&
+       (
+               cd 1a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo d >z/d &&
+               mkdir z/e &&
+               echo f >z/e/f &&
+               git add z/d z/e/f &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '1a-check: Simple directory rename detection' '
+       (
+               cd 1a &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e/f &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    B:z/d    B:z/e/f &&
+               test_cmp expect actual &&
+
+               git hash-object y/d >actual &&
+               git rev-parse B:z/d >expect &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse HEAD:z/d &&
+               test_must_fail git rev-parse HEAD:z/e/f &&
+               test_path_is_missing z/d &&
+               test_path_is_missing z/e/f
+       )
+'
+
+# Testcase 1b, Merge a directory with another
+#   Commit O: z/{b,c},   y/d
+#   Commit A: z/{b,c,e}, y/d
+#   Commit B: y/{b,c,d}
+#   Expected: y/{b,c,d,e}
+
+test_expect_success '1b-setup: Merge a directory with another' '
+       test_create_repo 1b &&
+       (
+               cd 1b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir y &&
+               echo d >y/d &&
+               git add z y &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               echo e >z/e &&
+               git add z/e &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z/b y &&
+               git mv z/c y &&
+               rmdir z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '1b-check: Merge a directory with another' '
+       (
+               cd 1b &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:y/d    A:z/e &&
+               test_cmp expect actual &&
+               test_must_fail git rev-parse HEAD:z/e
+       )
+'
+
+# Testcase 1c, Transitive renaming
+#   (Related to testcases 3a and 6d -- when should a transitive rename apply?)
+#   (Related to testcases 9c and 9d -- can transitivity repeat?)
+#   (Related to testcase 12b -- joint-transitivity?)
+#   Commit O: z/{b,c},   x/d
+#   Commit A: y/{b,c},   x/d
+#   Commit B: z/{b,c,d}
+#   Expected: y/{b,c,d}  (because x/d -> z/d -> y/d)
+
+test_expect_success '1c-setup: Transitive renaming' '
+       test_create_repo 1c &&
+       (
+               cd 1c &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '1c-check: Transitive renaming' '
+       (
+               cd 1c &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:x/d &&
+               test_cmp expect actual &&
+               test_must_fail git rev-parse HEAD:x/d &&
+               test_must_fail git rev-parse HEAD:z/d &&
+               test_path_is_missing z/d
+       )
+'
+
+# Testcase 1d, Directory renames (merging two directories into one new one)
+#              cause a rename/rename(2to1) conflict
+#   (Related to testcases 1c and 7b)
+#   Commit O. z/{b,c},        y/{d,e}
+#   Commit A. x/{b,c},        y/{d,e,m,wham_1}
+#   Commit B. z/{b,c,n,wham_2}, x/{d,e}
+#   Expected: x/{b,c,d,e,m,n}, CONFLICT:(y/wham_1 & z/wham_2 -> x/wham)
+#   Note: y/m & z/n should definitely move into x.  By the same token, both
+#         y/wham_1 & z/wham_2 should too...giving us a conflict.
+
+test_expect_success '1d-setup: Directory renames cause a rename/rename(2to1) conflict' '
+       test_create_repo 1d &&
+       (
+               cd 1d &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir y &&
+               echo d >y/d &&
+               echo e >y/e &&
+               git add z y &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z x &&
+               echo m >y/m &&
+               echo wham1 >y/wham &&
+               git add y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv y x &&
+               echo n >z/n &&
+               echo wham2 >z/wham &&
+               git add z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) conflict' '
+       (
+               cd 1d &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/rename)" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 8 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       :0:x/b :0:x/c :0:x/d :0:x/e :0:x/m :0:x/n &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  O:y/d  O:y/e  A:y/m  B:z/n &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse :0:x/wham &&
+               git rev-parse >actual \
+                       :2:x/wham :3:x/wham &&
+               git rev-parse >expect \
+                        A:y/wham  B:z/wham &&
+               test_cmp expect actual &&
+
+               test_path_is_missing x/wham &&
+               test_path_is_file x/wham~HEAD &&
+               test_path_is_file x/wham~B^0 &&
+
+               git hash-object >actual \
+                       x/wham~HEAD x/wham~B^0 &&
+               git rev-parse >expect \
+                       A:y/wham    B:z/wham &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 1e, Renamed directory, with all filenames being renamed too
+#   (Related to testcases 9f & 9g)
+#   Commit O: z/{oldb,oldc}
+#   Commit A: y/{newb,newc}
+#   Commit B: z/{oldb,oldc,d}
+#   Expected: y/{newb,newc,d}
+
+test_expect_success '1e-setup: Renamed directory, with all files being renamed too' '
+       test_create_repo 1e &&
+       (
+               cd 1e &&
+
+               mkdir z &&
+               echo b >z/oldb &&
+               echo c >z/oldc &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir y &&
+               git mv z/oldb y/newb &&
+               git mv z/oldc y/newc &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo d >z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '1e-check: Renamed directory, with all files being renamed too' '
+       (
+               cd 1e &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/newb HEAD:y/newc HEAD:y/d &&
+               git rev-parse >expect \
+                       O:z/oldb    O:z/oldc    B:z/d &&
+               test_cmp expect actual &&
+               test_must_fail git rev-parse HEAD:z/d
+       )
+'
+
+# Testcase 1f, Split a directory into two other directories
+#   (Related to testcases 3a, all of section 2, and all of section 4)
+#   Commit O: z/{b,c,d,e,f}
+#   Commit A: z/{b,c,d,e,f,g}
+#   Commit B: y/{b,c}, x/{d,e,f}
+#   Expected: y/{b,c}, x/{d,e,f,g}
+
+test_expect_success '1f-setup: Split a directory into two other directories' '
+       test_create_repo 1f &&
+       (
+               cd 1f &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d >z/d &&
+               echo e >z/e &&
+               echo f >z/f &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               echo g >z/g &&
+               git add z/g &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir y &&
+               mkdir x &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               git mv z/d x/ &&
+               git mv z/e x/ &&
+               git mv z/f x/ &&
+               rmdir z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '1f-check: Split a directory into two other directories' '
+       (
+               cd 1f &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:x/d HEAD:x/e HEAD:x/f HEAD:x/g &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:z/d    O:z/e    O:z/f    A:z/g &&
+               test_cmp expect actual &&
+               test_path_is_missing z/g &&
+               test_must_fail git rev-parse HEAD:z/g
+       )
+'
+
+###########################################################################
+# Rules suggested by testcases in section 1:
+#
+#   We should still detect the directory rename even if it wasn't just
+#   the directory renamed, but the files within it. (see 1b)
+#
+#   If renames split a directory into two or more others, the directory
+#   with the most renames, "wins" (see 1c).  However, see the testcases
+#   in section 2, plus testcases 3a and 4a.
+###########################################################################
+
+
+###########################################################################
+# SECTION 2: Split into multiple directories, with equal number of paths
+#
+# Explore the splitting-a-directory rules a bit; what happens in the
+# edge cases?
+#
+# Note that there is a closely related case of a directory not being
+# split on either side of history, but being renamed differently on
+# each side.  See testcase 8e for that.
+###########################################################################
+
+# Testcase 2a, Directory split into two on one side, with equal numbers of paths
+#   Commit O: z/{b,c}
+#   Commit A: y/b, w/c
+#   Commit B: z/{b,c,d}
+#   Expected: y/b, w/c, z/d, with warning about z/ -> (y/ vs. w/) conflict
+test_expect_success '2a-setup: Directory split into two on one side, with equal numbers of paths' '
+       test_create_repo 2a &&
+       (
+               cd 2a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir y &&
+               mkdir w &&
+               git mv z/b y/ &&
+               git mv z/c w/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo d >z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '2a-check: Directory split into two on one side, with equal numbers of paths' '
+       (
+               cd 2a &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT.*directory rename split" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:w/c :0:z/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  B:z/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 2b, Directory split into two on one side, with equal numbers of paths
+#   Commit O: z/{b,c}
+#   Commit A: y/b, w/c
+#   Commit B: z/{b,c}, x/d
+#   Expected: y/b, w/c, x/d; No warning about z/ -> (y/ vs. w/) conflict
+test_expect_success '2b-setup: Directory split into two on one side, with equal numbers of paths' '
+       test_create_repo 2b &&
+       (
+               cd 2b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir y &&
+               mkdir w &&
+               git mv z/b y/ &&
+               git mv z/c w/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir x &&
+               echo d >x/d &&
+               git add x/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '2b-check: Directory split into two on one side, with equal numbers of paths' '
+       (
+               cd 2b &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 >out &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:w/c :0:x/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  B:x/d &&
+               test_cmp expect actual &&
+               test_i18ngrep ! "CONFLICT.*directory rename split" out
+       )
+'
+
+###########################################################################
+# Rules suggested by section 2:
+#
+#   None; the rule was already covered in section 1.  These testcases are
+#   here just to make sure the conflict resolution and necessary warning
+#   messages are handled correctly.
+###########################################################################
+
+
+###########################################################################
+# SECTION 3: Path in question is the source path for some rename already
+#
+# Combining cases from Section 1 and trying to handle them could lead to
+# directory renaming detection being over-applied.  So, this section
+# provides some good testcases to check that the implementation doesn't go
+# too far.
+###########################################################################
+
+# Testcase 3a, Avoid implicit rename if involved as source on other side
+#   (Related to testcases 1c, 1f, and 9h)
+#   Commit O: z/{b,c,d}
+#   Commit A: z/{b,c,d} (no change)
+#   Commit B: y/{b,c}, x/d
+#   Expected: y/{b,c}, x/d
+test_expect_success '3a-setup: Avoid implicit rename if involved as source on other side' '
+       test_create_repo 3a &&
+       (
+               cd 3a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_tick &&
+               git commit --allow-empty -m "A" &&
+
+               git checkout B &&
+               mkdir y &&
+               mkdir x &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               git mv z/d x/ &&
+               rmdir z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '3a-check: Avoid implicit rename if involved as source on other side' '
+       (
+               cd 3a &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:x/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:z/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 3b, Avoid implicit rename if involved as source on other side
+#   (Related to testcases 5c and 7c, also kind of 1e and 1f)
+#   Commit O: z/{b,c,d}
+#   Commit A: y/{b,c}, x/d
+#   Commit B: z/{b,c}, w/d
+#   Expected: y/{b,c}, CONFLICT:(z/d -> x/d vs. w/d)
+#   NOTE: We're particularly checking that since z/d is already involved as
+#         a source in a file rename on the same side of history, that we don't
+#         get it involved in directory rename detection.  If it were, we might
+#         end up with CONFLICT:(z/d -> y/d vs. x/d vs. w/d), i.e. a
+#         rename/rename/rename(1to3) conflict, which is just weird.
+test_expect_success '3b-setup: Avoid implicit rename if involved as source on current side' '
+       test_create_repo 3b &&
+       (
+               cd 3b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir y &&
+               mkdir x &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               git mv z/d x/ &&
+               rmdir z &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir w &&
+               git mv z/d w/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '3b-check: Avoid implicit rename if involved as source on current side' '
+       (
+               cd 3b &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep CONFLICT.*rename/rename.*z/d.*x/d.*w/d out &&
+               test_i18ngrep ! CONFLICT.*rename/rename.*y/d out &&
+
+               git ls-files -s >out &&
+               test_line_count = 5 out &&
+               git ls-files -u >out &&
+               test_line_count = 3 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :1:z/d :2:x/d :3:w/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  O:z/d  O:z/d  O:z/d &&
+               test_cmp expect actual &&
+
+               test_path_is_missing z/d &&
+               git hash-object >actual \
+                       x/d   w/d &&
+               git rev-parse >expect \
+                       O:z/d O:z/d &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# Rules suggested by section 3:
+#
+#   Avoid directory-rename-detection for a path, if that path is the source
+#   of a rename on either side of a merge.
+###########################################################################
+
+
+###########################################################################
+# SECTION 4: Partially renamed directory; still exists on both sides of merge
+#
+# What if we were to attempt to do directory rename detection when someone
+# "mostly" moved a directory but still left some files around, or,
+# equivalently, fully renamed a directory in one commmit and then recreated
+# that directory in a later commit adding some new files and then tried to
+# merge?
+#
+# It's hard to divine user intent in these cases, because you can make an
+# argument that, depending on the intermediate history of the side being
+# merged, that some users will want files in that directory to
+# automatically be detected and renamed, while users with a different
+# intermediate history wouldn't want that rename to happen.
+#
+# I think that it is best to simply not have directory rename detection
+# apply to such cases.  My reasoning for this is four-fold: (1) it's
+# easiest for users in general to figure out what happened if we don't
+# apply directory rename detection in any such case, (2) it's an easy rule
+# to explain ["We don't do directory rename detection if the directory
+# still exists on both sides of the merge"], (3) we can get some hairy
+# edge/corner cases that would be really confusing and possibly not even
+# representable in the index if we were to even try, and [related to 3] (4)
+# attempting to resolve this issue of divining user intent by examining
+# intermediate history goes against the spirit of three-way merges and is a
+# path towards crazy corner cases that are far more complex than what we're
+# already dealing with.
+#
+# Note that the wording of the rule ("We don't do directory rename
+# detection if the directory still exists on both sides of the merge.")
+# also excludes "renaming" of a directory into a subdirectory of itself
+# (e.g. /some/dir/* -> /some/dir/subdir/*).  It may be possible to carve
+# out an exception for "renaming"-beneath-itself cases without opening
+# weird edge/corner cases for other partial directory renames, but for now
+# we are keeping the rule simple.
+#
+# This section contains a test for a partially-renamed-directory case.
+###########################################################################
+
+# Testcase 4a, Directory split, with original directory still present
+#   (Related to testcase 1f)
+#   Commit O: z/{b,c,d,e}
+#   Commit A: y/{b,c,d}, z/e
+#   Commit B: z/{b,c,d,e,f}
+#   Expected: y/{b,c,d}, z/{e,f}
+#   NOTE: Even though most files from z moved to y, we don't want f to follow.
+
+test_expect_success '4a-setup: Directory split, with original directory still present' '
+       test_create_repo 4a &&
+       (
+               cd 4a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d >z/d &&
+               echo e >z/e &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir y &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               git mv z/d y/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo f >z/f &&
+               git add z/f &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '4a-check: Directory split, with original directory still present' '
+       (
+               cd 4a &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 5 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d HEAD:z/e HEAD:z/f &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:z/d    O:z/e    B:z/f &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# Rules suggested by section 4:
+#
+#   Directory-rename-detection should be turned off for any directories (as
+#   a source for renames) that exist on both sides of the merge.  (The "as
+#   a source for renames" clarification is due to cases like 1c where
+#   the target directory exists on both sides and we do want the rename
+#   detection.)  But, sadly, see testcase 8b.
+###########################################################################
+
+
+###########################################################################
+# SECTION 5: Files/directories in the way of subset of to-be-renamed paths
+#
+# Implicitly renaming files due to a detected directory rename could run
+# into problems if there are files or directories in the way of the paths
+# we want to rename.  Explore such cases in this section.
+###########################################################################
+
+# Testcase 5a, Merge directories, other side adds files to original and target
+#   Commit O: z/{b,c},       y/d
+#   Commit A: z/{b,c,e_1,f}, y/{d,e_2}
+#   Commit B: y/{b,c,d}
+#   Expected: z/e_1, y/{b,c,d,e_2,f} + CONFLICT warning
+#   NOTE: While directory rename detection is active here causing z/f to
+#         become y/f, we did not apply this for z/e_1 because that would
+#         give us an add/add conflict for y/e_1 vs y/e_2.  This problem with
+#         this add/add, is that both versions of y/e are from the same side
+#         of history, giving us no way to represent this conflict in the
+#         index.
+
+test_expect_success '5a-setup: Merge directories, other side adds files to original and target' '
+       test_create_repo 5a &&
+       (
+               cd 5a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir y &&
+               echo d >y/d &&
+               git add z y &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               echo e1 >z/e &&
+               echo f >z/f &&
+               echo e2 >y/e &&
+               git add z/e z/f y/e &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               rmdir z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '5a-check: Merge directories, other side adds files to original and target' '
+       (
+               cd 5a &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT.*implicit dir rename" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :0:y/d :0:y/e :0:z/e :0:y/f &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  O:y/d  A:y/e  A:z/e  A:z/f &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 5b, Rename/delete in order to get add/add/add conflict
+#   (Related to testcase 8d; these may appear slightly inconsistent to users;
+#    Also related to testcases 7d and 7e)
+#   Commit O: z/{b,c,d_1}
+#   Commit A: y/{b,c,d_2}
+#   Commit B: z/{b,c,d_1,e}, y/d_3
+#   Expected: y/{b,c,e}, CONFLICT(add/add: y/d_2 vs. y/d_3)
+#   NOTE: If z/d_1 in commit B were to be involved in dir rename detection, as
+#         we normaly would since z/ is being renamed to y/, then this would be
+#         a rename/delete (z/d_1 -> y/d_1 vs. deleted) AND an add/add/add
+#         conflict of y/d_1 vs. y/d_2 vs. y/d_3.  Add/add/add is not
+#         representable in the index, so the existence of y/d_3 needs to
+#         cause us to bail on directory rename detection for that path, falling
+#         back to git behavior without the directory rename detection.
+
+test_expect_success '5b-setup: Rename/delete in order to get add/add/add conflict' '
+       test_create_repo 5b &&
+       (
+               cd 5b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d1 >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git rm z/d &&
+               git mv z y &&
+               echo d2 >y/d &&
+               git add y/d &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir y &&
+               echo d3 >y/d &&
+               echo e >z/e &&
+               git add y/d z/e &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '5b-check: Rename/delete in order to get add/add/add conflict' '
+       (
+               cd 5b &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (add/add).* y/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 5 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :0:y/e :2:y/d :3:y/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  B:z/e  A:y/d  B:y/d &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse :1:y/d &&
+               test_path_is_file y/d
+       )
+'
+
+# Testcase 5c, Transitive rename would cause rename/rename/rename/add/add/add
+#   (Directory rename detection would result in transitive rename vs.
+#    rename/rename(1to2) and turn it into a rename/rename(1to3).  Further,
+#    rename paths conflict with separate adds on the other side)
+#   (Related to testcases 3b and 7c)
+#   Commit O: z/{b,c}, x/d_1
+#   Commit A: y/{b,c,d_2}, w/d_1
+#   Commit B: z/{b,c,d_1,e}, w/d_3, y/d_4
+#   Expected: A mess, but only a rename/rename(1to2)/add/add mess.  Use the
+#             presence of y/d_4 in B to avoid doing transitive rename of
+#             x/d_1 -> z/d_1 -> y/d_1, so that the only paths we have at
+#             y/d are y/d_2 and y/d_4.  We still do the move from z/e to y/e,
+#             though, because it doesn't have anything in the way.
+
+test_expect_success '5c-setup: Transitive rename would cause rename/rename/rename/add/add/add' '
+       test_create_repo 5c &&
+       (
+               cd 5c &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d1 >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               echo d2 >y/d &&
+               git add y/d &&
+               git mv x w &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/ &&
+               mkdir w &&
+               mkdir y &&
+               echo d3 >w/d &&
+               echo d4 >y/d &&
+               echo e >z/e &&
+               git add w/ y/ z/e &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '5c-check: Transitive rename would cause rename/rename/rename/add/add/add' '
+       (
+               cd 5c &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/rename).*x/d.*w/d.*z/d" out &&
+               test_i18ngrep "CONFLICT (add/add).* y/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 9 out &&
+               git ls-files -u >out &&
+               test_line_count = 6 out &&
+               git ls-files -o >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :0:y/e &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  B:z/e &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse :1:y/d &&
+               git rev-parse >actual \
+                       :2:w/d :3:w/d :1:x/d :2:y/d :3:y/d :3:z/d &&
+               git rev-parse >expect \
+                        O:x/d  B:w/d  O:x/d  A:y/d  B:y/d  O:x/d &&
+               test_cmp expect actual &&
+
+               git hash-object >actual \
+                       w/d~HEAD w/d~B^0 z/d &&
+               git rev-parse >expect \
+                       O:x/d    B:w/d   O:x/d &&
+               test_cmp expect actual &&
+               test_path_is_missing x/d &&
+               test_path_is_file y/d &&
+               grep -q "<<<<" y/d  # conflict markers should be present
+       )
+'
+
+# Testcase 5d, Directory/file/file conflict due to directory rename
+#   Commit O: z/{b,c}
+#   Commit A: y/{b,c,d_1}
+#   Commit B: z/{b,c,d_2,f}, y/d/e
+#   Expected: y/{b,c,d/e,f}, z/d_2, CONFLICT(file/directory), y/d_1~HEAD
+#   Note: The fact that y/d/ exists in B makes us bail on directory rename
+#         detection for z/d_2, but that doesn't prevent us from applying the
+#         directory rename detection for z/f -> y/f.
+
+test_expect_success '5d-setup: Directory/file/file conflict due to directory rename' '
+       test_create_repo 5d &&
+       (
+               cd 5d &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               echo d1 >y/d &&
+               git add y/d &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir -p y/d &&
+               echo e >y/d/e &&
+               echo d2 >z/d &&
+               echo f >z/f &&
+               git add y/d/e z/d z/f &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '5d-check: Directory/file/file conflict due to directory rename' '
+       (
+               cd 5d &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (file/directory).*y/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -u >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 2 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d :0:y/d/e &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  B:z/d  B:z/f  A:y/d  B:y/d/e &&
+               test_cmp expect actual &&
+
+               git hash-object y/d~HEAD >actual &&
+               git rev-parse A:y/d >expect &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# Rules suggested by section 5:
+#
+#   If a subset of to-be-renamed files have a file or directory in the way,
+#   "turn off" the directory rename for those specific sub-paths, falling
+#   back to old handling.  But, sadly, see testcases 8a and 8b.
+###########################################################################
+
+
+###########################################################################
+# SECTION 6: Same side of the merge was the one that did the rename
+#
+# It may sound obvious that you only want to apply implicit directory
+# renames to directories if the _other_ side of history did the renaming.
+# If you did make an implementation that didn't explicitly enforce this
+# rule, the majority of cases that would fall under this section would
+# also be solved by following the rules from the above sections.  But
+# there are still a few that stick out, so this section covers them just
+# to make sure we also get them right.
+###########################################################################
+
+# Testcase 6a, Tricky rename/delete
+#   Commit O: z/{b,c,d}
+#   Commit A: z/b
+#   Commit B: y/{b,c}, z/d
+#   Expected: y/b, CONFLICT(rename/delete, z/c -> y/c vs. NULL)
+#   Note: We're just checking here that the rename of z/b and z/c to put
+#         them under y/ doesn't accidentally catch z/d and make it look like
+#         it is also involved in a rename/delete conflict.
+
+test_expect_success '6a-setup: Tricky rename/delete' '
+       test_create_repo 6a &&
+       (
+               cd 6a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git rm z/c &&
+               git rm z/d &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir y &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '6a-check: Tricky rename/delete' '
+       (
+               cd 6a &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/delete).*z/c.*y/c" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 2 out &&
+               git ls-files -u >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :3:y/c &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 6b, Same rename done on both sides
+#   (Related to testcases 6c and 8e)
+#   Commit O: z/{b,c}
+#   Commit A: y/{b,c}
+#   Commit B: y/{b,c}, z/d
+#   Expected: y/{b,c}, z/d
+#   Note: If we did directory rename detection here, we'd move z/d into y/,
+#         but B did that rename and still decided to put the file into z/,
+#         so we probably shouldn't apply directory rename detection for it.
+
+test_expect_success '6b-setup: Same rename done on both sides' '
+       test_create_repo 6b &&
+       (
+               cd 6b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z y &&
+               mkdir z &&
+               echo d >z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '6b-check: Same rename done on both sides' '
+       (
+               cd 6b &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:z/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    B:z/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 6c, Rename only done on same side
+#   (Related to testcases 6b and 8e)
+#   Commit O: z/{b,c}
+#   Commit A: z/{b,c} (no change)
+#   Commit B: y/{b,c}, z/d
+#   Expected: y/{b,c}, z/d
+#   NOTE: Seems obvious, but just checking that the implementation doesn't
+#         "accidentally detect a rename" and give us y/{b,c,d}.
+
+test_expect_success '6c-setup: Rename only done on same side' '
+       test_create_repo 6c &&
+       (
+               cd 6c &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_tick &&
+               git commit --allow-empty -m "A" &&
+
+               git checkout B &&
+               git mv z y &&
+               mkdir z &&
+               echo d >z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '6c-check: Rename only done on same side' '
+       (
+               cd 6c &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:z/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    B:z/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 6d, We don't always want transitive renaming
+#   (Related to testcase 1c)
+#   Commit O: z/{b,c}, x/d
+#   Commit A: z/{b,c}, x/d (no change)
+#   Commit B: y/{b,c}, z/d
+#   Expected: y/{b,c}, z/d
+#   NOTE: Again, this seems obvious but just checking that the implementation
+#         doesn't "accidentally detect a rename" and give us y/{b,c,d}.
+
+test_expect_success '6d-setup: We do not always want transitive renaming' '
+       test_create_repo 6d &&
+       (
+               cd 6d &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_tick &&
+               git commit --allow-empty -m "A" &&
+
+               git checkout B &&
+               git mv z y &&
+               git mv x z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '6d-check: We do not always want transitive renaming' '
+       (
+               cd 6d &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:z/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:x/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 6e, Add/add from one-side
+#   Commit O: z/{b,c}
+#   Commit A: z/{b,c} (no change)
+#   Commit B: y/{b,c,d_1}, z/d_2
+#   Expected: y/{b,c,d_1}, z/d_2
+#   NOTE: Again, this seems obvious but just checking that the implementation
+#         doesn't "accidentally detect a rename" and give us y/{b,c} +
+#         add/add conflict on y/d_1 vs y/d_2.
+
+test_expect_success '6e-setup: Add/add from one side' '
+       test_create_repo 6e &&
+       (
+               cd 6e &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_tick &&
+               git commit --allow-empty -m "A" &&
+
+               git checkout B &&
+               git mv z y &&
+               echo d1 > y/d &&
+               mkdir z &&
+               echo d2 > z/d &&
+               git add y/d z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '6e-check: Add/add from one side' '
+       (
+               cd 6e &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d HEAD:z/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    B:y/d    B:z/d &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# Rules suggested by section 6:
+#
+#   Only apply implicit directory renames to directories if the other
+#   side of history is the one doing the renaming.
+###########################################################################
+
+
+###########################################################################
+# SECTION 7: More involved Edge/Corner cases
+#
+# The ruleset we have generated in the above sections seems to provide
+# well-defined merges.  But can we find edge/corner cases that either (a)
+# are harder for users to understand, or (b) have a resolution that is
+# non-intuitive or suboptimal?
+#
+# The testcases in this section dive into cases that I've tried to craft in
+# a way to find some that might be surprising to users or difficult for
+# them to understand (the next section will look at non-intuitive or
+# suboptimal merge results).  Some of the testcases are similar to ones
+# from past sections, but have been simplified to try to highlight error
+# messages using a "modified" path (due to the directory rename).  Are
+# users okay with these?
+#
+# In my opinion, testcases that are difficult to understand from this
+# section is due to difficulty in the testcase rather than the directory
+# renaming (similar to how t6042 and t6036 have difficult resolutions due
+# to the problem setup itself being complex).  And I don't think the
+# error messages are a problem.
+#
+# On the other hand, the testcases in section 8 worry me slightly more...
+###########################################################################
+
+# Testcase 7a, rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file
+#   Commit O: z/{b,c}
+#   Commit A: y/{b,c}
+#   Commit B: w/b, x/c, z/d
+#   Expected: y/d, CONFLICT(rename/rename for both z/b and z/c)
+#   NOTE: There's a rename of z/ here, y/ has more renames, so z/d -> y/d.
+
+test_expect_success '7a-setup: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' '
+       test_create_repo 7a &&
+       (
+               cd 7a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir w &&
+               mkdir x &&
+               git mv z/b w/ &&
+               git mv z/c x/ &&
+               echo d > z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '7a-check: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' '
+       (
+               cd 7a &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/rename).*z/b.*y/b.*w/b" out &&
+               test_i18ngrep "CONFLICT (rename/rename).*z/c.*y/c.*x/c" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 7 out &&
+               git ls-files -u >out &&
+               test_line_count = 6 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :1:z/b :2:y/b :3:w/b :1:z/c :2:y/c :3:x/c :0:y/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/b  O:z/b  O:z/c  O:z/c  O:z/c  B:z/d &&
+               test_cmp expect actual &&
+
+               git hash-object >actual \
+                       y/b   w/b   y/c   x/c &&
+               git rev-parse >expect \
+                       O:z/b O:z/b O:z/c O:z/c &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 7b, rename/rename(2to1), but only due to transitive rename
+#   (Related to testcase 1d)
+#   Commit O: z/{b,c},     x/d_1, w/d_2
+#   Commit A: y/{b,c,d_2}, x/d_1
+#   Commit B: z/{b,c,d_1},        w/d_2
+#   Expected: y/{b,c}, CONFLICT(rename/rename(2to1): x/d_1, w/d_2 -> y_d)
+
+test_expect_success '7b-setup: rename/rename(2to1), but only due to transitive rename' '
+       test_create_repo 7b &&
+       (
+               cd 7b &&
+
+               mkdir z &&
+               mkdir x &&
+               mkdir w &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d1 > x/d &&
+               echo d2 > w/d &&
+               git add z x w &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               git mv w/d y/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/ &&
+               rmdir x &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '7b-check: rename/rename(2to1), but only due to transitive rename' '
+       (
+               cd 7b &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/rename)" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :2:y/d :3:y/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  O:w/d  O:x/d &&
+               test_cmp expect actual &&
+
+               test_path_is_missing y/d &&
+               test_path_is_file y/d~HEAD &&
+               test_path_is_file y/d~B^0 &&
+
+               git hash-object >actual \
+                       y/d~HEAD y/d~B^0 &&
+               git rev-parse >expect \
+                       O:w/d    O:x/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 7c, rename/rename(1to...2or3); transitive rename may add complexity
+#   (Related to testcases 3b and 5c)
+#   Commit O: z/{b,c}, x/d
+#   Commit A: y/{b,c}, w/d
+#   Commit B: z/{b,c,d}
+#   Expected: y/{b,c}, CONFLICT(x/d -> w/d vs. y/d)
+#   NOTE: z/ was renamed to y/ so we do want to report
+#         neither CONFLICT(x/d -> w/d vs. z/d)
+#         nor CONFLiCT x/d -> w/d vs. y/d vs. z/d)
+
+test_expect_success '7c-setup: rename/rename(1to...2or3); transitive rename may add complexity' '
+       test_create_repo 7c &&
+       (
+               cd 7c &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               git mv x w &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/ &&
+               rmdir x &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '7c-check: rename/rename(1to...2or3); transitive rename may add complexity' '
+       (
+               cd 7c &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/rename).*x/d.*w/d.*y/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 5 out &&
+               git ls-files -u >out &&
+               test_line_count = 3 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :1:x/d :2:w/d :3:y/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  O:x/d  O:x/d  O:x/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 7d, transitive rename involved in rename/delete; how is it reported?
+#   (Related somewhat to testcases 5b and 8d)
+#   Commit O: z/{b,c}, x/d
+#   Commit A: y/{b,c}
+#   Commit B: z/{b,c,d}
+#   Expected: y/{b,c}, CONFLICT(delete x/d vs rename to y/d)
+#   NOTE: z->y so NOT CONFLICT(delete x/d vs rename to z/d)
+
+test_expect_success '7d-setup: transitive rename involved in rename/delete; how is it reported?' '
+       test_create_repo 7d &&
+       (
+               cd 7d &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               git rm -rf x &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/ &&
+               rmdir x &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '7d-check: transitive rename involved in rename/delete; how is it reported?' '
+       (
+               cd 7d &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/delete).*x/d.*y/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :3:y/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  O:x/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 7e, transitive rename in rename/delete AND dirs in the way
+#   (Very similar to 'both rename source and destination involved in D/F conflict' from t6022-merge-rename.sh)
+#   (Also related to testcases 9c and 9d)
+#   Commit O: z/{b,c},     x/d_1
+#   Commit A: y/{b,c,d/g}, x/d/f
+#   Commit B: z/{b,c,d_1}
+#   Expected: rename/delete(x/d_1->y/d_1 vs. None) + D/F conflict on y/d
+#             y/{b,c,d/g}, y/d_1~B^0, x/d/f
+
+#   NOTE: The main path of interest here is d_1 and where it ends up, but
+#         this is actually a case that has two potential directory renames
+#         involved and D/F conflict(s), so it makes sense to walk through
+#         each step.
+#
+#         Commit A renames z/ -> y/.  Thus everything that B adds to z/
+#         should be instead moved to y/.  This gives us the D/F conflict on
+#         y/d because x/d_1 -> z/d_1 -> y/d_1 conflicts with y/d/g.
+#
+#         Further, commit B renames x/ -> z/, thus everything A adds to x/
+#         should instead be moved to z/...BUT we removed z/ and renamed it
+#         to y/, so maybe everything should move not from x/ to z/, but
+#         from x/ to z/ to y/.  Doing so might make sense from the logic so
+#         far, but note that commit A had both an x/ and a y/; it did the
+#         renaming of z/ to y/ and created x/d/f and it clearly made these
+#         things separate, so it doesn't make much sense to push these
+#         together.  Doing so is what I'd call a doubly transitive rename;
+#         see testcases 9c and 9d for further discussion of this issue and
+#         how it's resolved.
+
+test_expect_success '7e-setup: transitive rename in rename/delete AND dirs in the way' '
+       test_create_repo 7e &&
+       (
+               cd 7e &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d1 >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               git rm x/d &&
+               mkdir -p x/d &&
+               mkdir -p y/d &&
+               echo f >x/d/f &&
+               echo g >y/d/g &&
+               git add x/d/f y/d/g &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/ &&
+               rmdir x &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '7e-check: transitive rename in rename/delete AND dirs in the way' '
+       (
+               cd 7e &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/delete).*x/d.*y/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 5 out &&
+               git ls-files -u >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 2 out &&
+
+               git rev-parse >actual \
+                       :0:x/d/f :0:y/d/g :0:y/b :0:y/c :3:y/d &&
+               git rev-parse >expect \
+                        A:x/d/f  A:y/d/g  O:z/b  O:z/c  O:x/d &&
+               test_cmp expect actual &&
+
+               git hash-object y/d~B^0 >actual &&
+               git rev-parse O:x/d >expect &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# SECTION 8: Suboptimal merges
+#
+# As alluded to in the last section, the ruleset we have built up for
+# detecting directory renames unfortunately has some special cases where it
+# results in slightly suboptimal or non-intuitive behavior.  This section
+# explores these cases.
+#
+# To be fair, we already had non-intuitive or suboptimal behavior for most
+# of these cases in git before introducing implicit directory rename
+# detection, but it'd be nice if there was a modified ruleset out there
+# that handled these cases a bit better.
+###########################################################################
+
+# Testcase 8a, Dual-directory rename, one into the others' way
+#   Commit O. x/{a,b},   y/{c,d}
+#   Commit A. x/{a,b,e}, y/{c,d,f}
+#   Commit B. y/{a,b},   z/{c,d}
+#
+# Possible Resolutions:
+#   w/o dir-rename detection: y/{a,b,f},   z/{c,d},   x/e
+#   Currently expected:       y/{a,b,e,f}, z/{c,d}
+#   Optimal:                  y/{a,b,e},   z/{c,d,f}
+#
+# Note: Both x and y got renamed and it'd be nice to detect both, and we do
+# better with directory rename detection than git did without, but the
+# simple rule from section 5 prevents me from handling this as optimally as
+# we potentially could.
+
+test_expect_success '8a-setup: Dual-directory rename, one into the others way' '
+       test_create_repo 8a &&
+       (
+               cd 8a &&
+
+               mkdir x &&
+               mkdir y &&
+               echo a >x/a &&
+               echo b >x/b &&
+               echo c >y/c &&
+               echo d >y/d &&
+               git add x y &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               echo e >x/e &&
+               echo f >y/f &&
+               git add x/e y/f &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv y z &&
+               git mv x y &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '8a-check: Dual-directory rename, one into the others way' '
+       (
+               cd 8a &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/a HEAD:y/b HEAD:y/e HEAD:y/f HEAD:z/c HEAD:z/d &&
+               git rev-parse >expect \
+                       O:x/a    O:x/b    A:x/e    A:y/f    O:y/c    O:y/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 8b, Dual-directory rename, one into the others' way, with conflicting filenames
+#   Commit O. x/{a_1,b_1},     y/{a_2,b_2}
+#   Commit A. x/{a_1,b_1,e_1}, y/{a_2,b_2,e_2}
+#   Commit B. y/{a_1,b_1},     z/{a_2,b_2}
+#
+#   w/o dir-rename detection: y/{a_1,b_1,e_2}, z/{a_2,b_2}, x/e_1
+#   Currently expected:       <same>
+#   Scary:                    y/{a_1,b_1},     z/{a_2,b_2}, CONFLICT(add/add, e_1 vs. e_2)
+#   Optimal:                  y/{a_1,b_1,e_1}, z/{a_2,b_2,e_2}
+#
+# Note: Very similar to 8a, except instead of 'e' and 'f' in directories x and
+# y, both are named 'e'.  Without directory rename detection, neither file
+# moves directories.  Implement directory rename detection suboptimally, and
+# you get an add/add conflict, but both files were added in commit A, so this
+# is an add/add conflict where one side of history added both files --
+# something we can't represent in the index.  Obviously, we'd prefer the last
+# resolution, but our previous rules are too coarse to allow it.  Using both
+# the rules from section 4 and section 5 save us from the Scary resolution,
+# making us fall back to pre-directory-rename-detection behavior for both
+# e_1 and e_2.
+
+test_expect_success '8b-setup: Dual-directory rename, one into the others way, with conflicting filenames' '
+       test_create_repo 8b &&
+       (
+               cd 8b &&
+
+               mkdir x &&
+               mkdir y &&
+               echo a1 >x/a &&
+               echo b1 >x/b &&
+               echo a2 >y/a &&
+               echo b2 >y/b &&
+               git add x y &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               echo e1 >x/e &&
+               echo e2 >y/e &&
+               git add x/e y/e &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv y z &&
+               git mv x y &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '8b-check: Dual-directory rename, one into the others way, with conflicting filenames' '
+       (
+               cd 8b &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/a HEAD:y/b HEAD:z/a HEAD:z/b HEAD:x/e HEAD:y/e &&
+               git rev-parse >expect \
+                       O:x/a    O:x/b    O:y/a    O:y/b    A:x/e    A:y/e &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 8c, modify/delete or rename+modify/delete?
+#   (Related to testcases 5b, 8d, and 9h)
+#   Commit O: z/{b,c,d}
+#   Commit A: y/{b,c}
+#   Commit B: z/{b,c,d_modified,e}
+#   Expected: y/{b,c,e}, CONFLICT(modify/delete: on z/d)
+#
+#   Note: It could easily be argued that the correct resolution here is
+#         y/{b,c,e}, CONFLICT(rename/delete: z/d -> y/d vs deleted)
+#         and that the modifed version of d should be present in y/ after
+#         the merge, just marked as conflicted.  Indeed, I previously did
+#         argue that.  But applying directory renames to the side of
+#         history where a file is merely modified results in spurious
+#         rename/rename(1to2) conflicts -- see testcase 9h.  See also
+#         notes in 8d.
+
+test_expect_success '8c-setup: modify/delete or rename+modify/delete?' '
+       test_create_repo 8c &&
+       (
+               cd 8c &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               test_seq 1 10 >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git rm z/d &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo 11 >z/d &&
+               test_chmod +x z/d &&
+               echo e >z/e &&
+               git add z/d z/e &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '8c-check: modify/delete or rename+modify/delete' '
+       (
+               cd 8c &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (modify/delete).* z/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 5 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :0:y/e :1:z/d :3:z/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  B:z/e  O:z/d  B:z/d &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse :2:z/d &&
+               git ls-files -s z/d | grep ^100755 &&
+               test_path_is_file z/d &&
+               test_path_is_missing y/d
+       )
+'
+
+# Testcase 8d, rename/delete...or not?
+#   (Related to testcase 5b; these may appear slightly inconsistent to users;
+#    Also related to testcases 7d and 7e)
+#   Commit O: z/{b,c,d}
+#   Commit A: y/{b,c}
+#   Commit B: z/{b,c,d,e}
+#   Expected: y/{b,c,e}
+#
+#   Note: It would also be somewhat reasonable to resolve this as
+#             y/{b,c,e}, CONFLICT(rename/delete: x/d -> y/d or deleted)
+#
+#   In this case, I'm leaning towards: commit A was the one that deleted z/d
+#   and it did the rename of z to y, so the two "conflicts" (rename vs.
+#   delete) are both coming from commit A, which is illogical.  Conflicts
+#   during merging are supposed to be about opposite sides doing things
+#   differently.
+
+test_expect_success '8d-setup: rename/delete...or not?' '
+       test_create_repo 8d &&
+       (
+               cd 8d &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               test_seq 1 10 >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git rm z/d &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo e >z/e &&
+               git add z/e &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '8d-check: rename/delete...or not?' '
+       (
+               cd 8d &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/e &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    B:z/e &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 8e, Both sides rename, one side adds to original directory
+#   Commit O: z/{b,c}
+#   Commit A: y/{b,c}
+#   Commit B: w/{b,c}, z/d
+#
+# Possible Resolutions:
+#   w/o dir-rename detection: z/d, CONFLICT(z/b -> y/b vs. w/b),
+#                                  CONFLICT(z/c -> y/c vs. w/c)
+#   Currently expected:       y/d, CONFLICT(z/b -> y/b vs. w/b),
+#                                  CONFLICT(z/c -> y/c vs. w/c)
+#   Optimal:                  ??
+#
+# Notes: In commit A, directory z got renamed to y.  In commit B, directory z
+#        did NOT get renamed; the directory is still present; instead it is
+#        considered to have just renamed a subset of paths in directory z
+#        elsewhere.  Therefore, the directory rename done in commit A to z/
+#        applies to z/d and maps it to y/d.
+#
+#        It's possible that users would get confused about this, but what
+#        should we do instead?  Silently leaving at z/d seems just as bad or
+#        maybe even worse.  Perhaps we could print a big warning about z/d
+#        and how we're moving to y/d in this case, but when I started thinking
+#        about the ramifications of doing that, I didn't know how to rule out
+#        that opening other weird edge and corner cases so I just punted.
+
+test_expect_success '8e-setup: Both sides rename, one side adds to original directory' '
+       test_create_repo 8e &&
+       (
+               cd 8e &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z w &&
+               mkdir z &&
+               echo d >z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '8e-check: Both sides rename, one side adds to original directory' '
+       (
+               cd 8e &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep CONFLICT.*rename/rename.*z/c.*y/c.*w/c out &&
+               test_i18ngrep CONFLICT.*rename/rename.*z/b.*y/b.*w/b out &&
+
+               git ls-files -s >out &&
+               test_line_count = 7 out &&
+               git ls-files -u >out &&
+               test_line_count = 6 out &&
+               git ls-files -o >out &&
+               test_line_count = 2 out &&
+
+               git rev-parse >actual \
+                       :1:z/b :2:y/b :3:w/b :1:z/c :2:y/c :3:w/c :0:y/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/b  O:z/b  O:z/c  O:z/c  O:z/c  B:z/d &&
+               test_cmp expect actual &&
+
+               git hash-object >actual \
+                       y/b   w/b   y/c   w/c &&
+               git rev-parse >expect \
+                       O:z/b O:z/b O:z/c O:z/c &&
+               test_cmp expect actual &&
+
+               test_path_is_missing z/b &&
+               test_path_is_missing z/c
+       )
+'
+
+###########################################################################
+# SECTION 9: Other testcases
+#
+# This section consists of miscellaneous testcases I thought of during
+# the implementation which round out the testing.
+###########################################################################
+
+# Testcase 9a, Inner renamed directory within outer renamed directory
+#   (Related to testcase 1f)
+#   Commit O: z/{b,c,d/{e,f,g}}
+#   Commit A: y/{b,c}, x/w/{e,f,g}
+#   Commit B: z/{b,c,d/{e,f,g,h},i}
+#   Expected: y/{b,c,i}, x/w/{e,f,g,h}
+#   NOTE: The only reason this one is interesting is because when a directory
+#         is split into multiple other directories, we determine by the weight
+#         of which one had the most paths going to it.  A naive implementation
+#         of that could take the new file in commit B at z/i to x/w/i or x/i.
+
+test_expect_success '9a-setup: Inner renamed directory within outer renamed directory' '
+       test_create_repo 9a &&
+       (
+               cd 9a &&
+
+               mkdir -p z/d &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo e >z/d/e &&
+               echo f >z/d/f &&
+               echo g >z/d/g &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir x &&
+               git mv z/d x/w &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo h >z/d/h &&
+               echo i >z/i &&
+               git add z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '9a-check: Inner renamed directory within outer renamed directory' '
+       (
+               cd 9a &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 7 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/i &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    B:z/i &&
+               test_cmp expect actual &&
+
+               git rev-parse >actual \
+                       HEAD:x/w/e HEAD:x/w/f HEAD:x/w/g HEAD:x/w/h &&
+               git rev-parse >expect \
+                       O:z/d/e    O:z/d/f    O:z/d/g    B:z/d/h &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 9b, Transitive rename with content merge
+#   (Related to testcase 1c)
+#   Commit O: z/{b,c},   x/d_1
+#   Commit A: y/{b,c},   x/d_2
+#   Commit B: z/{b,c,d_3}
+#   Expected: y/{b,c,d_merged}
+
+test_expect_success '9b-setup: Transitive rename with content merge' '
+       test_create_repo 9b &&
+       (
+               cd 9b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               test_seq 1 10 >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               test_seq 1 11 >x/d &&
+               git add x/d &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               test_seq 0 10 >x/d &&
+               git mv x/d z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '9b-check: Transitive rename with content merge' '
+       (
+               cd 9b &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+
+               test_seq 0 11 >expected &&
+               test_cmp expected y/d &&
+               git add expected &&
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    :0:expected &&
+               test_cmp expect actual &&
+               test_must_fail git rev-parse HEAD:x/d &&
+               test_must_fail git rev-parse HEAD:z/d &&
+               test_path_is_missing z/d &&
+
+               test $(git rev-parse HEAD:y/d) != $(git rev-parse O:x/d) &&
+               test $(git rev-parse HEAD:y/d) != $(git rev-parse A:x/d) &&
+               test $(git rev-parse HEAD:y/d) != $(git rev-parse B:z/d)
+       )
+'
+
+# Testcase 9c, Doubly transitive rename?
+#   (Related to testcase 1c, 7e, and 9d)
+#   Commit O: z/{b,c},     x/{d,e},    w/f
+#   Commit A: y/{b,c},     x/{d,e,f,g}
+#   Commit B: z/{b,c,d,e},             w/f
+#   Expected: y/{b,c,d,e}, x/{f,g}
+#
+#   NOTE: x/f and x/g may be slightly confusing here.  The rename from w/f to
+#         x/f is clear.  Let's look beyond that.  Here's the logic:
+#            Commit B renamed x/ -> z/
+#            Commit A renamed z/ -> y/
+#         So, we could possibly further rename x/f to z/f to y/f, a doubly
+#         transient rename.  However, where does it end?  We can chain these
+#         indefinitely (see testcase 9d).  What if there is a D/F conflict
+#         at z/f/ or y/f/?  Or just another file conflict at one of those
+#         paths?  In the case of an N-long chain of transient renamings,
+#         where do we "abort" the rename at?  Can the user make sense of
+#         the resulting conflict and resolve it?
+#
+#         To avoid this confusion I use the simple rule that if the other side
+#         of history did a directory rename to a path that your side renamed
+#         away, then ignore that particular rename from the other side of
+#         history for any implicit directory renames.
+
+test_expect_success '9c-setup: Doubly transitive rename?' '
+       test_create_repo 9c &&
+       (
+               cd 9c &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d >x/d &&
+               echo e >x/e &&
+               mkdir w &&
+               echo f >w/f &&
+               git add z x w &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               git mv w/f x/ &&
+               echo g >x/g &&
+               git add x/g &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/d &&
+               git mv x/e z/e &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '9c-check: Doubly transitive rename?' '
+       (
+               cd 9c &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 >out &&
+               test_i18ngrep "WARNING: Avoiding applying x -> z rename to x/f" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e HEAD:x/f HEAD:x/g &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:x/d    O:x/e    O:w/f    A:x/g &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 9d, N-fold transitive rename?
+#   (Related to testcase 9c...and 1c and 7e)
+#   Commit O: z/a, y/b, x/c, w/d, v/e, u/f
+#   Commit A:  y/{a,b},  w/{c,d},  u/{e,f}
+#   Commit B: z/{a,t}, x/{b,c}, v/{d,e}, u/f
+#   Expected: <see NOTE first>
+#
+#   NOTE: z/ -> y/ (in commit A)
+#         y/ -> x/ (in commit B)
+#         x/ -> w/ (in commit A)
+#         w/ -> v/ (in commit B)
+#         v/ -> u/ (in commit A)
+#         So, if we add a file to z, say z/t, where should it end up?  In u?
+#         What if there's another file or directory named 't' in one of the
+#         intervening directories and/or in u itself?  Also, shouldn't the
+#         same logic that places 't' in u/ also move ALL other files to u/?
+#         What if there are file or directory conflicts in any of them?  If
+#         we attempted to do N-way (N-fold? N-ary? N-uple?) transitive renames
+#         like this, would the user have any hope of understanding any
+#         conflicts or how their working tree ended up?  I think not, so I'm
+#         ruling out N-ary transitive renames for N>1.
+#
+#   Therefore our expected result is:
+#     z/t, y/a, x/b, w/c, u/d, u/e, u/f
+#   The reason that v/d DOES get transitively renamed to u/d is that u/ isn't
+#   renamed somewhere.  A slightly sub-optimal result, but it uses fairly
+#   simple rules that are consistent with what we need for all the other
+#   testcases and simplifies things for the user.
+
+test_expect_success '9d-setup: N-way transitive rename?' '
+       test_create_repo 9d &&
+       (
+               cd 9d &&
+
+               mkdir z y x w v u &&
+               echo a >z/a &&
+               echo b >y/b &&
+               echo c >x/c &&
+               echo d >w/d &&
+               echo e >v/e &&
+               echo f >u/f &&
+               git add z y x w v u &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z/a y/ &&
+               git mv x/c w/ &&
+               git mv v/e u/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo t >z/t &&
+               git mv y/b x/ &&
+               git mv w/d v/ &&
+               git add z/t &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '9d-check: N-way transitive rename?' '
+       (
+               cd 9d &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 >out &&
+               test_i18ngrep "WARNING: Avoiding applying z -> y rename to z/t" out &&
+               test_i18ngrep "WARNING: Avoiding applying y -> x rename to y/a" out &&
+               test_i18ngrep "WARNING: Avoiding applying x -> w rename to x/b" out &&
+               test_i18ngrep "WARNING: Avoiding applying w -> v rename to w/c" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 7 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:z/t \
+                       HEAD:y/a HEAD:x/b HEAD:w/c \
+                       HEAD:u/d HEAD:u/e HEAD:u/f &&
+               git rev-parse >expect \
+                       B:z/t    \
+                       O:z/a    O:y/b    O:x/c    \
+                       O:w/d    O:v/e    A:u/f &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 9e, N-to-1 whammo
+#   (Related to testcase 9c...and 1c and 7e)
+#   Commit O: dir1/{a,b}, dir2/{d,e}, dir3/{g,h}, dirN/{j,k}
+#   Commit A: dir1/{a,b,c,yo}, dir2/{d,e,f,yo}, dir3/{g,h,i,yo}, dirN/{j,k,l,yo}
+#   Commit B: combined/{a,b,d,e,g,h,j,k}
+#   Expected: combined/{a,b,c,d,e,f,g,h,i,j,k,l}, CONFLICT(Nto1) warnings,
+#             dir1/yo, dir2/yo, dir3/yo, dirN/yo
+
+test_expect_success '9e-setup: N-to-1 whammo' '
+       test_create_repo 9e &&
+       (
+               cd 9e &&
+
+               mkdir dir1 dir2 dir3 dirN &&
+               echo a >dir1/a &&
+               echo b >dir1/b &&
+               echo d >dir2/d &&
+               echo e >dir2/e &&
+               echo g >dir3/g &&
+               echo h >dir3/h &&
+               echo j >dirN/j &&
+               echo k >dirN/k &&
+               git add dir* &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               echo c  >dir1/c &&
+               echo yo >dir1/yo &&
+               echo f  >dir2/f &&
+               echo yo >dir2/yo &&
+               echo i  >dir3/i &&
+               echo yo >dir3/yo &&
+               echo l  >dirN/l &&
+               echo yo >dirN/yo &&
+               git add dir* &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv dir1 combined &&
+               git mv dir2/* combined/ &&
+               git mv dir3/* combined/ &&
+               git mv dirN/* combined/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success C_LOCALE_OUTPUT '9e-check: N-to-1 whammo' '
+       (
+               cd 9e &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               grep "CONFLICT (implicit dir rename): Cannot map more than one path to combined/yo" out >error_line &&
+               grep -q dir1/yo error_line &&
+               grep -q dir2/yo error_line &&
+               grep -q dir3/yo error_line &&
+               grep -q dirN/yo error_line &&
+
+               git ls-files -s >out &&
+               test_line_count = 16 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 2 out &&
+
+               git rev-parse >actual \
+                       :0:combined/a :0:combined/b :0:combined/c \
+                       :0:combined/d :0:combined/e :0:combined/f \
+                       :0:combined/g :0:combined/h :0:combined/i \
+                       :0:combined/j :0:combined/k :0:combined/l &&
+               git rev-parse >expect \
+                        O:dir1/a      O:dir1/b      A:dir1/c \
+                        O:dir2/d      O:dir2/e      A:dir2/f \
+                        O:dir3/g      O:dir3/h      A:dir3/i \
+                        O:dirN/j      O:dirN/k      A:dirN/l &&
+               test_cmp expect actual &&
+
+               git rev-parse >actual \
+                       :0:dir1/yo :0:dir2/yo :0:dir3/yo :0:dirN/yo &&
+               git rev-parse >expect \
+                        A:dir1/yo  A:dir2/yo  A:dir3/yo  A:dirN/yo &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 9f, Renamed directory that only contained immediate subdirs
+#   (Related to testcases 1e & 9g)
+#   Commit O: goal/{a,b}/$more_files
+#   Commit A: priority/{a,b}/$more_files
+#   Commit B: goal/{a,b}/$more_files, goal/c
+#   Expected: priority/{a,b}/$more_files, priority/c
+
+test_expect_success '9f-setup: Renamed directory that only contained immediate subdirs' '
+       test_create_repo 9f &&
+       (
+               cd 9f &&
+
+               mkdir -p goal/a &&
+               mkdir -p goal/b &&
+               echo foo >goal/a/foo &&
+               echo bar >goal/b/bar &&
+               echo baz >goal/b/baz &&
+               git add goal &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv goal/ priority &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo c >goal/c &&
+               git add goal/c &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '9f-check: Renamed directory that only contained immediate subdirs' '
+       (
+               cd 9f &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       HEAD:priority/a/foo \
+                       HEAD:priority/b/bar \
+                       HEAD:priority/b/baz \
+                       HEAD:priority/c &&
+               git rev-parse >expect \
+                       O:goal/a/foo \
+                       O:goal/b/bar \
+                       O:goal/b/baz \
+                       B:goal/c &&
+               test_cmp expect actual &&
+               test_must_fail git rev-parse HEAD:goal/c
+       )
+'
+
+# Testcase 9g, Renamed directory that only contained immediate subdirs, immediate subdirs renamed
+#   (Related to testcases 1e & 9f)
+#   Commit O: goal/{a,b}/$more_files
+#   Commit A: priority/{alpha,bravo}/$more_files
+#   Commit B: goal/{a,b}/$more_files, goal/c
+#   Expected: priority/{alpha,bravo}/$more_files, priority/c
+
+test_expect_success '9g-setup: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' '
+       test_create_repo 9g &&
+       (
+               cd 9g &&
+
+               mkdir -p goal/a &&
+               mkdir -p goal/b &&
+               echo foo >goal/a/foo &&
+               echo bar >goal/b/bar &&
+               echo baz >goal/b/baz &&
+               git add goal &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir priority &&
+               git mv goal/a/ priority/alpha &&
+               git mv goal/b/ priority/beta &&
+               rmdir goal/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo c >goal/c &&
+               git add goal/c &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_failure '9g-check: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' '
+       (
+               cd 9g &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       HEAD:priority/alpha/foo \
+                       HEAD:priority/beta/bar  \
+                       HEAD:priority/beta/baz  \
+                       HEAD:priority/c &&
+               git rev-parse >expect \
+                       O:goal/a/foo \
+                       O:goal/b/bar \
+                       O:goal/b/baz \
+                       B:goal/c &&
+               test_cmp expect actual &&
+               test_must_fail git rev-parse HEAD:goal/c
+       )
+'
+
+# Testcase 9h, Avoid implicit rename if involved as source on other side
+#   (Extremely closely related to testcase 3a)
+#   Commit O: z/{b,c,d_1}
+#   Commit A: z/{b,c,d_2}
+#   Commit B: y/{b,c}, x/d_1
+#   Expected: y/{b,c}, x/d_2
+#   NOTE: If we applied the z/ -> y/ rename to z/d, then we'd end up with
+#         a rename/rename(1to2) conflict (z/d -> y/d vs. x/d)
+test_expect_success '9h-setup: Avoid dir rename on merely modified path' '
+       test_create_repo 9h &&
+       (
+               cd 9h &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nd\n" >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_tick &&
+               echo more >>z/d &&
+               git add z/d &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir y &&
+               mkdir x &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               git mv z/d x/ &&
+               rmdir z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '9h-check: Avoid dir rename on merely modified path' '
+       (
+               cd 9h &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:x/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    A:z/d &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# Rules suggested by section 9:
+#
+#   If the other side of history did a directory rename to a path that your
+#   side renamed away, then ignore that particular rename from the other
+#   side of history for any implicit directory renames.
+###########################################################################
+
+###########################################################################
+# SECTION 10: Handling untracked files
+#
+# unpack_trees(), upon which the recursive merge algorithm is based, aborts
+# the operation if untracked or dirty files would be deleted or overwritten
+# by the merge.  Unfortunately, unpack_trees() does not understand renames,
+# and if it doesn't abort, then it muddies up the working directory before
+# we even get to the point of detecting renames, so we need some special
+# handling, at least in the case of directory renames.
+###########################################################################
+
+# Testcase 10a, Overwrite untracked: normal rename/delete
+#   Commit O: z/{b,c_1}
+#   Commit A: z/b + untracked z/c + untracked z/d
+#   Commit B: z/{b,d_1}
+#   Expected: Aborted Merge +
+#       ERROR_MSG(untracked working tree files would be overwritten by merge)
+
+test_expect_success '10a-setup: Overwrite untracked with normal rename/delete' '
+       test_create_repo 10a &&
+       (
+               cd 10a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git rm z/c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z/c z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '10a-check: Overwrite untracked with normal rename/delete' '
+       (
+               cd 10a &&
+
+               git checkout A^0 &&
+               echo very >z/c &&
+               echo important >z/d &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "The following untracked working tree files would be overwritten by merge" err &&
+
+               git ls-files -s >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 4 out &&
+
+               echo very >expect &&
+               test_cmp expect z/c &&
+
+               echo important >expect &&
+               test_cmp expect z/d &&
+
+               git rev-parse HEAD:z/b >actual &&
+               git rev-parse O:z/b >expect &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 10b, Overwrite untracked: dir rename + delete
+#   Commit O: z/{b,c_1}
+#   Commit A: y/b + untracked y/{c,d,e}
+#   Commit B: z/{b,d_1,e}
+#   Expected: Failed Merge; y/b + untracked y/c + untracked y/d on disk +
+#             z/c_1 -> z/d_1 rename recorded at stage 3 for y/d +
+#       ERROR_MSG(refusing to lose untracked file at 'y/d')
+
+test_expect_success '10b-setup: Overwrite untracked with dir rename + delete' '
+       test_create_repo 10b &&
+       (
+               cd 10b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git rm z/c &&
+               git mv z/ y/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z/c z/d &&
+               echo e >z/e &&
+               git add z/e &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '10b-check: Overwrite untracked with dir rename + delete' '
+       (
+               cd 10b &&
+
+               git checkout A^0 &&
+               echo very >y/c &&
+               echo important >y/d &&
+               echo contents >y/e &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "CONFLICT (rename/delete).*Version B\^0 of y/d left in tree at y/d~B\^0" out &&
+               test_i18ngrep "Error: Refusing to lose untracked file at y/e; writing to y/e~B\^0 instead" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 5 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :3:y/d :3:y/e &&
+               git rev-parse >expect \
+                       O:z/b  O:z/c  B:z/e &&
+               test_cmp expect actual &&
+
+               echo very >expect &&
+               test_cmp expect y/c &&
+
+               echo important >expect &&
+               test_cmp expect y/d &&
+
+               echo contents >expect &&
+               test_cmp expect y/e
+       )
+'
+
+# Testcase 10c, Overwrite untracked: dir rename/rename(1to2)
+#   Commit O: z/{a,b}, x/{c,d}
+#   Commit A: y/{a,b}, w/c, x/d + different untracked y/c
+#   Commit B: z/{a,b,c}, x/d
+#   Expected: Failed Merge; y/{a,b} + x/d + untracked y/c +
+#             CONFLICT(rename/rename) x/c -> w/c vs y/c +
+#             y/c~B^0 +
+#             ERROR_MSG(Refusing to lose untracked file at y/c)
+
+test_expect_success '10c-setup: Overwrite untracked with dir rename/rename(1to2)' '
+       test_create_repo 10c &&
+       (
+               cd 10c &&
+
+               mkdir z x &&
+               echo a >z/a &&
+               echo b >z/b &&
+               echo c >x/c &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir w &&
+               git mv x/c w/c &&
+               git mv z/ y/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/c z/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)' '
+       (
+               cd 10c &&
+
+               git checkout A^0 &&
+               echo important >y/c &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "CONFLICT (rename/rename)" out &&
+               test_i18ngrep "Refusing to lose untracked file at y/c; adding as y/c~B\^0 instead" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -u >out &&
+               test_line_count = 3 out &&
+               git ls-files -o >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       :0:y/a :0:y/b :0:x/d :1:x/c :2:w/c :3:y/c &&
+               git rev-parse >expect \
+                        O:z/a  O:z/b  O:x/d  O:x/c  O:x/c  O:x/c &&
+               test_cmp expect actual &&
+
+               git hash-object y/c~B^0 >actual &&
+               git rev-parse O:x/c >expect &&
+               test_cmp expect actual &&
+
+               echo important >expect &&
+               test_cmp expect y/c
+       )
+'
+
+# Testcase 10d, Delete untracked w/ dir rename/rename(2to1)
+#   Commit O: z/{a,b,c_1},        x/{d,e,f_2}
+#   Commit A: y/{a,b},            x/{d,e,f_2,wham_1} + untracked y/wham
+#   Commit B: z/{a,b,c_1,wham_2}, y/{d,e}
+#   Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~B^0,wham~HEAD}+
+#             CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham
+#             ERROR_MSG(Refusing to lose untracked file at y/wham)
+
+test_expect_success '10d-setup: Delete untracked with dir rename/rename(2to1)' '
+       test_create_repo 10d &&
+       (
+               cd 10d &&
+
+               mkdir z x &&
+               echo a >z/a &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d >x/d &&
+               echo e >x/e &&
+               echo f >x/f &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z/c x/wham &&
+               git mv z/ y/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/f z/wham &&
+               git mv x/ y/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '
+       (
+               cd 10d &&
+
+               git checkout A^0 &&
+               echo important >y/wham &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "CONFLICT (rename/rename)" out &&
+               test_i18ngrep "Refusing to lose untracked file at y/wham" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       :0:y/a :0:y/b :0:y/d :0:y/e :2:y/wham :3:y/wham &&
+               git rev-parse >expect \
+                        O:z/a  O:z/b  O:x/d  O:x/e  O:z/c     O:x/f &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse :1:y/wham &&
+
+               echo important >expect &&
+               test_cmp expect y/wham &&
+
+               git hash-object >actual \
+                       y/wham~B^0 y/wham~HEAD &&
+               git rev-parse >expect \
+                       O:x/f      O:z/c &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 10e, Does git complain about untracked file that's not in the way?
+#   Commit O: z/{a,b}
+#   Commit A: y/{a,b} + untracked z/c
+#   Commit B: z/{a,b,c}
+#   Expected: y/{a,b,c} + untracked z/c
+
+test_expect_success '10e-setup: Does git complain about untracked file that is not really in the way?' '
+       test_create_repo 10e &&
+       (
+               cd 10e &&
+
+               mkdir z &&
+               echo a >z/a &&
+               echo b >z/b &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z/ y/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo c >z/c &&
+               git add z/c &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_failure '10e-check: Does git complain about untracked file that is not really in the way?' '
+       (
+               cd 10e &&
+
+               git checkout A^0 &&
+               mkdir z &&
+               echo random >z/c &&
+
+               git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep ! "following untracked working tree files would be overwritten by merge" err &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       :0:y/a :0:y/b :0:y/c &&
+               git rev-parse >expect \
+                        O:z/a  O:z/b  B:z/c &&
+               test_cmp expect actual &&
+
+               echo random >expect &&
+               test_cmp expect z/c
+       )
+'
+
+###########################################################################
+# SECTION 11: Handling dirty (not up-to-date) files
+#
+# unpack_trees(), upon which the recursive merge algorithm is based, aborts
+# the operation if untracked or dirty files would be deleted or overwritten
+# by the merge.  Unfortunately, unpack_trees() does not understand renames,
+# and if it doesn't abort, then it muddies up the working directory before
+# we even get to the point of detecting renames, so we need some special
+# handling.  This was true even of normal renames, but there are additional
+# codepaths that need special handling with directory renames.  Add
+# testcases for both renamed-by-directory-rename-detection and standard
+# rename cases.
+###########################################################################
+
+# Testcase 11a, Avoid losing dirty contents with simple rename
+#   Commit O: z/{a,b_v1},
+#   Commit A: z/{a,c_v1}, and z/c_v1 has uncommitted mods
+#   Commit B: z/{a,b_v2}
+#   Expected: ERROR_MSG(Refusing to lose dirty file at z/c) +
+#             z/a, staged version of z/c has sha1sum matching B:z/b_v2,
+#             z/c~HEAD with contents of B:z/b_v2,
+#             z/c with uncommitted mods on top of A:z/c_v1
+
+test_expect_success '11a-setup: Avoid losing dirty contents with simple rename' '
+       test_create_repo 11a &&
+       (
+               cd 11a &&
+
+               mkdir z &&
+               echo a >z/a &&
+               test_seq 1 10 >z/b &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z/b z/c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo 11 >>z/b &&
+               git add z/b &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '11a-check: Avoid losing dirty contents with simple rename' '
+       (
+               cd 11a &&
+
+               git checkout A^0 &&
+               echo stuff >>z/c &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "Refusing to lose dirty file at z/c" out &&
+
+               test_seq 1 10 >expected &&
+               echo stuff >>expected &&
+               test_cmp expected z/c &&
+
+               git ls-files -s >out &&
+               test_line_count = 2 out &&
+               git ls-files -u >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       :0:z/a :2:z/c &&
+               git rev-parse >expect \
+                        O:z/a  B:z/b &&
+               test_cmp expect actual &&
+
+               git hash-object z/c~HEAD >actual &&
+               git rev-parse B:z/b >expect &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 11b, Avoid losing dirty file involved in directory rename
+#   Commit O: z/a,         x/{b,c_v1}
+#   Commit A: z/{a,c_v1},  x/b,       and z/c_v1 has uncommitted mods
+#   Commit B: y/a,         x/{b,c_v2}
+#   Expected: y/{a,c_v2}, x/b, z/c_v1 with uncommitted mods untracked,
+#             ERROR_MSG(Refusing to lose dirty file at z/c)
+
+
+test_expect_success '11b-setup: Avoid losing dirty file involved in directory rename' '
+       test_create_repo 11b &&
+       (
+               cd 11b &&
+
+               mkdir z x &&
+               echo a >z/a &&
+               echo b >x/b &&
+               test_seq 1 10 >x/c &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv x/c z/c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z y &&
+               echo 11 >>x/c &&
+               git add x/c &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '11b-check: Avoid losing dirty file involved in directory rename' '
+       (
+               cd 11b &&
+
+               git checkout A^0 &&
+               echo stuff >>z/c &&
+
+               git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "Refusing to lose dirty file at z/c" out &&
+
+               grep -q stuff z/c &&
+               test_seq 1 10 >expected &&
+               echo stuff >>expected &&
+               test_cmp expected z/c &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -m >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       :0:x/b :0:y/a :0:y/c &&
+               git rev-parse >expect \
+                        O:x/b  O:z/a  B:x/c &&
+               test_cmp expect actual &&
+
+               git hash-object y/c >actual &&
+               git rev-parse B:x/c >expect &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 11c, Avoid losing not-up-to-date with rename + D/F conflict
+#   Commit O: y/a,         x/{b,c_v1}
+#   Commit A: y/{a,c_v1},  x/b,       and y/c_v1 has uncommitted mods
+#   Commit B: y/{a,c/d},   x/{b,c_v2}
+#   Expected: Abort_msg("following files would be overwritten by merge") +
+#             y/c left untouched (still has uncommitted mods)
+
+test_expect_success '11c-setup: Avoid losing not-uptodate with rename + D/F conflict' '
+       test_create_repo 11c &&
+       (
+               cd 11c &&
+
+               mkdir y x &&
+               echo a >y/a &&
+               echo b >x/b &&
+               test_seq 1 10 >x/c &&
+               git add y x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv x/c y/c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir y/c &&
+               echo d >y/c/d &&
+               echo 11 >>x/c &&
+               git add x/c y/c/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '11c-check: Avoid losing not-uptodate with rename + D/F conflict' '
+       (
+               cd 11c &&
+
+               git checkout A^0 &&
+               echo stuff >>y/c &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "following files would be overwritten by merge" err &&
+
+               grep -q stuff y/c &&
+               test_seq 1 10 >expected &&
+               echo stuff >>expected &&
+               test_cmp expected y/c &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -m >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 3 out
+       )
+'
+
+# Testcase 11d, Avoid losing not-up-to-date with rename + D/F conflict
+#   Commit O: z/a,         x/{b,c_v1}
+#   Commit A: z/{a,c_v1},  x/b,       and z/c_v1 has uncommitted mods
+#   Commit B: y/{a,c/d},   x/{b,c_v2}
+#   Expected: D/F: y/c_v2 vs y/c/d) +
+#             Warning_Msg("Refusing to lose dirty file at z/c) +
+#             y/{a,c~HEAD,c/d}, x/b, now-untracked z/c_v1 with uncommitted mods
+
+test_expect_success '11d-setup: Avoid losing not-uptodate with rename + D/F conflict' '
+       test_create_repo 11d &&
+       (
+               cd 11d &&
+
+               mkdir z x &&
+               echo a >z/a &&
+               echo b >x/b &&
+               test_seq 1 10 >x/c &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv x/c z/c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z y &&
+               mkdir y/c &&
+               echo d >y/c/d &&
+               echo 11 >>x/c &&
+               git add x/c y/c/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '11d-check: Avoid losing not-uptodate with rename + D/F conflict' '
+       (
+               cd 11d &&
+
+               git checkout A^0 &&
+               echo stuff >>z/c &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "Refusing to lose dirty file at z/c" out &&
+
+               grep -q stuff z/c &&
+               test_seq 1 10 >expected &&
+               echo stuff >>expected &&
+               test_cmp expected z/c
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+               git ls-files -u >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 5 out &&
+
+               git rev-parse >actual \
+                       :0:x/b :0:y/a :0:y/c/d :3:y/c &&
+               git rev-parse >expect \
+                        O:x/b  O:z/a  B:y/c/d  B:x/c &&
+               test_cmp expect actual &&
+
+               git hash-object y/c~HEAD >actual &&
+               git rev-parse B:x/c >expect &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 11e, Avoid deleting not-up-to-date with dir rename/rename(1to2)/add
+#   Commit O: z/{a,b},      x/{c_1,d}
+#   Commit A: y/{a,b,c_2},  x/d, w/c_1, and y/c_2 has uncommitted mods
+#   Commit B: z/{a,b,c_1},  x/d
+#   Expected: Failed Merge; y/{a,b} + x/d +
+#             CONFLICT(rename/rename) x/c_1 -> w/c_1 vs y/c_1 +
+#             ERROR_MSG(Refusing to lose dirty file at y/c)
+#             y/c~B^0 has O:x/c_1 contents
+#             y/c~HEAD has A:y/c_2 contents
+#             y/c has dirty file from before merge
+
+test_expect_success '11e-setup: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' '
+       test_create_repo 11e &&
+       (
+               cd 11e &&
+
+               mkdir z x &&
+               echo a >z/a &&
+               echo b >z/b &&
+               echo c >x/c &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z/ y/ &&
+               echo different >y/c &&
+               mkdir w &&
+               git mv x/c w/ &&
+               git add y/c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/c z/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' '
+       (
+               cd 11e &&
+
+               git checkout A^0 &&
+               echo mods >>y/c &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "CONFLICT (rename/rename)" out &&
+               test_i18ngrep "Refusing to lose dirty file at y/c" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 7 out &&
+               git ls-files -u >out &&
+               test_line_count = 4 out &&
+               git ls-files -o >out &&
+               test_line_count = 4 out &&
+
+               echo different >expected &&
+               echo mods >>expected &&
+               test_cmp expected y/c &&
+
+               git rev-parse >actual \
+                       :0:y/a :0:y/b :0:x/d :1:x/c :2:w/c :2:y/c :3:y/c &&
+               git rev-parse >expect \
+                        O:z/a  O:z/b  O:x/d  O:x/c  O:x/c  A:y/c  O:x/c &&
+               test_cmp expect actual &&
+
+               git hash-object >actual \
+                       y/c~B^0 y/c~HEAD &&
+               git rev-parse >expect \
+                       O:x/c   A:y/c &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 11f, Avoid deleting not-up-to-date w/ dir rename/rename(2to1)
+#   Commit O: z/{a,b},        x/{c_1,d_2}
+#   Commit A: y/{a,b,wham_1}, x/d_2, except y/wham has uncommitted mods
+#   Commit B: z/{a,b,wham_2}, x/c_1
+#   Expected: Failed Merge; y/{a,b} + untracked y/{wham~B^0,wham~B^HEAD} +
+#             y/wham with dirty changes from before merge +
+#             CONFLICT(rename/rename) x/c vs x/d -> y/wham
+#             ERROR_MSG(Refusing to lose dirty file at y/wham)
+
+test_expect_success '11f-setup: Avoid deleting not-uptodate with dir rename/rename(2to1)' '
+       test_create_repo 11f &&
+       (
+               cd 11f &&
+
+               mkdir z x &&
+               echo a >z/a &&
+               echo b >z/b &&
+               test_seq 1 10 >x/c &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z/ y/ &&
+               git mv x/c y/wham &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/wham &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rename(2to1)' '
+       (
+               cd 11f &&
+
+               git checkout A^0 &&
+               echo important >>y/wham &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "CONFLICT (rename/rename)" out &&
+               test_i18ngrep "Refusing to lose dirty file at y/wham" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 4 out &&
+
+               test_seq 1 10 >expected &&
+               echo important >>expected &&
+               test_cmp expected y/wham &&
+
+               test_must_fail git rev-parse :1:y/wham &&
+               git hash-object >actual \
+                       y/wham~B^0 y/wham~HEAD &&
+               git rev-parse >expect \
+                       O:x/d      O:x/c &&
+               test_cmp expect actual &&
+
+               git rev-parse >actual \
+                       :0:y/a :0:y/b :2:y/wham :3:y/wham &&
+               git rev-parse >expect \
+                        O:z/a  O:z/b  O:x/c     O:x/d &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# SECTION 12: Everything else
+#
+# Tests suggested by others.  Tests added after implementation completed
+# and submitted.  Grab bag.
+###########################################################################
+
+# Testcase 12a, Moving one directory hierarchy into another
+#   (Related to testcase 9a)
+#   Commit O: node1/{leaf1,leaf2}, node2/{leaf3,leaf4}
+#   Commit A: node1/{leaf1,leaf2,node2/{leaf3,leaf4}}
+#   Commit B: node1/{leaf1,leaf2,leaf5}, node2/{leaf3,leaf4,leaf6}
+#   Expected: node1/{leaf1,leaf2,leaf5,node2/{leaf3,leaf4,leaf6}}
+
+test_expect_success '12a-setup: Moving one directory hierarchy into another' '
+       test_create_repo 12a &&
+       (
+               cd 12a &&
+
+               mkdir -p node1 node2 &&
+               echo leaf1 >node1/leaf1 &&
+               echo leaf2 >node1/leaf2 &&
+               echo leaf3 >node2/leaf3 &&
+               echo leaf4 >node2/leaf4 &&
+               git add node1 node2 &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv node2/ node1/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo leaf5 >node1/leaf5 &&
+               echo leaf6 >node2/leaf6 &&
+               git add node1 node2 &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '12a-check: Moving one directory hierarchy into another' '
+       (
+               cd 12a &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+
+               git rev-parse >actual \
+                       HEAD:node1/leaf1 HEAD:node1/leaf2 HEAD:node1/leaf5 \
+                       HEAD:node1/node2/leaf3 \
+                       HEAD:node1/node2/leaf4 \
+                       HEAD:node1/node2/leaf6 &&
+               git rev-parse >expect \
+                       O:node1/leaf1    O:node1/leaf2    B:node1/leaf5 \
+                       O:node2/leaf3 \
+                       O:node2/leaf4 \
+                       B:node2/leaf6 &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 12b, Moving two directory hierarchies into each other
+#   (Related to testcases 1c and 12c)
+#   Commit O: node1/{leaf1, leaf2}, node2/{leaf3, leaf4}
+#   Commit A: node1/{leaf1, leaf2, node2/{leaf3, leaf4}}
+#   Commit B: node2/{leaf3, leaf4, node1/{leaf1, leaf2}}
+#   Expected: node1/node2/node1/{leaf1, leaf2},
+#             node2/node1/node2/{leaf3, leaf4}
+#   NOTE: Without directory renames, we would expect
+#                   node2/node1/{leaf1, leaf2},
+#                   node1/node2/{leaf3, leaf4}
+#         with directory rename detection, we note that
+#             commit A renames node2/ -> node1/node2/
+#             commit B renames node1/ -> node2/node1/
+#         therefore, applying those directory renames to the initial result
+#         (making all four paths experience a transitive renaming), yields
+#         the expected result.
+#
+#         You may ask, is it weird to have two directories rename each other?
+#         To which, I can do no more than shrug my shoulders and say that
+#         even simple rules give weird results when given weird inputs.
+
+test_expect_success '12b-setup: Moving one directory hierarchy into another' '
+       test_create_repo 12b &&
+       (
+               cd 12b &&
+
+               mkdir -p node1 node2 &&
+               echo leaf1 >node1/leaf1 &&
+               echo leaf2 >node1/leaf2 &&
+               echo leaf3 >node2/leaf3 &&
+               echo leaf4 >node2/leaf4 &&
+               git add node1 node2 &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv node2/ node1/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv node1/ node2/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '12b-check: Moving one directory hierarchy into another' '
+       (
+               cd 12b &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       HEAD:node1/node2/node1/leaf1 \
+                       HEAD:node1/node2/node1/leaf2 \
+                       HEAD:node2/node1/node2/leaf3 \
+                       HEAD:node2/node1/node2/leaf4 &&
+               git rev-parse >expect \
+                       O:node1/leaf1 \
+                       O:node1/leaf2 \
+                       O:node2/leaf3 \
+                       O:node2/leaf4 &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 12c, Moving two directory hierarchies into each other w/ content merge
+#   (Related to testcase 12b)
+#   Commit O: node1/{       leaf1_1, leaf2_1}, node2/{leaf3_1, leaf4_1}
+#   Commit A: node1/{       leaf1_2, leaf2_2,  node2/{leaf3_2, leaf4_2}}
+#   Commit B: node2/{node1/{leaf1_3, leaf2_3},        leaf3_3, leaf4_3}
+#   Expected: Content merge conflicts for each of:
+#               node1/node2/node1/{leaf1, leaf2},
+#               node2/node1/node2/{leaf3, leaf4}
+#   NOTE: This is *exactly* like 12c, except that every path is modified on
+#         each side of the merge.
+
+test_expect_success '12c-setup: Moving one directory hierarchy into another w/ content merge' '
+       test_create_repo 12c &&
+       (
+               cd 12c &&
+
+               mkdir -p node1 node2 &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf1\n" >node1/leaf1 &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf2\n" >node1/leaf2 &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf3\n" >node2/leaf3 &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf4\n" >node2/leaf4 &&
+               git add node1 node2 &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv node2/ node1/ &&
+               for i in `git ls-files`; do echo side A >>$i; done &&
+               git add -u &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv node1/ node2/ &&
+               for i in `git ls-files`; do echo side B >>$i; done &&
+               git add -u &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '12c-check: Moving one directory hierarchy into another w/ content merge' '
+       (
+               cd 12c &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 &&
+
+               git ls-files -u >out &&
+               test_line_count = 12 out &&
+
+               git rev-parse >actual \
+                       :1:node1/node2/node1/leaf1 \
+                       :1:node1/node2/node1/leaf2 \
+                       :1:node2/node1/node2/leaf3 \
+                       :1:node2/node1/node2/leaf4 \
+                       :2:node1/node2/node1/leaf1 \
+                       :2:node1/node2/node1/leaf2 \
+                       :2:node2/node1/node2/leaf3 \
+                       :2:node2/node1/node2/leaf4 \
+                       :3:node1/node2/node1/leaf1 \
+                       :3:node1/node2/node1/leaf2 \
+                       :3:node2/node1/node2/leaf3 \
+                       :3:node2/node1/node2/leaf4 &&
+               git rev-parse >expect \
+                       O:node1/leaf1 \
+                       O:node1/leaf2 \
+                       O:node2/leaf3 \
+                       O:node2/leaf4 \
+                       A:node1/leaf1 \
+                       A:node1/leaf2 \
+                       A:node1/node2/leaf3 \
+                       A:node1/node2/leaf4 \
+                       B:node2/node1/leaf1 \
+                       B:node2/node1/leaf2 \
+                       B:node2/leaf3 \
+                       B:node2/leaf4 &&
+               test_cmp expect actual
+       )
+'
+
+test_done
index f5f46a95b4be9bca4ca30e70cbe75051a191c96a..7541ba5edbcae1f1555c367b7f8bfc795913ff60 100755 (executable)
@@ -110,13 +110,6 @@ test_expect_success TTY 'configuration can disable pager' '
        ! test -e paginated.out
 '
 
-test_expect_success TTY 'git config uses a pager if configured to' '
-       rm -f paginated.out &&
-       test_config pager.config true &&
-       test_terminal git config --list &&
-       test -e paginated.out
-'
-
 test_expect_success TTY 'configuration can enable pager (from subdir)' '
        rm -f paginated.out &&
        mkdir -p subdir &&
@@ -252,6 +245,48 @@ test_expect_success TTY 'git branch --set-upstream-to ignores pager.branch' '
        ! test -e paginated.out
 '
 
+test_expect_success TTY 'git config ignores pager.config when setting' '
+       rm -f paginated.out &&
+       test_terminal git -c pager.config config foo.bar bar &&
+       ! test -e paginated.out
+'
+
+test_expect_success TTY 'git config --edit ignores pager.config' '
+       rm -f paginated.out editor.used &&
+       write_script editor <<-\EOF &&
+               touch editor.used
+       EOF
+       EDITOR=./editor test_terminal git -c pager.config config --edit &&
+       ! test -e paginated.out &&
+       test -e editor.used
+'
+
+test_expect_success TTY 'git config --get ignores pager.config' '
+       rm -f paginated.out &&
+       test_terminal git -c pager.config config --get foo.bar &&
+       ! test -e paginated.out
+'
+
+test_expect_success TTY 'git config --get-urlmatch defaults to paging' '
+       rm -f paginated.out &&
+       test_terminal git -c http."https://foo.com/".bar=foo \
+                         config --get-urlmatch http https://foo.com &&
+       test -e paginated.out
+'
+
+test_expect_success TTY 'git config --get-all respects pager.config' '
+       rm -f paginated.out &&
+       test_terminal git -c pager.config=false config --get-all foo.bar &&
+       ! test -e paginated.out
+'
+
+test_expect_success TTY 'git config --list defaults to paging' '
+       rm -f paginated.out &&
+       test_terminal git config --list &&
+       test -e paginated.out
+'
+
+
 # A colored commit log will begin with an appropriate ANSI escape
 # for the first color; the text "commit" comes later.
 colorful() {
index 9c422bcd7cc8c1b2525579ca3a7ffa6ee412ba42..dd8ab7ede182fc3c3da840fee083bf23a94ce13c 100755 (executable)
@@ -92,7 +92,7 @@ test_expect_success 'will not overwrite removed file with staged changes' '
        test_cmp important c1.c
 '
 
-test_expect_failure 'will not overwrite unstaged changes in renamed file' '
+test_expect_success 'will not overwrite unstaged changes in renamed file' '
        git reset --hard c1 &&
        git mv c1.c other.c &&
        git commit -m rename &&
index 19601fb546d5a329e4889b352d817fe6f7fb7fb3..e80eacbb1b81a319232c0b5f28aab3c82d795a93 100755 (executable)
@@ -224,6 +224,7 @@ Message-Id: MESSAGE-ID-STRING
 X-Mailer: X-MAILER-STRING
 In-Reply-To: <unique-message-id@example.com>
 References: <unique-message-id@example.com>
+Reply-To: Reply <reply@example.com>
 
 Result: OK
 EOF
@@ -316,6 +317,7 @@ test_expect_success $PREREQ 'Show all headers' '
                --dry-run \
                --suppress-cc=sob \
                --from="Example <from@example.com>" \
+               --reply-to="Reply <reply@example.com>" \
                --to=to@example.com \
                --cc=cc@example.com \
                --bcc=bcc@example.com \
index c30660d60626c886dfa5993acddaebf2d3364de9..06742748e99fda241ec629e34c0700ae4050692e 100755 (executable)
@@ -447,12 +447,10 @@ test_expect_success 'cvs update (-p)' '
     git push gitcvs.git >/dev/null &&
     cd cvswork &&
     GIT_CONFIG="$git_config" cvs update &&
-    rm -f failures &&
     for i in merge no-lf empty really-empty; do
-        GIT_CONFIG="$git_config" cvs update -p "$i" >$i.out
-       test_cmp $i.out ../$i >>failures 2>&1
-    done &&
-    test -z "$(cat failures)"
+       GIT_CONFIG="$git_config" cvs update -p "$i" >$i.out &&
+       test_cmp $i.out ../$i || return 1
+    done
 '
 
 cd "$WORKDIR"
index 6d2d3c8739cbd6978d823e7947d9f95c758ff241..cf31ace66763741a7f53e3b9a1cb3c0019255f56 100755 (executable)
@@ -455,20 +455,20 @@ test_expect_success 'cvs up -r $(git rev-parse v1)' '
 '
 
 test_expect_success 'cvs diff -r v1 -u' '
-       ( cd cvswork && cvs -f diff -r v1 -u ) >cvsDiff.out 2>cvs.log &&
+       ( cd cvswork && cvs -f diff -r v1 -u >../cvsDiff.out 2>../cvs.log ) &&
        test_must_be_empty cvsDiff.out &&
        test_must_be_empty cvs.log
 '
 
 test_expect_success 'cvs diff -N -r v2 -u' '
-       ( cd cvswork && ! cvs -f diff -N -r v2 -u ) >cvsDiff.out 2>cvs.log &&
+       ( cd cvswork && ! cvs -f diff -N -r v2 -u >../cvsDiff.out 2>../cvs.log ) &&
        test_must_be_empty cvs.log &&
        test -s cvsDiff.out &&
        check_diff cvsDiff.out v2 v1 >check_diff.out 2>&1
 '
 
 test_expect_success 'cvs diff -N -r v2 -r v1.2' '
-       ( cd cvswork && ! cvs -f diff -N -r v2 -r v1.2 -u ) >cvsDiff.out 2>cvs.log &&
+       ( cd cvswork && ! cvs -f diff -N -r v2 -r v1.2 -u >../cvsDiff.out 2>../cvs.log ) &&
        test_must_be_empty cvs.log &&
        test -s cvsDiff.out &&
        check_diff cvsDiff.out v2 v1.2 >check_diff.out 2>&1
@@ -487,7 +487,7 @@ test_expect_success 'apply early [cvswork3] diff to b3' '
 '
 
 test_expect_success 'check [cvswork3] diff' '
-       ( cd cvswork3 && ! cvs -f diff -N -u ) >"$WORKDIR/cvsDiff.out" 2>cvs.log &&
+       ( cd cvswork3 && ! cvs -f diff -N -u >"$WORKDIR/cvsDiff.out" 2>../cvs.log ) &&
        test_must_be_empty cvs.log &&
        test -s cvsDiff.out &&
        test $(grep Index: cvsDiff.out | wc -l) = 3 &&
index d5685891a560d2f1a06c937043ceda2c8b9fd9db..31a2cf2d0b5422b6c046da1a5f7600557b75977c 100644 (file)
@@ -1508,8 +1508,8 @@ static int verify_uptodate_1(const struct cache_entry *ce,
                add_rejected_path(o, error_type, ce->name);
 }
 
-static int verify_uptodate(const struct cache_entry *ce,
-                          struct unpack_trees_options *o)
+int verify_uptodate(const struct cache_entry *ce,
+                   struct unpack_trees_options *o)
 {
        if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
                return 0;
index 6c48117b845fbf7b983852be302e4472c5e6d651..41178ada94a4b7c5222cab7dd17d9eeb7a1956e4 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef UNPACK_TREES_H
 #define UNPACK_TREES_H
 
+#include "tree-walk.h"
 #include "string-list.h"
 
 #define MAX_UNPACK_TREES 8
@@ -78,6 +79,9 @@ struct unpack_trees_options {
 extern int unpack_trees(unsigned n, struct tree_desc *t,
                struct unpack_trees_options *options);
 
+int verify_uptodate(const struct cache_entry *ce,
+                   struct unpack_trees_options *o);
+
 int threeway_merge(const struct cache_entry * const *stages,
                   struct unpack_trees_options *o);
 int twoway_merge(const struct cache_entry * const *src,
index 8f5028f6b25eb570aedb9763f2456442b2e4a1aa..a69241b25ddaff5b61380aa8b451a6fcb833502c 100644 (file)
@@ -147,7 +147,7 @@ PATTERNS("csharp",
         /* Keywords */
         "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n"
         /* Methods and constructors */
-        "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n"
+        "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n"
         /* Properties */
         "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n"
         /* Type definitions */