[PATCH] Fix git-merge-one-file permissions auto-merging
[git.git] / read-tree.c
index 4acbb6b..fbd0da0 100644 (file)
@@ -155,28 +155,51 @@ static int threeway_merge(struct cache_entry *stages[4], struct cache_entry **ds
 /*
  * Two-way merge.
  *
- * The rule is: 
- *  - every current entry has to match the old tree
- *  - if the current entry matches the new tree, we leave it
- *    as-is. Otherwise we require that it be up-to-date.
+ * The rule is to "carry forward" what is in the index without losing
+ * information across a "fast forward", favoring a successful merge
+ * over a merge failure when it makes sense.  For details of the
+ * "carry forward" rule, please see <Documentation/git-read-tree.txt>.
+ *
  */
 static int twoway_merge(struct cache_entry **src, struct cache_entry **dst)
 {
-       struct cache_entry *old = src[0];
-       struct cache_entry *a = src[1], *b = src[2];
+       struct cache_entry *current = src[0];
+       struct cache_entry *oldtree = src[1], *newtree = src[2];
 
        if (src[3])
                return -1;
 
-       if (old) {
-               if (!a || !same(old, a))
+       if (current) {
+               if ((!oldtree && !newtree) || /* 4 and 5 */
+                   (!oldtree && newtree &&
+                    same(current, newtree)) || /* 6 and 7 */
+                   (oldtree && newtree &&
+                    same(oldtree, newtree)) || /* 14 and 15 */
+                   (oldtree && newtree &&
+                    !same(oldtree, newtree) && /* 18 and 19*/
+                    same(current, newtree))) {
+                       *dst++ = current;
+                       return 1;
+               }
+               else if (oldtree && !newtree && same(current, oldtree)) {
+                       /* 10 or 11 */
+                       verify_uptodate(current);
+                       return 0;
+               }
+               else if (oldtree && newtree &&
+                        same(current, oldtree) && !same(current, newtree)) {
+                       /* 20 or 21 */
+                       verify_uptodate(current);
+                       return merged_entry(newtree, NULL, dst);
+               }
+               else
+                       /* all other failures */
                        return -1;
        }
-       if (b)
-               return merged_entry(b, old, dst);
-       if (old)
-               verify_uptodate(old);
-       return 0;
+       else if (newtree)
+               return merged_entry(newtree, NULL, dst);
+       else
+               return 0;
 }
 
 /*
@@ -195,11 +218,11 @@ static int oneway_merge(struct cache_entry **src, struct cache_entry **dst)
 
        if (!a)
                return 0;
-       if (old && same(old, a))
-               *a = *old;
-       a->ce_flags &= ~htons(CE_STAGEMASK);
-       *dst++ = a;
-       return 1;
+       if (old && same(old, a)) {
+               *dst++ = old;
+               return 1;
+       }
+       return merged_entry(a, NULL, dst);
 }
 
 static void check_updates(struct cache_entry **src, int nr)
@@ -221,7 +244,9 @@ static void check_updates(struct cache_entry **src, int nr)
        }
 }
 
-static void merge_cache(struct cache_entry **src, int nr, int (*fn)(struct cache_entry **, struct cache_entry **))
+typedef int (*merge_fn_t)(struct cache_entry **, struct cache_entry **);
+
+static void merge_cache(struct cache_entry **src, int nr, merge_fn_t fn)
 {
        struct cache_entry **dst = src;
 
@@ -250,7 +275,7 @@ static void merge_cache(struct cache_entry **src, int nr, int (*fn)(struct cache
        check_updates(active_cache, active_nr);
 }
 
-static char *read_tree_usage = "git-read-tree (<sha> | -m <sha1> [<sha2> [<sha3>]])";
+static char *read_tree_usage = "git-read-tree (<sha> | -m [-u] <sha1> [<sha2> [<sha3>]])";
 
 static struct cache_file cache_file;
 
@@ -295,21 +320,17 @@ int main(int argc, char **argv)
                        die("failed to unpack tree object %s", arg);
                stage++;
        }
+       if (update && !merge)
+               usage(read_tree_usage);
        if (merge) {
-               switch (stage) {
-               case 4: /* Three-way merge */
-                       merge_cache(active_cache, active_nr, threeway_merge);
-                       break;
-               case 3: /* Update from one tree to another */
-                       merge_cache(active_cache, active_nr, twoway_merge);
-                       check_updates(active_cache, active_nr);
-                       break;
-               case 2: /* Just read a tree, merge with old cache contents */
-                       merge_cache(active_cache, active_nr, oneway_merge);
-                       break;
-               default:
+               static const merge_fn_t merge_function[] = {
+                       [1] = oneway_merge,
+                       [2] = twoway_merge,
+                       [3] = threeway_merge,
+               };
+               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 (write_cache(newfd, active_cache, active_nr) ||
            commit_index_file(&cache_file))