diff --git a/read-tree.c b/read-tree.c
index ee7a46c909e..f2a8bb53a1c 100644
--- a/read-tree.c
+++ b/read-tree.c
@@ -209,6 +209,58 @@ static int twoway_merge(struct cache_entry **src, struct cache_entry **dst)
 		return deleted_entry(oldtree, current, dst);
 }
 
+/*
+ * Two-way merge emulated with three-way merge.
+ *
+ * This treats "read-tree -m H M" by transforming it internally
+ * into "read-tree -m H I+H M", where I+H is a tree that would
+ * contain the contents of the current index file, overlayed on
+ * top of H.  Unlike the traditional two-way merge, this leaves
+ * the stages in the resulting index file and lets the user resolve
+ * the merge conflicts using standard tools for three-way merge.
+ *
+ * This function is just to set-up such an arrangement, and the
+ * actual merge uses threeway_merge() function.
+ */
+static void setup_emu23(void)
+{
+	/* stage0 contains I, stage1 H, stage2 M.
+	 * move stage2 to stage3, and create stage2 entries
+	 * by scanning stage0 and stage1 entries.
+	 */
+	int i, namelen, size;
+	struct cache_entry *ce, *stage2;
+
+	for (i = 0; i < active_nr; i++) {
+		ce = active_cache[i];
+		if (ce_stage(ce) != 2)
+			continue;
+		/* hoist them up to stage 3 */
+		namelen = ce_namelen(ce);
+		ce->ce_flags = create_ce_flags(namelen, 3);
+	}
+
+	for (i = 0; i < active_nr; i++) {
+		ce = active_cache[i];
+		if (ce_stage(ce) > 1)
+			continue;
+		namelen = ce_namelen(ce);
+		size = cache_entry_size(namelen);
+		stage2 = xmalloc(size);
+		memcpy(stage2, ce, size);
+		stage2->ce_flags = create_ce_flags(namelen, 2);
+		if (add_cache_entry(stage2, ADD_CACHE_OK_TO_ADD) < 0)
+			die("cannot merge index and our head tree");
+
+		/* We are done with this name, so skip to next name */
+		while (i < active_nr &&
+		       ce_namelen(active_cache[i]) == namelen &&
+		       !memcmp(active_cache[i]->name, ce->name, namelen))
+			i++;
+		i--; /* compensate for the loop control */
+	}
+}
+
 /*
  * One-way merge.
  *
@@ -315,7 +367,7 @@ static struct cache_file cache_file;
 
 int main(int argc, char **argv)
 {
-	int i, newfd, merge, reset;
+	int i, newfd, merge, reset, emu23;
 	unsigned char sha1[20];
 
 	newfd = hold_index_file_for_update(&cache_file, get_index_file());
@@ -324,6 +376,7 @@ int main(int argc, char **argv)
 
 	merge = 0;
 	reset = 0;
+	emu23 = 0;
 	for (i = 1; i < argc; i++) {
 		const char *arg = argv[i];
 
@@ -335,7 +388,7 @@ int main(int argc, char **argv)
 
 		/* This differs from "-m" in that we'll silently ignore unmerged entries */
 		if (!strcmp(arg, "--reset")) {
-			if (stage || merge)
+			if (stage || merge || emu23)
 				usage(read_tree_usage);
 			reset = 1;
 			merge = 1;
@@ -345,7 +398,7 @@ int main(int argc, char **argv)
 
 		/* "-m" stands for "merge", meaning we start in stage 1 */
 		if (!strcmp(arg, "-m")) {
-			if (stage || merge)
+			if (stage || merge || emu23)
 				usage(read_tree_usage);
 			if (read_cache_unmerged())
 				die("you need to resolve your current index first");
@@ -353,6 +406,17 @@ int main(int argc, char **argv)
 			merge = 1;
 			continue;
 		}
+
+		/* "-emu23" uses 3-way merge logic to perform fast-forward */
+		if (!strcmp(arg, "--emu23")) {
+			if (stage || merge || emu23)
+				usage(read_tree_usage);
+			if (read_cache_unmerged())
+				die("you need to resolve your current index first");
+			merge = emu23 = stage = 1;
+			continue;
+		}
+
 		if (get_sha1(arg, sha1) < 0)
 			usage(read_tree_usage);
 		if (stage > 3)
@@ -369,9 +433,18 @@ int main(int argc, char **argv)
 			[2] = twoway_merge,
 			[3] = threeway_merge,
 		};
+		merge_fn_t fn;
+
 		if (stage < 2 || stage > 4)
 			die("just how do you expect me to merge %d trees?", stage-1);
-		merge_cache(active_cache, active_nr, merge_function[stage-1]);
+		if (emu23 && stage != 3)
+			die("--emu23 takes only two trees");
+		fn = merge_function[stage-1];
+		if (stage == 3 && emu23) { 
+			setup_emu23();
+			fn = merge_function[3];
+		}
+		merge_cache(active_cache, active_nr, fn);
 	}
 	if (write_cache(newfd, active_cache, active_nr) ||
 	    commit_index_file(&cache_file))
diff --git a/t/t1005-read-tree-m-2way-emu23.sh b/t/t1005-read-tree-m-2way-emu23.sh
new file mode 100644
index 00000000000..3345c9dd3f0
--- /dev/null
+++ b/t/t1005-read-tree-m-2way-emu23.sh
@@ -0,0 +1,316 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Two way merge with read-tree --emu23 $H $M
+
+This test tries two-way merge (aka fast forward with carry forward).
+
+There is the head (called H) and another commit (called M), which is
+simply ahead of H.  The index and the work tree contains a state that
+is derived from H, but may also have local changes.  This test checks
+all the combinations described in the two-tree merge "carry forward"
+rules, found in <Documentation/git-rev-tree.txt>.
+
+In the test, these paths are used:
+        bozbar  - in H, stays in M, modified from bozbar to gnusto
+        frotz   - not in H added in M
+        nitfol  - in H, stays in M unmodified
+        rezrov  - in H, deleted in M
+        yomin   - not in H nor M
+'
+. ./test-lib.sh
+
+read_tree_twoway () {
+    git-read-tree --emu23 "$1" "$2" &&
+    git-ls-files --stage &&
+    git-merge-cache git-merge-one-file-script -a &&
+    git-ls-files --stage
+}
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+compare_change () {
+    	cat current
+	sed -n >current \
+	    -e '/^--- /d; /^+++ /d; /^@@ /d;' \
+	    -e 's/^\([-+][0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /p' \
+	    "$1"
+	diff -u expected current
+}
+
+check_cache_at () {
+	clean_if_empty=`git-diff-files "$1"`
+	case "$clean_if_empty" in
+	'')  echo "$1: clean" ;;
+	?*)  echo "$1: dirty" ;;
+	esac
+	case "$2,$clean_if_empty" in
+	clean,)		:     ;;
+	clean,?*)	false ;;
+	dirty,)		false ;;
+	dirty,?*)	:     ;;
+	esac
+}
+
+check_stages () {
+    cat >expected_stages
+    git-ls-files --stage | sed -e "s/ $_x40 / X /" >current_stages
+    diff -u expected_stages current_stages
+}
+
+test_expect_success \
+    setup \
+    'echo frotz >frotz &&
+     echo nitfol >nitfol &&
+     echo bozbar >bozbar &&
+     echo rezrov >rezrov &&
+     echo yomin >yomin &&
+     git-update-cache --add nitfol bozbar rezrov &&
+     treeH=`git-write-tree` &&
+     echo treeH $treeH &&
+     git-ls-tree $treeH &&
+
+     echo gnusto >bozbar &&
+     git-update-cache --add frotz bozbar --force-remove rezrov &&
+     git-ls-files --stage >M.out &&
+     treeM=`git-write-tree` &&
+     echo treeM $treeM &&
+     git-ls-tree $treeM &&
+     git-diff-tree $treeH $treeM'
+
+test_expect_success \
+    '1, 2, 3 - no carry forward' \
+    'rm -f .git/index &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >1-3.out &&
+     diff -u M.out 1-3.out &&
+     check_cache_at bozbar dirty &&
+     check_cache_at frotz clean && # different from pure 2-way
+     check_cache_at nitfol dirty'
+
+echo '+100644 X 0	yomin' >expected
+
+test_expect_success \
+    '4 - carry forward local addition.' \
+    'rm -f .git/index &&
+     git-update-cache --add yomin &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >4.out || exit
+     diff -u M.out 4.out >4diff.out
+     compare_change 4diff.out expected &&
+     check_cache_at yomin clean'
+
+# "read-tree -m H I+H M" where !H && !M; so (I+H) not being up-to-date
+# should not matter, but without #3ALT this does not work.
+: test_expect_success \
+    '5 - carry forward local addition.' \
+    'rm -f .git/index &&
+     echo yomin >yomin &&
+     git-update-cache --add yomin &&
+     echo yomin yomin >yomin &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >5.out || exit
+     diff -u M.out 5.out >5diff.out
+     compare_change 5diff.out expected &&
+     check_cache_at yomin dirty'
+
+# "read-tree -m H I+H M" where !H && M && (I+H) == M, so this should
+# succeed (even the entry is clean), but without #5ALT this does not
+# work.
+: test_expect_success \
+    '6 - local addition already has the same.' \
+    'rm -f .git/index &&
+     git-update-cache --add frotz &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >6.out &&
+     diff -u M.out 6.out &&
+     check_cache_at frotz clean'
+
+# Exactly the same pattern as above but with dirty cache.  This also
+# should succeed, but without #5ALT it does not.
+: test_expect_success \
+    '7 - local addition already has the same.' \
+    'rm -f .git/index &&
+     echo frotz >frotz &&
+     git-update-cache --add frotz &&
+     echo frotz frotz >frotz &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >7.out &&
+     diff -u M.out 7.out &&
+     check_cache_at frotz dirty'
+
+test_expect_success \
+    '8 - conflicting addition.' \
+    'rm -f .git/index &&
+     echo frotz frotz >frotz &&
+     git-update-cache --add frotz &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '9 - conflicting addition.' \
+    'rm -f .git/index &&
+     echo frotz frotz >frotz &&
+     git-update-cache --add frotz &&
+     echo frotz >frotz &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '10 - path removed.' \
+    'rm -f .git/index &&
+     echo rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >10.out &&
+     diff -u M.out 10.out'
+
+test_expect_success \
+    '11 - dirty path removed.' \
+    'rm -f .git/index &&
+     echo rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     echo rezrov rezrov >rezrov &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '12 - unmatching local changes being removed.' \
+    'rm -f .git/index &&
+     echo rezrov rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '13 - unmatching local changes being removed.' \
+    'rm -f .git/index &&
+     echo rezrov rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     echo rezrov >rezrov &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+cat >expected <<EOF
+-100644 X 0	nitfol
++100644 X 0	nitfol
+EOF
+
+test_expect_success \
+    '14 - unchanged in two heads.' \
+    'rm -f .git/index &&
+     echo nitfol nitfol >nitfol &&
+     git-update-cache --add nitfol &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >14.out || exit
+     diff -u M.out 14.out >14diff.out
+     compare_change 14diff.out expected &&
+     check_cache_at nitfol clean'
+
+test_expect_success \
+    '15 - unchanged in two heads.' \
+    'rm -f .git/index &&
+     echo nitfol nitfol >nitfol &&
+     git-update-cache --add nitfol &&
+     echo nitfol nitfol nitfol >nitfol &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >15.out || exit
+     diff -u M.out 15.out >15diff.out
+     compare_change 15diff.out expected &&
+     check_cache_at nitfol dirty'
+
+# This is different from straight 2-way merge in that it leaves
+# three stages of bozbar in the index file without failing, so
+# the user can run git-diff-stages to examine the situation.
+test_expect_success \
+    '16 - conflicting local change.' \
+    'rm -f .git/index &&
+     echo bozbar bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     git-read-tree --emu23 $treeH $treeM &&
+     check_stages' <<\EOF
+100644 X 1	bozbar
+100644 X 2	bozbar
+100644 X 3	bozbar
+100644 X 3	frotz
+100644 X 0	nitfol
+100644 X 1	rezrov
+100644 X 2	rezrov
+EOF
+
+test_expect_success \
+    '17 - conflicting local change.' \
+    'rm -f .git/index &&
+     echo bozbar bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     echo bozbar bozbar bozbar >bozbar &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '18 - local change already having a good result.' \
+    'rm -f .git/index &&
+     echo gnusto >bozbar &&
+     git-update-cache --add bozbar &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >18.out &&
+     diff -u M.out 18.out &&
+     check_cache_at bozbar clean'
+
+test_expect_success \
+    '19 - local change already having a good result, further modified.' \
+    'rm -f .git/index &&
+     echo gnusto >bozbar &&
+     git-update-cache --add bozbar &&
+     echo gnusto gnusto >bozbar &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >19.out &&
+     diff -u M.out 19.out &&
+     check_cache_at bozbar dirty'
+
+test_expect_success \
+    '20 - no local change, use new tree.' \
+    'rm -f .git/index &&
+     echo bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >20.out &&
+     diff -u M.out 20.out &&
+     check_cache_at bozbar dirty'
+
+test_expect_success \
+    '21 - no local change, dirty cache.' \
+    'rm -f .git/index &&
+     echo bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     echo gnusto gnusto >bozbar &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+# Also make sure we did not break DF vs DF/DF case.
+test_expect_success \
+    'DF vs DF/DF case setup.' \
+    'rm -f .git/index &&
+     echo DF >DF &&
+     git-update-cache --add DF &&
+     treeDF=`git-write-tree` &&
+     echo treeDF $treeDF &&
+     git-ls-tree $treeDF &&
+
+     rm -f DF &&
+     mkdir DF &&
+     echo DF/DF >DF/DF &&
+     git-update-cache --add --remove DF DF/DF &&
+     treeDFDF=`git-write-tree` &&
+     echo treeDFDF $treeDFDF &&
+     git-ls-tree $treeDFDF &&
+     git-ls-files --stage >DFDF.out'
+
+test_expect_success \
+    'DF vs DF/DF case test.' \
+    'rm -f .git/index &&
+     rm -fr DF &&
+     echo DF >DF &&
+     git-update-cache --add DF &&
+     read_tree_twoway $treeDF $treeDFDF &&
+     git-ls-files --stage >DFDFcheck.out &&
+     diff -u DFDF.out DFDFcheck.out &&
+     check_cache_at DF/DF clean && # different from pure 2-way
+     :'
+
+test_done