Merge branch 'jn/send-pack-error' into maint
[gitweb.git] / builtin / reset.c
index 0f5022eed24f980f6fedee49f8602fefa6fe85e4..0037be4693bbd875e644df13ce4a9fae31e7378c 100644 (file)
 #include "cache-tree.h"
 
 static const char * const git_reset_usage[] = {
-       "git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
-       "git reset [--mixed] <commit> [--] <paths>...",
+       "git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]",
+       "git reset [-q] <commit> [--] <paths>...",
+       "git reset --patch [<commit>] [--] [<paths>...]",
        NULL
 };
 
-enum reset_type { MIXED, SOFT, HARD, MERGE, NONE };
-static const char *reset_type_names[] = { "mixed", "soft", "hard", "merge", NULL };
+enum reset_type { MIXED, SOFT, HARD, MERGE, KEEP, NONE };
+static const char *reset_type_names[] = {
+       "mixed", "soft", "hard", "merge", "keep", NULL
+};
 
 static char *args_to_str(const char **argv)
 {
@@ -71,6 +74,7 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
        if (!quiet)
                opts.verbose_update = 1;
        switch (reset_type) {
+       case KEEP:
        case MERGE:
                opts.update = 1;
                break;
@@ -85,6 +89,16 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
 
        read_cache_unmerged();
 
+       if (reset_type == KEEP) {
+               unsigned char head_sha1[20];
+               if (get_sha1("HEAD", head_sha1))
+                       return error("You do not have a valid HEAD.");
+               if (!fill_tree_descriptor(desc, head_sha1))
+                       return error("Failed to find tree of HEAD.");
+               nr++;
+               opts.fn = twoway_merge;
+       }
+
        if (!fill_tree_descriptor(desc + nr - 1, sha1))
                return error("Failed to find tree of %s.", sha1_to_hex(sha1));
        if (unpack_trees(nr, desc, &opts))
@@ -211,6 +225,14 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)
                warning("Reflog action message too long: %.*s...", 50, buf);
 }
 
+static void die_if_unmerged_cache(int reset_type)
+{
+       if (is_merge() || read_cache() < 0 || unmerged_cache())
+               die("Cannot do a %s reset in the middle of a merge.",
+                   reset_type_names[reset_type]);
+
+}
+
 int cmd_reset(int argc, const char **argv, const char *prefix)
 {
        int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
@@ -229,6 +251,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                                "reset HEAD, index and working tree", HARD),
                OPT_SET_INT(0, "merge", &reset_type,
                                "reset HEAD, index and working tree", MERGE),
+               OPT_SET_INT(0, "keep", &reset_type,
+                               "reset HEAD but keep local changes", KEEP),
                OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
                OPT_END()
        };
@@ -294,7 +318,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
         * affecting the working tree nor HEAD. */
        if (i < argc) {
                if (reset_type == MIXED)
-                       warning("--mixed option is deprecated with paths.");
+                       warning("--mixed with paths is deprecated; use 'git reset -- <paths>' instead.");
                else if (reset_type != NONE)
                        die("Cannot do %s reset with paths.",
                                        reset_type_names[reset_type]);
@@ -304,7 +328,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
        if (reset_type == NONE)
                reset_type = MIXED; /* by default */
 
-       if (reset_type == HARD || reset_type == MERGE)
+       if (reset_type != SOFT && reset_type != MIXED)
                setup_work_tree();
 
        if (reset_type == MIXED && is_bare_repository())
@@ -314,12 +338,18 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
        /* Soft reset does not touch the index file nor the working tree
         * at all, but requires them in a good order.  Other resets reset
         * the index file to the tree object we are switching to. */
-       if (reset_type == SOFT) {
-               if (is_merge() || read_cache() < 0 || unmerged_cache())
-                       die("Cannot do a soft reset in the middle of a merge.");
+       if (reset_type == SOFT)
+               die_if_unmerged_cache(reset_type);
+       else {
+               int err;
+               if (reset_type == KEEP)
+                       die_if_unmerged_cache(reset_type);
+               err = reset_index_file(sha1, reset_type, quiet);
+               if (reset_type == KEEP)
+                       err = err || reset_index_file(sha1, MIXED, quiet);
+               if (err)
+                       die("Could not reset index file to revision '%s'.", rev);
        }
-       else if (reset_index_file(sha1, reset_type, quiet))
-               die("Could not reset index file to revision '%s'.", rev);
 
        /* Any resets update HEAD to the head being switched to,
         * saving the previous head in ORIG_HEAD before. */