Skip to content

Commit 9e8ecea

Browse files
torvaldsgitster
authored andcommitted
Add 'merge' mode to 'git reset'
We have always had a nice way to reset a working tree to another state while carrying our changes around: "git read-tree -u -m". Yes, it fails if the target tree is different in the paths that are dirty in the working tree, but this is how we used to switch branches in "git checkout", and it worked fine. However, perhaps exactly _because_ we've supported this from very early on, another low-level command, namely "git reset", never did. But as time went on, 'git reset' remains as a very common command, while 'git read-tree' is now a very odd and low-level plumbing thing that nobody sane should ever use, because it only makes sense together with other operations like either switching branches or just rewriting HEAD. Which means that we have effectively lost the ability to do something very common: jump to another point in time without always dropping all our dirty state. So add this kind of mode to "git reset", and since it merges your changes to what you are resetting to, just call it that: "git reset --merge". I've wanted this for a long time, since I very commonly carry a dirty tree while working on things. My main 'Makefile' file quite often has the next version already modified, and sometimes I have local modifications that I don't want to commit, but I still do pulls and patch applications, and occasionally want to do "git reset" to undo them - while still keeping my local modifications. (Maybe we could eventually change it to something like "if we have a working tree, default to --merge, otherwise default to --mixed"). NOTE! This new mode is certainly not perfect. There's a few things to look out for: - if the index has unmerged entries, "--merge" will currently simply refuse to reset ("you need to resolve your current index first"). You'll need to use "--hard" or similar in this case. This is sad, because normally a unmerged index means that the working tree file should have matched the source tree, so the correct action is likely to make --merge reset such a path to the target (like --hard), regardless of dirty state in-tree or in-index. But that's not how read-tree has ever worked, so.. - "git checkout -m" actually knows how to do a three-way merge, rather than refuse to update the working tree. So we do know how to do that, and arguably that would be even nicer behavior. At the same time it's also arguably true that there is a chance of loss of state (ie you cannot get back to the original tree if the three-way merge ends up resolving cleanly to no diff at all), so the "refuse to do it" is in some respects the safer - but less user-friendly - option. In other words, I think 'git reset --merge' could become a bit more friendly, but this is already a big improvement. It allows you to undo a recent commit without having to throw your current work away. Yes, yes, with a dirty tree you could always do git stash git reset --hard git stash apply instead, but isn't "git reset --merge" a nice way to handle one particular simple case? Signed-off-by: Linus Torvalds <[email protected]> -- Hmm? Maybe I'm the only one that does a lot of work with a dirty tree, and sure, I can do other things like the "git stash" thing, or using "git checkout" to actually create a new branch, and then playing games with branch renaming etc to make it work like this one. But I suspect others dislike how "git reset" works too. But see the suggested improvements above. builtin-reset.c | 26 ++++++++++++++++++-------- 1 files changed, 18 insertions(+), 8 deletions(-)
1 parent 3273ebc commit 9e8ecea

File tree

1 file changed

+18
-8
lines changed

1 file changed

+18
-8
lines changed

builtin-reset.c

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@
2020
#include "parse-options.h"
2121

2222
static const char * const git_reset_usage[] = {
23-
"git reset [--mixed | --soft | --hard] [-q] [<commit>]",
23+
"git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
2424
"git reset [--mixed] <commit> [--] <paths>...",
2525
NULL
2626
};
2727

28+
enum reset_type { MIXED, SOFT, HARD, MERGE, NONE };
29+
static const char *reset_type_names[] = { "mixed", "soft", "hard", "merge", NULL };
30+
2831
static char *args_to_str(const char **argv)
2932
{
3033
char *buf = NULL;
@@ -49,17 +52,25 @@ static inline int is_merge(void)
4952
return !access(git_path("MERGE_HEAD"), F_OK);
5053
}
5154

52-
static int reset_index_file(const unsigned char *sha1, int is_hard_reset, int quiet)
55+
static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
5356
{
5457
int i = 0;
5558
const char *args[6];
5659

5760
args[i++] = "read-tree";
5861
if (!quiet)
5962
args[i++] = "-v";
60-
args[i++] = "--reset";
61-
if (is_hard_reset)
63+
switch (reset_type) {
64+
case MERGE:
6265
args[i++] = "-u";
66+
args[i++] = "-m";
67+
break;
68+
case HARD:
69+
args[i++] = "-u";
70+
/* fallthrough */
71+
default:
72+
args[i++] = "--reset";
73+
}
6374
args[i++] = sha1_to_hex(sha1);
6475
args[i] = NULL;
6576

@@ -169,9 +180,6 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)
169180
warning("Reflog action message too long: %.*s...", 50, buf);
170181
}
171182

172-
enum reset_type { MIXED, SOFT, HARD, NONE };
173-
static const char *reset_type_names[] = { "mixed", "soft", "hard", NULL };
174-
175183
int cmd_reset(int argc, const char **argv, const char *prefix)
176184
{
177185
int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
@@ -186,6 +194,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
186194
OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
187195
OPT_SET_INT(0, "hard", &reset_type,
188196
"reset HEAD, index and working tree", HARD),
197+
OPT_SET_INT(0, "merge", &reset_type,
198+
"reset HEAD, index and working tree", MERGE),
189199
OPT_BOOLEAN('q', NULL, &quiet,
190200
"disable showing new HEAD in hard reset and progress message"),
191201
OPT_END()
@@ -266,7 +276,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
266276
if (is_merge() || read_cache() < 0 || unmerged_cache())
267277
die("Cannot do a soft reset in the middle of a merge.");
268278
}
269-
else if (reset_index_file(sha1, (reset_type == HARD), quiet))
279+
else if (reset_index_file(sha1, reset_type, quiet))
270280
die("Could not reset index file to revision '%s'.", rev);
271281

272282
/* Any resets update HEAD to the head being switched to,

0 commit comments

Comments
 (0)