* Add 'sane' mode to 'git reset'
@ 2008-12-01 17:30 Linus Torvalds
2008-12-01 17:44 ` Avery Pennarun
2008-12-03 22:22 ` Junio C Hamano
0 siblings, 2 replies; 8+ messages in thread
From: Linus Torvalds @ 2008-12-01 17:30 UTC (permalink / raw)
To: Junio C Hamano, Git Mailing List
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's probably the
sanest form of reset (it will not throw any state away), just call it
that: "git reset --sane". It should probably be the default, but we likely
cannot change the semantics of a regular "git reset", even though it is
unlikely that very many people really use the current (insane) default
mode of "--mixed" that only resets the index.
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 --sane, 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, "--sane" 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 --sane 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 --sane' 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 --sane" a nice way to handle one particular
simple case?
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
--
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(-)
diff --git a/builtin-reset.c b/builtin-reset.c
index 9514b77..05c176d 100644
--- a/builtin-reset.c
+++ b/builtin-reset.c
@@ -20,11 +20,14 @@
#include "parse-options.h"
static const char * const git_reset_usage[] = {
- "git reset [--mixed | --soft | --hard] [-q] [<commit>]",
+ "git reset [--mixed | --soft | --hard | --sane] [-q] [<commit>]",
"git reset [--mixed] <commit> [--] <paths>...",
NULL
};
+enum reset_type { MIXED, SOFT, HARD, SANE, NONE };
+static const char *reset_type_names[] = { "mixed", "soft", "hard", "sane", NULL };
+
static char *args_to_str(const char **argv)
{
char *buf = NULL;
@@ -49,7 +52,7 @@ static inline int is_merge(void)
return !access(git_path("MERGE_HEAD"), F_OK);
}
-static int reset_index_file(const unsigned char *sha1, int is_hard_reset, int quiet)
+static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
{
int i = 0;
const char *args[6];
@@ -57,9 +60,17 @@ static int reset_index_file(const unsigned char *sha1, int is_hard_reset, int qu
args[i++] = "read-tree";
if (!quiet)
args[i++] = "-v";
- args[i++] = "--reset";
- if (is_hard_reset)
+ switch (reset_type) {
+ case SANE:
args[i++] = "-u";
+ args[i++] = "-m";
+ break;
+ case HARD:
+ args[i++] = "-u";
+ /* fallthrough */
+ default:
+ args[i++] = "--reset";
+ }
args[i++] = sha1_to_hex(sha1);
args[i] = NULL;
@@ -169,9 +180,6 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)
warning("Reflog action message too long: %.*s...", 50, buf);
}
-enum reset_type { MIXED, SOFT, HARD, NONE };
-static const char *reset_type_names[] = { "mixed", "soft", "hard", NULL };
-
int cmd_reset(int argc, const char **argv, const char *prefix)
{
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)
OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
OPT_SET_INT(0, "hard", &reset_type,
"reset HEAD, index and working tree", HARD),
+ OPT_SET_INT(0, "sane", &reset_type,
+ "reset HEAD, index and working tree", SANE),
OPT_BOOLEAN('q', NULL, &quiet,
"disable showing new HEAD in hard reset and progress message"),
OPT_END()
@@ -266,7 +276,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
if (is_merge() || read_cache() < 0 || unmerged_cache())
die("Cannot do a soft reset in the middle of a merge.");
}
- else if (reset_index_file(sha1, (reset_type == HARD), quiet))
+ 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,
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: Add 'sane' mode to 'git reset'
2008-12-01 17:30 Add 'sane' mode to 'git reset' Linus Torvalds
@ 2008-12-01 17:44 ` Avery Pennarun
2008-12-01 18:04 ` Jakub Narebski
2008-12-01 18:06 ` Linus Torvalds
2008-12-03 22:22 ` Junio C Hamano
1 sibling, 2 replies; 8+ messages in thread
From: Avery Pennarun @ 2008-12-01 17:44 UTC (permalink / raw)
To: Linus Torvalds; +Cc: Junio C Hamano, Git Mailing List
On Mon, Dec 1, 2008 at 12:30 PM, Linus Torvalds
<torvalds@linux-foundation.org> wrote:
> So add this kind of mode to "git reset", and since it's probably the
> sanest form of reset (it will not throw any state away), just call it
> that: "git reset --sane". It should probably be the default, but we likely
> cannot change the semantics of a regular "git reset", even though it is
> unlikely that very many people really use the current (insane) default
> mode of "--mixed" that only resets the index.
For reference, I advised someone just yesterday to use "git reset
HEAD^" to undo an accidental "commit -a" instead of just "commit".
Also, as far as I know, "git reset HEAD filename" is the only
recommended way to undo an accidental "git add". (Which I do
sometimes when I meant to write "git add -p".) Those two options are
pretty common, I think, and are also perfectly "sane".
How about calling it --merge instead? That's really what it does:
merges the diffs from (your current index) to (the requested index)
into (your working tree and your index).
Or --keep, because it keeps your working tree changes.
Have fun,
Avery
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: Add 'sane' mode to 'git reset'
2008-12-01 17:44 ` Avery Pennarun
@ 2008-12-01 18:04 ` Jakub Narebski
2008-12-01 18:06 ` Linus Torvalds
1 sibling, 0 replies; 8+ messages in thread
From: Jakub Narebski @ 2008-12-01 18:04 UTC (permalink / raw)
To: git
Avery Pennarun wrote:
> On Mon, Dec 1, 2008 at 12:30 PM, Linus Torvalds
> <torvalds@linux-foundation.org> wrote:
>> So add this kind of mode to "git reset", and since it's probably the
>> sanest form of reset (it will not throw any state away), just call it
>> that: "git reset --sane". It should probably be the default, but we likely
>> cannot change the semantics of a regular "git reset", even though it is
>> unlikely that very many people really use the current (insane) default
>> mode of "--mixed" that only resets the index.
[...]
> How about calling it --merge instead? That's really what it does:
> merges the diffs from (your current index) to (the requested index)
> into (your working tree and your index).
I like it, because it is similar to how "git checkout --merge" works.
> Or --keep, because it keeps your working tree changes.
That is also better than --sane...
--
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: Add 'sane' mode to 'git reset'
2008-12-01 17:44 ` Avery Pennarun
2008-12-01 18:04 ` Jakub Narebski
@ 2008-12-01 18:06 ` Linus Torvalds
1 sibling, 0 replies; 8+ messages in thread
From: Linus Torvalds @ 2008-12-01 18:06 UTC (permalink / raw)
To: Avery Pennarun; +Cc: Junio C Hamano, Git Mailing List
On Mon, 1 Dec 2008, Avery Pennarun wrote:
>
> For reference, I advised someone just yesterday to use "git reset
> HEAD^" to undo an accidental "commit -a" instead of just "commit".
Yeah, I guess the --mixed default of "git reset" is occasionally useful.
> Also, as far as I know, "git reset HEAD filename" is the only
> recommended way to undo an accidental "git add".
The path-name based ones are actually a totally different animal than the
non-pathname version of "git reset". With pathnames, it won't change the
actual HEAD, so it's really a totally different class of command, just
sharing a name.
But:
> How about calling it --merge instead? That's really what it does:
> merges the diffs from (your current index) to (the requested index)
> into (your working tree and your index).
Sure, "git reset --merge" would probably be a fine form.
Linus
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: Add 'sane' mode to 'git reset'
2008-12-01 17:30 Add 'sane' mode to 'git reset' Linus Torvalds
2008-12-01 17:44 ` Avery Pennarun
@ 2008-12-03 22:22 ` Junio C Hamano
2008-12-04 0:39 ` Linus Torvalds
1 sibling, 1 reply; 8+ messages in thread
From: Junio C Hamano @ 2008-12-03 22:22 UTC (permalink / raw)
To: Linus Torvalds; +Cc: Git Mailing List
Linus Torvalds <torvalds@linux-foundation.org> writes:
> 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.
> ...
> 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.
I think a bit more explicit description with an example command sequence
in the documentation is in order. Here is what I managed to reconstruct
from your description.
Let's say you have a small change in the Makefile and some other local
changes that you would want to keep uncommitted. Then you receive a
pull request that you would want to respond to. So you do this:
$ git pull $URL $branch
knowing that your own local changes will be safe because pull will
refuse to clobber them if the changes in the other person did overlaps
with it. Let's further say that the changes did not overlap, the
merge succeeded and you still have your own local changes.
But after inspecting the result, you realize that you do not want this
change after all. That means rewinding the HEAD back to the previous
point (i.e. ORIG_HEAD), match the index to that commit, *and* rewind
the work tree back to the state before the merge (i.e. mostly matching
the index except for the local changes to Makefile and a handful other
files). "git reset" is the command to move the HEAD. What mode
should you use?
Unfortunately, the only two modes that touch the index and/or the work
tree are --mixed and --hard.
You do not want to use --hard, because it will discard your local
changes. You do not want to use --mixed either, because it will keep
not just your local changes but also the changes the merge brought
into your work tree. That's when you would want to use --merge mode.
$ git reset --merge ORIG_HEAD
This command (1) resets the HEAD to the given commit (ORIG_HEAD in
this example), and runs "read-tree -m -u HEAD ORIG_HEAD" which means
(2) resets the index to match the tree recorded by that commit, and
(3) updates the files in the work tree for difference between HEAD and
ORIG_HEAD, i.e. undoing the changes made between ORIG_HEAD and HEAD.
Because you know the work undone in (3) does not overlap with your
local changes, this is exactly what you want in such a situation.
This procedure works just as well when you are accepting patches by
replacing "git pull" with "git am" and "ORIG_HEAD" with "HEAD~$n"
(where $n is the number of patches contained in the e-mailed series)
in the above description.
Does the above describe what you meant?
Although I admit that I have used "read-tree -m -u" for probably more than
a couple of times in the past 6 months, I really do not recall the exact
sequence that led me into the situation where it was the most appropriate
command to use. I do not think any of these occassions were about me
having a wrong commit at HEAD (meaning, I really wanted to read the index
and update the work tree, without changing where the next commit would
go), so obviously my workflow is quite different from yours.
The recovery procedure I would use in such a case (and I rarely pull while
managing git.git even though I do so in my day job) would be:
$ git checkout -b this-needs-more-work
$ git checkout master
to stash away the "topic" that needs more work in a new branch, and have
"checkout" take me back to where I was with my local modifications, and
that is probably why I never felt the need for this new mode (nor did not
understand why you thought very strongly that --merge should be the
default mode and not --mixed).
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: Add 'sane' mode to 'git reset'
2008-12-03 22:22 ` Junio C Hamano
@ 2008-12-04 0:39 ` Linus Torvalds
2008-12-04 2:00 ` [RFC/PATCH] Document "git-reset --merge" Junio C Hamano
2008-12-04 2:03 ` Add 'sane' mode to 'git reset' Junio C Hamano
0 siblings, 2 replies; 8+ messages in thread
From: Linus Torvalds @ 2008-12-04 0:39 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Git Mailing List
On Wed, 3 Dec 2008, Junio C Hamano wrote:
>
> I think a bit more explicit description with an example command sequence
> in the documentation is in order. Here is what I managed to reconstruct
> from your description.
>
> Let's say you have a small change in the Makefile and some other local
> changes that you would want to keep uncommitted. Then you receive a
> pull request that you would want to respond to. So you do this:
>
> $ git pull $URL $branch
>
> knowing that your own local changes will be safe because pull will
> refuse to clobber them if the changes in the other person did overlaps
> with it. Let's further say that the changes did not overlap, the
> merge succeeded and you still have your own local changes.
>
> But after inspecting the result, you realize that you do not want this
> change after all. That means rewinding the HEAD back to the previous
> point (i.e. ORIG_HEAD), match the index to that commit, *and* rewind
> the work tree back to the state before the merge (i.e. mostly matching
> the index except for the local changes to Makefile and a handful other
> files). "git reset" is the command to move the HEAD. What mode
> should you use?
>
> Unfortunately, the only two modes that touch the index and/or the work
> tree are --mixed and --hard.
>
> You do not want to use --hard, because it will discard your local
> changes. You do not want to use --mixed either, because it will keep
> not just your local changes but also the changes the merge brought
> into your work tree. That's when you would want to use --merge mode.
>
> $ git reset --merge ORIG_HEAD
Exactly.
> The recovery procedure I would use in such a case (and I rarely pull while
> managing git.git even though I do so in my day job) would be:
>
> $ git checkout -b this-needs-more-work
> $ git checkout master
No, that won't work. The second 'git checkout' does nothing, since
'master' still has all the broken code in it.
It would have worked had you started the new branch _before_ the pull, but
if you had that, then you wouldn't have had anything to reset anyway,
you'd just switch back to master and get rid of the new temp-branch.
But you could probably do something like this:
git checkout -b temp-branch ORIG_HEAD
git branch -M master
which depends on that checkout doing the right thing.
Linus
^ permalink raw reply [flat|nested] 8+ messages in thread
* [RFC/PATCH] Document "git-reset --merge"
2008-12-04 0:39 ` Linus Torvalds
@ 2008-12-04 2:00 ` Junio C Hamano
2008-12-04 2:03 ` Add 'sane' mode to 'git reset' Junio C Hamano
1 sibling, 0 replies; 8+ messages in thread
From: Junio C Hamano @ 2008-12-04 2:00 UTC (permalink / raw)
To: Git Mailing List; +Cc: Linus Torvalds
The commit log message for the feature made it sound as if this is a saner
version of --mixed, but the use case presented makes it clear that it is a
better variant of --hard when your changes and somebody else's changes are
mixed together.
Perhaps we would want to rewrite the example that shows the use of --hard
not to talk about recovering from a failed merge?
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
Documentation/git-reset.txt | 29 ++++++++++++++++++++++++++++-
1 files changed, 28 insertions(+), 1 deletions(-)
diff --git c/Documentation/git-reset.txt i/Documentation/git-reset.txt
index 52aab5e..c542b0c 100644
--- c/Documentation/git-reset.txt
+++ i/Documentation/git-reset.txt
@@ -8,7 +8,7 @@ git-reset - Reset current HEAD to the specified state
SYNOPSIS
--------
[verse]
-'git reset' [--mixed | --soft | --hard] [-q] [<commit>]
+'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]
'git reset' [-q] [<commit>] [--] <paths>...
DESCRIPTION
@@ -45,6 +45,11 @@ OPTIONS
switched to. Any changes to tracked files in the working tree
since <commit> are lost.
+--merge::
+ Resets the index to match the tree recorded by the named commit,
+ and updates the files that are different between the named commit
+ and the current commit in the working tree.
+
-q::
Be quiet, only report errors.
@@ -152,6 +157,28 @@ tip of the current branch in ORIG_HEAD, so resetting hard to it
brings your index file and the working tree back to that state,
and resets the tip of the branch to that commit.
+Undo a merge or pull inside a dirty work tree::
++
+------------
+$ git pull <1>
+Auto-merging nitfol
+Merge made by recursive.
+ nitfol | 20 +++++----
+ ...
+$ git reset --merge ORIG_HEAD <2>
+------------
++
+<1> Even if you may have local modifications in your
+working tree, you can safely say "git pull" when you know
+that the change in the other branch does not overlap with
+them.
+<2> After inspecting the result of the merge, you may find
+that the change in the other branch is unsatisfactory. Running
+"git reset --hard ORIG_HEAD" will let you go back to where you
+were, but it will discard your local changes, which you do not
+want. "git reset --merge" keeps your local changes.
+
+
Interrupted workflow::
+
Suppose you are interrupted by an urgent fix request while you
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: Add 'sane' mode to 'git reset'
2008-12-04 0:39 ` Linus Torvalds
2008-12-04 2:00 ` [RFC/PATCH] Document "git-reset --merge" Junio C Hamano
@ 2008-12-04 2:03 ` Junio C Hamano
1 sibling, 0 replies; 8+ messages in thread
From: Junio C Hamano @ 2008-12-04 2:03 UTC (permalink / raw)
To: Linus Torvalds; +Cc: Git Mailing List
Linus Torvalds <torvalds@linux-foundation.org> writes:
> On Wed, 3 Dec 2008, Junio C Hamano wrote:
>>
>> The recovery procedure I would use in such a case (and I rarely pull while
>> managing git.git even though I do so in my day job) would be:
>>
>> $ git checkout -b this-needs-more-work
>> $ git checkout master
>
> No, that won't work. The second 'git checkout' does nothing, since
> 'master' still has all the broken code in it.
Yeah, brain-o. I should have said
git checkout -b needs-more-work
git branch -f master master~$n
git checkout master
or something like that.
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2008-12-04 2:04 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-12-01 17:30 Add 'sane' mode to 'git reset' Linus Torvalds
2008-12-01 17:44 ` Avery Pennarun
2008-12-01 18:04 ` Jakub Narebski
2008-12-01 18:06 ` Linus Torvalds
2008-12-03 22:22 ` Junio C Hamano
2008-12-04 0:39 ` Linus Torvalds
2008-12-04 2:00 ` [RFC/PATCH] Document "git-reset --merge" Junio C Hamano
2008-12-04 2:03 ` Add 'sane' mode to 'git reset' Junio C Hamano
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).