summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 46b5139)
raw | patch | inline | side by side (parent: 46b5139)
author | Thomas Rast <trast@student.ethz.ch> | |
Sat, 15 Aug 2009 11:48:31 +0000 (13:48 +0200) | ||
committer | Junio C Hamano <gitster@pobox.com> | |
Sat, 15 Aug 2009 22:17:47 +0000 (15:17 -0700) |
This introduces a --patch mode for git-reset. The basic case is
git reset --patch -- [files...]
which acts as the opposite of 'git add --patch -- [files...]': it
offers hunks for *un*staging. Advanced usage is
git reset --patch <revision> -- [files...]
which offers hunks from the diff between the index and <revision> for
forward application to the index. (That is, the basic case is just
<revision> = HEAD.)
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
git reset --patch -- [files...]
which acts as the opposite of 'git add --patch -- [files...]': it
offers hunks for *un*staging. Advanced usage is
git reset --patch <revision> -- [files...]
which offers hunks from the diff between the index and <revision> for
forward application to the index. (That is, the basic case is just
<revision> = HEAD.)
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-reset.txt | patch | blob | history | |
builtin-reset.c | patch | blob | history | |
git-add--interactive.perl | patch | blob | history | |
t/t7105-reset-patch.sh | [new file with mode: 0755] | patch | blob |
index abb25d1c00c97144b1f3709e408fe9cad613e623..469cf6dbacb8de24b5dd0dd78d63dd9ecc8fbd01 100644 (file)
[verse]
'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]
'git reset' [-q] [<commit>] [--] <paths>...
+'git reset' --patch [<commit>] [--] [<paths>...]
DESCRIPTION
-----------
If you want to undo a commit other than the latest on a branch,
linkgit:git-revert[1] is your friend.
-The second form with 'paths' is used to revert selected paths in
-the index from a given commit, without moving HEAD.
+The second and third forms with 'paths' and/or --patch are used to
+revert selected paths in the index from a given commit, without moving
+HEAD.
OPTIONS
and updates the files that are different between the named commit
and the current commit in the working tree.
+-p::
+--patch::
+ Interactively select hunks in the difference between the index
+ and <commit> (defaults to HEAD). The chosen hunks are applied
+ in reverse to the index.
++
+This means that `git reset -p` is the opposite of `git add -p` (see
+linkgit:git-add[1]).
+
-q::
Be quiet, only report errors.
diff --git a/builtin-reset.c b/builtin-reset.c
index 5fa1789d0c2b90ef9ee83d091b67292f2ff0db26..246a127b5f1b0a4935e1cbcb79ce3c4f4b538e6d 100644 (file)
--- a/builtin-reset.c
+++ b/builtin-reset.c
}
}
+static int interactive_reset(const char *revision, const char **argv,
+ const char *prefix)
+{
+ const char **pathspec = NULL;
+
+ if (*argv)
+ pathspec = get_pathspec(prefix, argv);
+
+ return run_add_interactive(revision, "--patch=reset", pathspec);
+}
+
static int read_from_tree(const char *prefix, const char **argv,
unsigned char *tree_sha1, int refresh_flags)
{
int cmd_reset(int argc, const char **argv, const char *prefix)
{
int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
+ int patch_mode = 0;
const char *rev = "HEAD";
unsigned char sha1[20], *orig = NULL, sha1_orig[20],
*old_orig = NULL, sha1_old_orig[20];
"reset HEAD, index and working tree", MERGE),
OPT_BOOLEAN('q', NULL, &quiet,
"disable showing new HEAD in hard reset and progress message"),
+ OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
OPT_END()
};
die("Could not parse object '%s'.", rev);
hashcpy(sha1, commit->object.sha1);
+ if (patch_mode) {
+ if (reset_type != NONE)
+ die("--patch is incompatible with --{hard,mixed,soft}");
+ return interactive_reset(rev, argv + i, prefix);
+ }
+
/* git reset tree [--] paths... can be used to
* load chosen paths from the tree into the index without
* affecting the working tree nor HEAD. */
index 360610314e8e98c4c79a86273696317bf44cb1dd..d14f48c8379cb287e2e1bbe9c8fb7946e6b160fe 100755 (executable)
# command line options
my $patch_mode;
+my $patch_mode_revision;
sub apply_patch;
PARTICIPLE => 'staging',
FILTER => 'file-only',
},
+ 'reset_head' => {
+ DIFF => 'diff-index -p --cached',
+ APPLY => sub { apply_patch 'apply -R --cached', @_; },
+ APPLY_CHECK => 'apply -R --cached',
+ VERB => 'Unstage',
+ TARGET => '',
+ PARTICIPLE => 'unstaging',
+ FILTER => 'index-only',
+ },
+ 'reset_nothead' => {
+ DIFF => 'diff-index -R -p --cached',
+ APPLY => sub { apply_patch 'apply --cached', @_; },
+ APPLY_CHECK => 'apply --cached',
+ VERB => 'Apply',
+ TARGET => ' to index',
+ PARTICIPLE => 'applying',
+ FILTER => 'index-only',
+ },
);
my %patch_mode_flavour = %{$patch_modes{stage}};
return if (!@tracked);
}
- my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';
+ my $reference;
+ if (defined $patch_mode_revision and $patch_mode_revision ne 'HEAD') {
+ $reference = $patch_mode_revision;
+ } elsif (is_initial_commit()) {
+ $reference = get_empty_tree();
+ } else {
+ $reference = 'HEAD';
+ }
for (run_cmd_pipe(qw(git diff-index --cached
--numstat --summary), $reference,
'--', @tracked)) {
sub parse_diff {
my ($path) = @_;
my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
+ if (defined $patch_mode_revision) {
+ push @diff_cmd, $patch_mode_revision;
+ }
my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path);
my @colored = ();
if ($diff_use_color) {
sub process_args {
return unless @ARGV;
my $arg = shift @ARGV;
- if ($arg eq "--patch") {
- $patch_mode = 1;
- $arg = shift @ARGV or die "missing --";
+ if ($arg =~ /--patch(?:=(.*))?/) {
+ if (defined $1) {
+ if ($1 eq 'reset') {
+ $patch_mode = 'reset_head';
+ $patch_mode_revision = 'HEAD';
+ $arg = shift @ARGV or die "missing --";
+ if ($arg ne '--') {
+ $patch_mode_revision = $arg;
+ $patch_mode = ($arg eq 'HEAD' ?
+ 'reset_head' : 'reset_nothead');
+ $arg = shift @ARGV or die "missing --";
+ }
+ } elsif ($1 eq 'stage') {
+ $patch_mode = 'stage';
+ $arg = shift @ARGV or die "missing --";
+ } else {
+ die "unknown --patch mode: $1";
+ }
+ } else {
+ $patch_mode = 'stage';
+ $arg = shift @ARGV or die "missing --";
+ }
die "invalid argument $arg, expecting --"
unless $arg eq "--";
+ %patch_mode_flavour = %{$patch_modes{$patch_mode}};
}
elsif ($arg ne "--") {
die "invalid argument $arg, expecting --";
diff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh
--- /dev/null
+++ b/t/t7105-reset-patch.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='git reset --patch'
+. ./lib-patch-mode.sh
+
+test_expect_success 'setup' '
+ mkdir dir &&
+ echo parent > dir/foo &&
+ echo dummy > bar &&
+ git add dir &&
+ git commit -m initial &&
+ test_tick &&
+ test_commit second dir/foo head &&
+ set_and_save_state bar bar_work bar_index &&
+ save_head
+'
+
+# note: bar sorts before foo, so the first 'n' is always to skip 'bar'
+
+test_expect_success 'saying "n" does nothing' '
+ set_and_save_state dir/foo work work
+ (echo n; echo n) | git reset -p &&
+ verify_saved_state dir/foo &&
+ verify_saved_state bar
+'
+
+test_expect_success 'git reset -p' '
+ (echo n; echo y) | git reset -p &&
+ verify_state dir/foo work head &&
+ verify_saved_state bar
+'
+
+test_expect_success 'git reset -p HEAD^' '
+ (echo n; echo y) | git reset -p HEAD^ &&
+ verify_state dir/foo work parent &&
+ verify_saved_state bar
+'
+
+# The idea in the rest is that bar sorts first, so we always say 'y'
+# first and if the path limiter fails it'll apply to bar instead of
+# dir/foo. There's always an extra 'n' to reject edits to dir/foo in
+# the failure case (and thus get out of the loop).
+
+test_expect_success 'git reset -p dir' '
+ set_state dir/foo work work
+ (echo y; echo n) | git reset -p dir &&
+ verify_state dir/foo work head &&
+ verify_saved_state bar
+'
+
+test_expect_success 'git reset -p -- foo (inside dir)' '
+ set_state dir/foo work work
+ (echo y; echo n) | (cd dir && git reset -p -- foo) &&
+ verify_state dir/foo work head &&
+ verify_saved_state bar
+'
+
+test_expect_success 'git reset -p HEAD^ -- dir' '
+ (echo y; echo n) | git reset -p HEAD^ -- dir &&
+ verify_state dir/foo work parent &&
+ verify_saved_state bar
+'
+
+test_expect_success 'none of this moved HEAD' '
+ verify_saved_head
+'
+
+
+test_done