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

STALE3712d

5 messages, 2 authors, 2016-06-15 · open the first message on its own page

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

From: Simon Sasburg <hidden>
Date: 2016-06-15 22:43:46

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 :-)

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

From: Simon Sasburg <hidden>
Date: 2016-06-15 22:43:46

Signed-off-by: Simon Sasburg <redacted>
---
 git-rebase--interactive.sh |   42 ++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 40 insertions(+), 2 deletions(-)
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

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

From: Simon Sasburg <hidden>
Date: 2016-06-15 22:43:46

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 <redacted>
---
 git-rebase.sh |   63 ++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 54 insertions(+), 9 deletions(-)
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

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

From: Simon Sasburg <hidden>
Date: 2016-06-15 22:43:46

Signed-off-by: Simon Sasburg <redacted>
---
 git-svn.perl |   16 +++++++++++-----
 1 files changed, 11 insertions(+), 5 deletions(-)
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

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

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:43:50

Simon Sasburg [off-list ref] writes:
+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.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help