threads / rfc / 10573

RFC patch, 3 partsStarting rebase from dirty tree

Subject: [RFC PATCH 0/3] Starting rebase from dirty tree

## tl;dr

8 messages between Nov 1, 2007 and Nov 12, 2007. Diffs are folded; open one to read it.

replies: 7people: 3as markdown or json

Simon Sasburg· Nov 1, 2007, 21:30 UTC · lore
These patches allow a --dirty option for git-rebase, git-rebase--interactive, and git-svn rebase.

When --dirty is given, starting from a dirty tree state will be allowed. The difference between HEAD and the index and between the index and the working tree will be committed, these commits will be undone when the rebase is completed.

For me the most often used use case is git-svn rebase one, which i use often. This prevents me form having to commit+reset or stash+unstash everytime i want to do this in a dirty tree.

I recently saw some discussion about a stash-based patch to do the same thing, and decided to share this. These patches are a bit rough in some places, and mostly meant to see what others think if this way of doing this.

One advantage above using stash is that merge conflicts will be presented to the user in the same way as when doing a normal rebase. Also having to explicitly ask for this behaviour with --dirty will hopefully give the user some clue that this isn't a standard operation.

But, in the rebase--interactive case, it allows the user to shoot himself in the foot by re-ordering the commits such that the temporary ones aren't at the end anymore.
Well, comments are welcome :-)
Simon Sasburg· Nov 1, 2007, 21:30 UTC · re: Simon Sasburg · lore

[PATCH 1/3] Introduce --dirty option to git-rebase, allowing you to start from a dirty state.

This will store the difference between HEAD and the index into a commit, and the difference between the index and the working tree into a commit.

When the rebase is done, it restores the index and the working tree by undoing these commits with git-reset.

Signed-off-by: Simon Sasburg <Simon.Sasburg@gmail.com>
---
 git-rebase.sh |   63 ++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 54 insertions(+), 9 deletions(-)
Show changes to git-rebase.sh +54 −9
diff --git a/git-rebase.sh b/git-rebase.sh
index 224cca9..c923c3b 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -42,6 +42,7 @@ To restore the original branch and stop rebasing run \"git rebase --abort\".
 unset newbase
 strategy=recursive
 do_merge=
+fix_dirty=
 dotest=$GIT_DIR/.dotest-merge
 prec=4
 verbose=
@@ -117,9 +118,39 @@ call_merge () {
 
 finish_rb_merge () {
 	rm -r "$dotest"
+	restore_dirty_state
 	echo "All done."
 }
 
+store_dirty_state () {
+	echo "Storing dirty index/working tree"
+	diff=$(git diff --cached)
+	case "$diff" in
+	?*)	git commit -m "REBASE--dirty: store HEAD..index diff"
+		;;
+	esac
+	diff=$(git diff)
+	case "$diff" in
+	?*)	git commit -a -m "REBASE--dirty: store index..workingtree diff"
+		;;
+	esac
+}
+
+restore_dirty_state () {
+	lastmsg=$(git-rev-list HEAD^..HEAD --pretty=oneline | sed "s:[^ ]* ::")
+	if test "$lastmsg" = "REBASE--dirty: store index..workingtree diff"
+	then
+		echo "Restoring dirty index state"
+		git reset --mixed HEAD^
+	fi
+	lastmsg=$(git-rev-list HEAD^..HEAD --pretty=oneline | sed "s:[^ ]* ::")
+	if test "$lastmsg" = "REBASE--dirty: store HEAD..index diff"
+	then
+		echo "Restoring dirty working dir state"
+		git reset --soft HEAD^
+	fi
+}
+
 is_interactive () {
 	test -f "$dotest"/interactive ||
 	while :; do case $#,"$1" in 0,|*,-i|*,--interactive) break ;; esac
@@ -156,6 +187,10 @@ do
 		git am --resolved --3way --resolvemsg="$RESOLVEMSG"
 		exit
 		;;
+	--dirty)
+		do_merge=t
+		fix_dirty=t
+		;;
 	--skip)
 		if test -d "$dotest"
 		then
@@ -188,6 +223,7 @@ do
 			die "No rebase in progress?"
 		fi
 		git reset --hard ORIG_HEAD
+		restore_dirty_state
 		exit
 		;;
 	--onto)
@@ -253,15 +289,19 @@ else
 	fi
 fi
 
-# The tree must be really really clean.
-git update-index --refresh || exit
-diff=$(git diff-index --cached --name-status -r HEAD)
-case "$diff" in
-?*)	echo "cannot rebase: your index is not up-to-date"
-	echo "$diff"
-	exit 1
-	;;
-esac
+# The tree must be really really clean, unless --dirty is given.
+if test "$fix_dirty" = ""
+then
+	git update-index --refresh || exit
+	diff=$(git diff-index --cached --name-status -r HEAD)
+	case "$diff" in
+	?*)	echo "cannot rebase: your index is not up-to-date"
+		echo "$diff"
+		exit 1
+		;;
+	esac
+	
+fi
 
 # The upstream head must be given.  Make sure it is valid.
 upstream_name="$1"
@@ -318,6 +358,11 @@ then
 	GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
 fi
 
+if test "$fix_dirty" = "t"
+then
+	store_dirty_state
+fi
+
 # Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
 echo "First, rewinding head to replay your work on top of it..."
 git-reset --hard "$onto"
-- 
1.5.3.4.502.g37c97
Simon Sasburg· Nov 1, 2007, 21:30 UTC · re: Simon Sasburg · lore

[PATCH 2/3] Implement --dirty for git-rebase--interactive.

Signed-off-by: Simon Sasburg <Simon.Sasburg@gmail.com>
---
 git-rebase--interactive.sh |   42 ++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 40 insertions(+), 2 deletions(-)
Show changes to git-rebase--interactive.sh +40 −2
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 76dc679..326076b 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -25,6 +25,7 @@ REWRITTEN="$DOTEST"/rewritten
 PRESERVE_MERGES=
 STRATEGY=
 VERBOSE=
+FIX_DIRTY=
 test -d "$REWRITTEN" && PRESERVE_MERGES=t
 test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
 test -f "$DOTEST"/verbose && VERBOSE=t
@@ -56,6 +57,35 @@ require_clean_work_tree () {
 	die "Working tree is dirty"
 }
 
+store_dirty_state () {
+	echo "Storing dirty index/working tree"
+	diff=$(git diff --cached)
+	case "$diff" in
+	?*)	git commit -m "REBASE--dirty: store HEAD..index diff"
+		;;
+	esac
+	diff=$(git diff)
+	case "$diff" in
+	?*)	git commit -a -m "REBASE--dirty: store index..workingtree diff"
+		;;
+	esac
+}
+
+restore_dirty_state () {
+	lastmsg=$(git-rev-list HEAD^..HEAD --pretty=oneline | sed "s:[^ ]* ::")
+	if test "$lastmsg" = "REBASE--dirty: store index..workingtree diff"
+	then
+		echo "Restoring dirty index state"
+		git reset --mixed HEAD^
+	fi
+	lastmsg=$(git-rev-list HEAD^..HEAD --pretty=oneline | sed "s:[^ ]* ::")
+	if test "$lastmsg" = "REBASE--dirty: store HEAD..index diff"
+	then
+		echo "Restoring dirty working dir state"
+		git reset --soft HEAD^
+	fi
+}
+
 ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
 
 comment_for_reflog () {
@@ -329,6 +359,7 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	restore_dirty_state &&
 	rm -rf "$DOTEST" &&
 	git gc --auto &&
 	warn "Successfully rebased and updated $HEADNAME."
@@ -378,6 +409,7 @@ do
 			;;
 		esac &&
 		output git reset --hard $HEAD &&
+		restore_dirty_state &&
 		rm -rf "$DOTEST"
 		exit
 		;;
@@ -417,6 +449,9 @@ do
 	''|-h)
 		usage
 		;;
+	--dirty)
+		FIX_DIRTY=t
+		;;
 	*)
 		test -d "$DOTEST" &&
 			die "Interactive rebase already started"
@@ -435,7 +470,7 @@ do
 			;;
 		esac
 
-		require_clean_work_tree
+		test "$FIX_DIRTY" = "t" || require_clean_work_tree
 
 		if test ! -z "$2"
 		then
@@ -445,9 +480,12 @@ do
 				die "Could not checkout $2"
 		fi
 
-		HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
 		UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
 
+		test "$FIX_DIRTY" = "t" && store_dirty_state
+
+		HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
+
 		mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
 
 		test -z "$ONTO" && ONTO=$UPSTREAM
-- 
1.5.3.4.502.g37c97
Simon Sasburg· Nov 1, 2007, 21:30 UTC · re: Simon Sasburg · lore

[PATCH 3/3] Make git-svn rebase --dirty pass along --dirty to git-rebase.

Signed-off-by: Simon Sasburg <Simon.Sasburg@gmail.com>
---
 git-svn.perl |   16 +++++++++++-----
 1 files changed, 11 insertions(+), 5 deletions(-)
Show changes to git-svn.perl +11 −5
diff --git a/git-svn.perl b/git-svn.perl
index 22bb47b..5898a26 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -63,7 +63,7 @@ my ($_stdin, $_help, $_edit,
 	$_message, $_file,
 	$_template, $_shared,
 	$_version, $_fetch_all, $_no_rebase,
-	$_merge, $_strategy, $_dry_run, $_local,
+	$_merge, $_strategy, $_dry_run, $_local, $_dirty,
 	$_prefix, $_no_checkout, $_verbose);
 $Git::SVN::_follow_parent = 1;
 my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
@@ -169,6 +169,7 @@ my %cmd = (
 			  'verbose|v' => \$_verbose,
 			  'strategy|s=s' => \$_strategy,
 			  'local|l' => \$_local,
+			  'dirty|d' => \$_dirty,
 			  'fetch-all|all' => \$_fetch_all,
 			  %fc_opts } ],
 	'commit-diff' => [ \&cmd_commit_diff,
@@ -482,16 +483,20 @@ sub cmd_find_rev {
 }
 
 sub cmd_rebase {
-	command_noisy(qw/update-index --refresh/);
+	unless ($_dirty) {
+		command_noisy(qw/update-index --refresh/);
+	}
 	my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
 	unless ($gs) {
 		die "Unable to determine upstream SVN information from ",
 		    "working tree history\n";
 	}
 	if (command(qw/diff-index HEAD --/)) {
-		print STDERR "Cannot rebase with uncommited changes:\n";
-		command_noisy('status');
-		exit 1;
+		unless ($_dirty) {
+			print STDERR "Cannot rebase with uncommited changes:\n";
+			command_noisy('status');
+			exit 1;
+		}
 	}
 	unless ($_local) {
 		$_fetch_all ? $gs->fetch_all : $gs->fetch;
@@ -697,6 +702,7 @@ sub rebase_cmd {
 	push @cmd, '-v' if $_verbose;
 	push @cmd, qw/--merge/ if $_merge;
 	push @cmd, "--strategy=$_strategy" if $_strategy;
+	push @cmd, "--dirty" if $_dirty;
 	@cmd;
 }
 
-- 
1.5.3.4.502.g37c97
Junio C Hamano· Nov 12, 2007, 19:51 UTC · re: Simon Sasburg · lore

Re: [PATCH 2/3] Implement --dirty for git-rebase--interactive.

Simon Sasburg <simon.sasburg@gmail.com> writes:
Show 14 quoted lines
> +restore_dirty_state () {
> +	lastmsg=$(git-rev-list HEAD^..HEAD --pretty=oneline | sed "s:[^ ]* ::")
> +	if test "$lastmsg" = "REBASE--dirty: store index..workingtree diff"
> +	then
> +		echo "Restoring dirty index state"
> +		git reset --mixed HEAD^
> +	fi
> +	lastmsg=$(git-rev-list HEAD^..HEAD --pretty=oneline | sed "s:[^ ]* ::")
> +	if test "$lastmsg" = "REBASE--dirty: store HEAD..index diff"
> +	then
> +		echo "Restoring dirty working dir state"
> +		git reset --soft HEAD^
> +	fi
> +}

This forces the user to pay the two rev-list overhead, even when running rebase without --dirty option. Can we avoid paying any, when not giving the option?

Also it pollutes the reflog of the branch, but that won't be a huge issue with Dscho's "detach HEAD while rebasing" patch.

Junio C Hamano· Nov 1, 2007, 22:30 UTC · re: Simon Sasburg · lore

Re: [PATCH 1/3] Introduce --dirty option to git-rebase, allowing you to start from a dirty state.

Doesn't this have the exact same problem with the one in 'next' that uses "git-stash create", which Shawn said he was upset about, and I said I will revert?

FYI, this is what I wrote in the log for the revert.
commit 0f49327c9755b6575b447f79b540749d231cb26d
Author: Junio C Hamano <gitster@pobox.com>
Date:   Thu Nov 1 13:46:20 2007 -0700
    Revert "rebase: allow starting from a dirty tree."
    
    This reverts commit 6c9ad166dbbf9e5a0c09450b892151dbec49b8dc.
    Allowing rebase to start in a dirty tree might have been a worthy
    goal, but it is not necessarily always wanted (some people prefer
    to be reminded that the state is dirty, and think about the next
    action that may not be to stash and proceed).  Furthermore, depending
    on the nature of local changes, unstashing the dirty state on top of
    the rebased result is not always desirable.
    
    Signed-off-by: Junio C Hamano <gitster@pobox.com>
Junio C Hamano· Nov 1, 2007, 22:36 UTC · re: Junio C Hamano · lore

Re: [PATCH 1/3] Introduce --dirty option to git-rebase, allowing you to start from a dirty state.

Junio C Hamano <gitster@pobox.com> writes:
> Doesn't this have the exact same problem with the one in 'next'
> that uses "git-stash create", which Shawn said he was upset
> about, and I said I will revert?

Sorry, --dirty is not the default, which changes everything. Forget what I said, sorry for the noise.

Shawn O. Pearce· Nov 2, 2007, 05:08 UTC · re: Junio C Hamano · lore

Re: [PATCH 1/3] Introduce --dirty option to git-rebase, allowing you to start from a dirty state.

Junio C Hamano <gitster@pobox.com> wrote:
Show 8 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
> 
> > Doesn't this have the exact same problem with the one in 'next'
> > that uses "git-stash create", which Shawn said he was upset
> > about, and I said I will revert?
> 
> Sorry, --dirty is not the default, which changes everything.
> Forget what I said, sorry for the noise.

I'm happy with having --dirty, but AS AN OPTION. Heck, I'd probably use it sometimes, but only if it also reapplies the stash after the rebase is complete. But doing that stash/apply automatically is really freaking annoying.

For the same reasons why I like git-checkout not defaulting to -m. I want Git to stop by default if I'm about to possibly go into a command that is going to cause conflicts, as I may not be ready to deal with them right now.

-- 
Shawn.

← back to recent threads