@matheusbsbs / matheus-bagliano / commits / 9bc454df08
Fork 0

reset: add option "--keep" to "git reset"

The purpose of this new option is to discard some of the last commits but to keep current changes in the work tree. The use case is when you work on something and commit that work. And then you work on something else that touches other files, but you don't commit it yet. Then you realize that what you commited when you worked on the first thing is not good or belongs to another branch. So you want to get rid of the previous commits (at least in the current branch) but you want to make sure that you keep the changes you have in the work tree. And you are pretty sure that your changes are independent from what you previously commited, so you don't want the reset to succeed if the previous commits changed a file that you also changed in your work tree. The table below shows what happens when running "git reset --keep target" to reset the HEAD to another commit (as a special case "target" could be the same as HEAD). working index HEAD target working index HEAD ---------------------------------------------------- A B C D --keep (disallowed) A B C C --keep A C C B B C D --keep (disallowed) B B C C --keep B C C In this table, A, B and C are some different states of a file. For example the last line of the table means that if a file is in state B in the working tree and the index, and in a different state C in HEAD and in the target, then "git reset --keep target" will put the file in state B in the working tree, and in state C in the index and in HEAD. The following table shows what happens on unmerged entries: working index HEAD target working index HEAD ---------------------------------------------------- X U A B --keep (disallowed) X U A A --keep X A A In this table X can be any state and U means an unmerged entry. Though the error message when "reset --keep" is disallowed on unmerged entries is something like: error: Entry 'file1' would be overwritten by merge. Cannot merge. fatal: Could not reset index file to revision 'HEAD^'. which is not very nice. A following patch will add some test cases for "--keep". The "--keep" option is implemented by doing a 2 way merge between HEAD and the reset target, and if this succeeds by doing a mixed reset to the target. The code comes from the sequencer GSoC project, where such an option was developed by Stephan Beyer: git://repo.or.cz/git/sbeyer.git (at commit 5a78908b70ceb5a4ea9fd4b82f07ceba1f019079) But in the sequencer project the "reset" flag was set in the "struct unpack_trees_options" passed to "unpack_trees()". With this flag the changes in the working tree were discarded if the file was different between HEAD and the reset target. Mentored-by: Daniel Barkalow <barkalow@iabervon.org> Mentored-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Stephan Beyer <s-beyer@gmx.net> Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Christian Couder committed Jan 19, 2010 at 05:25 UTC 9bc454df08ca2a27b51ac0ab9ff8f154e51b8698
1 file changed +24 -5
builtin-reset.c
+24 -5
@@ -22,13 +22,15 @@
22 #include "cache-tree.h"
23
24 static const char * const git_reset_usage[] = {
25 - "git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
25 + "git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]",
26 "git reset [--mixed] <commit> [--] <paths>...",
27 NULL
28 };
29
30 -enum reset_type { MIXED, SOFT, HARD, MERGE, NONE };
31 -static const char *reset_type_names[] = { "mixed", "soft", "hard", "merge", NULL };
30 +enum reset_type { MIXED, SOFT, HARD, MERGE, KEEP, NONE };
31 +static const char *reset_type_names[] = {
32 + "mixed", "soft", "hard", "merge", "keep", NULL
33 +};
34
35 static char *args_to_str(const char **argv)
36 {
@@ -71,6 +73,7 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
73 if (!quiet)
74 opts.verbose_update = 1;
75 switch (reset_type) {
76 + case KEEP:
77 case MERGE:
78 opts.update = 1;
79 break;
@@ -85,6 +88,16 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
88
89 read_cache_unmerged();
90
91 + if (reset_type == KEEP) {
92 + unsigned char head_sha1[20];
93 + if (get_sha1("HEAD", head_sha1))
94 + return error("You do not have a valid HEAD.");
95 + if (!fill_tree_descriptor(desc, head_sha1))
96 + return error("Failed to find tree of HEAD.");
97 + nr++;
98 + opts.fn = twoway_merge;
99 + }
100 +
101 if (!fill_tree_descriptor(desc + nr - 1, sha1))
102 return error("Failed to find tree of %s.", sha1_to_hex(sha1));
103 if (unpack_trees(nr, desc, &opts))
@@ -229,6 +242,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
242 "reset HEAD, index and working tree", HARD),
243 OPT_SET_INT(0, "merge", &reset_type,
244 "reset HEAD, index and working tree", MERGE),
245 + OPT_SET_INT(0, "keep", &reset_type,
246 + "reset HEAD but keep local changes", KEEP),
247 OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
248 OPT_END()
249 };
@@ -317,9 +332,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
332 if (reset_type == SOFT) {
333 if (is_merge() || read_cache() < 0 || unmerged_cache())
334 die("Cannot do a soft reset in the middle of a merge.");
335 + } else {
336 + int err = reset_index_file(sha1, reset_type, quiet);
337 + if (reset_type == KEEP)
338 + err = err || reset_index_file(sha1, MIXED, quiet);
339 + if (err)
340 + die("Could not reset index file to revision '%s'.", rev);
341 }
321 - else if (reset_index_file(sha1, reset_type, quiet))
322 - die("Could not reset index file to revision '%s'.", rev);
342
343 /* Any resets update HEAD to the head being switched to,
344 * saving the previous head in ORIG_HEAD before. */