[RFC/PATCH,1/2] rebuash - squash/rebase in a single step
diff mbox series

Message ID 20190630051816.8814-1-eantoranz@gmail.com
State New
Headers show
Series
  • [RFC/PATCH,1/2] rebuash - squash/rebase in a single step
Related show

Commit Message

Edmundo Carmona Antoranz June 30, 2019, 5:18 a.m. UTC
Rebuash allows us to do squash/rebase without having to resort
to use rebase.

Consider the case where we have feature branches with merges in history:

Rx: revisions in main branch
Fx: Revisions in feature branch
Mx: Merge revisions (some of them with conflicts)

------------
	R1---R2---R3---R4---R5---R6---R7
	 \         \              \
	  F1---F2---M1---F3---F4---M2---F5
------------

If on M1 there were conflicts, it's more than likely that if we tried to
cherry-pick R1..F2 on top of R7, we will have to deal with a conflict. But that
conflict has already been taken care of on M1. So, in order to leverage that
work that has already been done, instead of looking back, we look forward.

First, we create a (temporary) merge commit of both branches (M3)

------------
	R1---R2---R3---R4---R5---R6---R7---M3
	 \         \              \       /
	  F1---F2---M1---F3---F4---M2---F5
------------

At this point, all differences between M3 and R7 are the changes related to the
feature branch, so we can run git reset --soft from M3 to R7 to put all those
differeces in index, and then we create single revision that is both
squashed/rebased for our feature branch.
---
 .gitignore                    |   1 +
 Documentation/git-rebuash.txt |  76 ++++++++
 Makefile                      |   1 +
 command-list.txt              |   1 +
 git-rebuash.sh                | 324 ++++++++++++++++++++++++++++++++++
 5 files changed, 403 insertions(+)
 create mode 100644 Documentation/git-rebuash.txt
 create mode 100644 git-rebuash.sh

Comments

Jeff King June 30, 2019, 6:53 a.m. UTC | #1
On Sat, Jun 29, 2019 at 11:18:15PM -0600, Edmundo Carmona Antoranz wrote:

> Rebuash allows us to do squash/rebase without having to resort
> to use rebase.
> 
> Consider the case where we have feature branches with merges in history:
> 
> Rx: revisions in main branch
> Fx: Revisions in feature branch
> Mx: Merge revisions (some of them with conflicts)
> 
> ------------
> 	R1---R2---R3---R4---R5---R6---R7
> 	 \         \              \
> 	  F1---F2---M1---F3---F4---M2---F5
> ------------
> 
> If on M1 there were conflicts, it's more than likely that if we tried to
> cherry-pick R1..F2 on top of R7, we will have to deal with a conflict. But that
> conflict has already been taken care of on M1. So, in order to leverage that
> work that has already been done, instead of looking back, we look forward.
> 
> First, we create a (temporary) merge commit of both branches (M3)
> 
> ------------
> 	R1---R2---R3---R4---R5---R6---R7---M3
> 	 \         \              \       /
> 	  F1---F2---M1---F3---F4---M2---F5
> ------------
> 
> At this point, all differences between M3 and R7 are the changes related to the
> feature branch, so we can run git reset --soft from M3 to R7 to put all those
> differeces in index, and then we create single revision that is both
> squashed/rebased for our feature branch.

So if I understand correctly, our goal is:

  R1--R2--...--R7--R8

where R8 has the same tree as M3?

Wouldn't doing "git merge --squash" do the same thing?

If I set up that scenario like so:

  git init repo
  cd repo

  commit() {
    for i in "$@"; do echo $i >file && git add file && git commit -m $i; done
  }

  commit R1 R2 R3
  git checkout -b feature HEAD~2
  commit F1 F2
  git merge master
  commit M1
  git checkout master
  commit R4 R5 R6
  git checkout feature
  commit F3 F4
  git merge master
  commit M2 F5
  git checkout master
  commit R7

and then do:

  git merge --squash feature

I get the same merge that rebuash is doing (with R6 as the merge base,
so we see F5 and R7 conflicting with each other). And then when I finish
it with "git commit", the result is a linear strand with M3 at the tip
(and its commit message is even auto-populated with information from the
squashed commits).

-Peff
Edmundo Carmona Antoranz June 30, 2019, 3:09 p.m. UTC | #2
On Sun, Jun 30, 2019 at 12:54 AM Jeff King <peff@peff.net> wrote:
>
>
> and then do:
>
>   git merge --squash feature
>
> I get the same merge that rebuash is doing (with R6 as the merge base,
> so we see F5 and R7 conflicting with each other). And then when I finish
> it with "git commit", the result is a linear strand with M3 at the tip
> (and its commit message is even auto-populated with information from the
> squashed commits).
>
> -Peff

From the point of view of the revisions that you produce in the end,
it's the same thing, but you are not rebasing/squashing your feature
branch, you are moving your upstream branch to where you want the
squashed/rebased branch to be. So, in fact you would need more steps,
something like (starting from your feature branch being checked out):

git checkout --detach $( git rev-parse --abbrev-ref --symbolic-full-name @{u} )
git merge --squash my-feature-branch
git branch -f my-feature-branch
git checkout my-feature-branch

Yes, it works. Only that with rebuash you would do (starting from the
feature branch being checked out branch):

git rebuash

as long as the upstream branch is set, of course.

I think it makes more sense in terms of development flow of feature
branches, if you know in the end you will give up a squashed branch:

modify
commit
modify
commit
git pull # no need to use pull --rebase, merges will be fine
modify
commit
modify
commit
git pull
git modify
# now I'm ready to rebase/squash
git fetch
git rebuash

adding history could be done with an additional option (--hist
(default) and --no-hist?)

But, as you said, it's not like it's not possible to do it (with a
little more effort) with available tools like merge --squash
Jeff King June 30, 2019, 10:39 p.m. UTC | #3
On Sun, Jun 30, 2019 at 09:09:31AM -0600, Edmundo Carmona Antoranz wrote:

> >   git merge --squash feature
> >
> > I get the same merge that rebuash is doing (with R6 as the merge base,
> > so we see F5 and R7 conflicting with each other). And then when I finish
> > it with "git commit", the result is a linear strand with M3 at the tip
> > (and its commit message is even auto-populated with information from the
> > squashed commits).
> 
> From the point of view of the revisions that you produce in the end,
> it's the same thing, but you are not rebasing/squashing your feature
> branch, you are moving your upstream branch to where you want the
> squashed/rebased branch to be. So, in fact you would need more steps,
> something like (starting from your feature branch being checked out):

Ah, OK, that's what I was missing. I agree that "merge --squash" isn't
quite what you want, then. It's sort of a "reverse squash": do the
merge, but use the _other_ side as the parent of the new squash commit.

I wonder if it might be easier to implement as an option to git-merge.
I noticed when I hit a conflict with rebuash that it emphatically told
me not to use "git commit" once I had resolved everything. If this were
just a special case of a merge, that might be a bit more seamless
(though I imagine it might still require teaching git-commit about the
feature, since it generally wants to mark HEAD as the parent).

> I think it makes more sense in terms of development flow of feature
> branches, if you know in the end you will give up a squashed branch:

Yeah, I agree it is a separate operation that by itself makes sense. I
do wonder a little why you'd care about squashing on the branch. If
you're eventually going to squash the whole thing anyway, you don't care
about a messy history. So you can just continue to back-merge from
master into the feature branch and build on top.

But perhaps the squashed version is easier to work with for further
modifications? I'm not sure how, though. Certainly in your example
rewriting changes in F1 with "rebase --interactive" would be a pain. But
I think the end-state of the tree after your rebuash is identical to
what you'd get by just merging from master. So in either case, just
building new work on top should be the same.

> But, as you said, it's not like it's not possible to do it (with a
> little more effort) with available tools like merge --squash

Yeah, but I agree with you that just because it is possible to do
something does not mean that it is not a good idea to make the workflow
easier or safer.

I'm still not quite sure of the greater workflow where having the
rebuash-ed commit on the feature branch is more useful than just having
a merge from master.

-Peff
Edmundo Carmona Antoranz July 1, 2019, 1:37 a.m. UTC | #4
On Sun, Jun 30, 2019 at 4:39 PM Jeff King <peff@peff.net> wrote:
>
>
> But perhaps the squashed version is easier to work with for further
> modifications? I'm not sure how, though. Certainly in your example
> rewriting changes in F1 with "rebase --interactive" would be a pain. But
> I think the end-state of the tree after your rebuash is identical to
> what you'd get by just merging from master. So in either case, just
> building new work on top should be the same.

> I'm still not quite sure of the greater workflow where having the
> rebuash-ed commit on the feature branch is more useful than just having
> a merge from master.

Hmm... I as a gatekeeper would rather get either a straight line of
revisions for a feature with no merges (even if a final merge takes
care of solving conflicts with the upstream branch) or a single
revision (if I thought that the change is not worth having more than a
single revision). I'd ask the developer to rebase the whole thing and
give a straight line (with rebase -i or cherry-picks) or to give me a
single revision (where rebuash would come into the picture).

Also, I wonder how it would make life easier for people that are
learning to use git and the command that they see thrown around very
often is to use `git pull` in order to get updates from the other
developers.

But that might be me being opinionated.

PS About rebuash ordering not to use commit: Sure, at the moment,
rebuash is not commit-safe.... or merge-continue-safe.... but I can
add checks for that in case the user runs them before using rebuash
--continue
Junio C Hamano July 1, 2019, 6:35 p.m. UTC | #5
Jeff King <peff@peff.net> writes:

>> First, we create a (temporary) merge commit of both branches (M3)
>> 
>> ------------
>> 	R1---R2---R3---R4---R5---R6---R7---M3
>> 	 \         \              \       /
>> 	  F1---F2---M1---F3---F4---M2---F5
>> ------------
>> 
>> At this point, all differences between M3 and R7 are the changes related to the
>> feature branch, so we can run git reset --soft from M3 to R7 to put all those
>> differeces in index, and then we create single revision that is both
>> squashed/rebased for our feature branch.
>
> So if I understand correctly, our goal is:
>
>   R1--R2--...--R7--R8
>
> where R8 has the same tree as M3?
>
> Wouldn't doing "git merge --squash" do the same thing?

Yup, from Edmundo's description, I agree that they are equivalent,
modulo the merge direction.

That affects two things, though.  Who becomes the first parent is
obviously swapped, but equally importantly, the merge conflicts are
presented as if you are merging from the upstream, taking assortment
of random changes into a stale codebase with slight modification.
Swapping the direction would present the merge much better in that
it let you pretend as if you started from the up-to-date upstream
and replayed your own changes in the topic, and because you are by
definition more familiar with your own changes, during conflict
resolution, you would understand the output from "git diff HEAD"
much better than the case where you merge upstream into your topic.

But "rebase the feature branch on updated upstream and then merge
the result, optionally squashing all of them into one big ball of
wax" *could* be a lot more useful feature, serving as poor-man's
imitation of "git imerge", *but* only if the final squashing is made
optional (that at the same time means that sometimes the M3 merge
can be unmanageably messy and stepwise rebase may make it
manageable).  

If M3 merge is always easier to manage than incremental stepwise
rebase of the topic, then doing the "git merge --reverse-squash"
would be a saner interface and also conceptually simpler.
Junio C Hamano July 1, 2019, 6:50 p.m. UTC | #6
Edmundo Carmona Antoranz <eantoranz@gmail.com> writes:

> Hmm... I as a gatekeeper would rather get either a straight line of
> revisions for a feature with no merges (even if a final merge takes
> care of solving conflicts with the upstream branch) or a single
> revision (if I thought that the change is not worth having more than a
> single revision). I'd ask the developer to rebase the whole thing and
> give a straight line (with rebase -i or cherry-picks) or to give me a
> single revision (where rebuash would come into the picture).

That part is understandable, but is "rebase-and-squash" a tool
intended to be used by the contributor to respond to that request?

Wouldn't the developer just do

	git checkout topic
	git fetch
	git rebase [-i] [@{upstream}]
	git push [publish +topic]

to update the topic and ask to be pulled again?  The two steps in
the middle may be "pull --rebase", but my point is I do not quite
see where the new squash/rebase-in-a-single-step thing comes into
this picture.  There may be a different picture that it fits, but
I do not think it is this one.
Edmundo Carmona Antoranz July 1, 2019, 8:48 p.m. UTC | #7
On Mon, Jul 1, 2019 at 12:51 PM Junio C Hamano <gitster@pobox.com> wrote:
>
>
> That part is understandable, but is "rebase-and-squash" a tool
> intended to be used by the contributor to respond to that request?
>
> Wouldn't the developer just do
>
>         git checkout topic
>         git fetch
>         git rebase [-i] [@{upstream}]
>         git push [publish +topic]
>
> to update the topic and ask to be pulled again?  The two steps in
> the middle may be "pull --rebase", but my point is I do not quite
> see where the new squash/rebase-in-a-single-step thing comes into
> this picture.  There may be a different picture that it fits, but
> I do not think it is this one.
>
>

I think rebasing -i makes sense* _if_ there are no conflicts on the
way to reach the current state of the branch if the developer was
pulling while developing the feature branch. If there are conflicts
that I took care of when i was pulling, i don't want to run rebase -i
to have to deal with them yet again. So rebuash would help developers
with or without merges with upstream on their feature branch, with or
without conflicts on those merges (if there are merges), get their
development into a single revision without having to write 4 or 5 git
commands, as Jeff was saying, if it makes workflows simpler....

* rebase -i is the way I see people solve their "squash and rebase"
needs (pick the first revision, then squash all the others... but what
happens if the very first revision is conflicting with the current
state of upstream?), but I (for one) do it the way rebuash is doing:
merge with upstream, reset --soft to upstream, commit, voila! That's
why i wrote rebuash in the first place, just so you can see where I'm
coming from.
Derrick Stolee July 2, 2019, 11:37 a.m. UTC | #8
On 7/1/2019 2:35 PM, Junio C Hamano wrote:
> Jeff King <peff@peff.net> writes:
> 
>>> First, we create a (temporary) merge commit of both branches (M3)
>>>
>>> ------------
>>> 	R1---R2---R3---R4---R5---R6---R7---M3
>>> 	 \         \              \       /
>>> 	  F1---F2---M1---F3---F4---M2---F5
>>> ------------
>>>
>>> At this point, all differences between M3 and R7 are the changes related to the
>>> feature branch, so we can run git reset --soft from M3 to R7 to put all those
>>> differeces in index, and then we create single revision that is both
>>> squashed/rebased for our feature branch.
>>
>> So if I understand correctly, our goal is:
>>
>>   R1--R2--...--R7--R8
>>
>> where R8 has the same tree as M3?
>>
>> Wouldn't doing "git merge --squash" do the same thing?
> 
> Yup, from Edmundo's description, I agree that they are equivalent,
> modulo the merge direction.

[snip]

> If M3 merge is always easier to manage than incremental stepwise
> rebase of the topic, then doing the "git merge --reverse-squash"
> would be a saner interface and also conceptually simpler.

I agree that this would be a better way to expose this behavior,
and likely the implementation could be very clean.

Thanks,
-Stolee
Junio C Hamano July 2, 2019, 5:20 p.m. UTC | #9
Derrick Stolee <stolee@gmail.com> writes:

> On 7/1/2019 2:35 PM, Junio C Hamano wrote:
>> Jeff King <peff@peff.net> writes:
>> 
>>>> First, we create a (temporary) merge commit of both branches (M3)
>>>>
>>>> ------------
>>>> 	R1---R2---R3---R4---R5---R6---R7---M3
>>>> 	 \         \              \       /
>>>> 	  F1---F2---M1---F3---F4---M2---F5
>>>> ------------
>>>>
> ...
>> If M3 merge is always easier to manage than incremental stepwise
>> rebase of the topic, then doing the "git merge --reverse-squash"
>> would be a saner interface and also conceptually simpler.
>
> I agree that this would be a better way to expose this behavior,
> and likely the implementation could be very clean.

What I was sort-of hoping to get comments on was actually something
else.

Would there be cases where the merge M3 gets unmanageably complex
even if rebasing the feature commits one by one is relatively simple
(and how often would that happen)?  "merge --squash" would not work
well (and extending the command to merge in a different direction
would not help) in such a situation, but "rebase -i" would work
much better (and "imerge" would, too).
Johannes Sixt July 2, 2019, 7:30 p.m. UTC | #10
Am 02.07.19 um 19:20 schrieb Junio C Hamano:
> Derrick Stolee <stolee@gmail.com> writes:
> 
>> On 7/1/2019 2:35 PM, Junio C Hamano wrote:
>>> Jeff King <peff@peff.net> writes:
>>>
>>>>> First, we create a (temporary) merge commit of both branches (M3)
>>>>>
>>>>> ------------
>>>>> 	R1---R2---R3---R4---R5---R6---R7---M3
>>>>> 	 \         \              \       /
>>>>> 	  F1---F2---M1---F3---F4---M2---F5
>>>>> ------------
>>>>>
>> ...
>>> If M3 merge is always easier to manage than incremental stepwise
>>> rebase of the topic, then doing the "git merge --reverse-squash"
>>> would be a saner interface and also conceptually simpler.
>>
>> I agree that this would be a better way to expose this behavior,
>> and likely the implementation could be very clean.
> 
> What I was sort-of hoping to get comments on was actually something
> else.
> 
> Would there be cases where the merge M3 gets unmanageably complex
> even if rebasing the feature commits one by one is relatively simple
> (and how often would that happen)?  "merge --squash" would not work
> well (and extending the command to merge in a different direction
> would not help) in such a situation, but "rebase -i" would work
> much better (and "imerge" would, too).

I've come across the situation occasionally. Once I have resolved a
bunch of conflicts in M1 and M2, I think twice whether I should rebase
individual commits; it is typically much more tedious.

A common situation is that a line is "A" in F1, "B" in F2, and "C" in
R3; then I have to resolve ONE conflict in M1 ("<B=C>"), but with
individual commits rebased on top of R3, I have two conflicts, "<C=A>"
and "<AC=B>", neither of which is helped by rerere.

After merges M1 and M2, it is all a done deal, and M3 is typically a
much simpler merge than the sum of conflicts incurred by the individual
commits. I would generally not recommend a rebase in this situation.

But I wouldn't turn M3 into a squashed merged commit, either, as long as
F1...F5 aren't messy.

-- Hannes

Patch
diff mbox series

diff --git a/.gitignore b/.gitignore
index 4470d7cfc0..46b5c94c8f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -123,6 +123,7 @@ 
 /git-read-tree
 /git-rebase
 /git-rebase--preserve-merges
+/git-rebuash
 /git-receive-pack
 /git-reflog
 /git-remote
diff --git a/Documentation/git-rebuash.txt b/Documentation/git-rebuash.txt
new file mode 100644
index 0000000000..721da64010
--- /dev/null
+++ b/Documentation/git-rebuash.txt
@@ -0,0 +1,76 @@ 
+git-rebuash(1)
+=============
+
+NAME
+----
+git-rebuash - Squash/Rebase in a single shot
+
+
+SYNOPSIS
+--------
+[verse]
+'git rebuash' [-m <msg>] [ -u <upstream> ]
+'git rebuash' --continue
+'git rebuash' --abort
+
+DESCRIPTION
+-----------
+Rebuash allows us to do squash/rebase without having to resort
+to use rebase.
+
+Consider the case where we have feature branches with merges in history:
+
+Rx: revisions in main branch
+Fx: Revisions in feature branch
+Mx: Merge revisions (some of them with conflicts)
+
+------------
+	R1---R2---R3---R4---R5---R6---R7
+	 \         \              \
+	  F1---F2---M1---F3---F4---M2---F5
+------------
+
+If on M1 there were conflicts, it's more than likely that if we tried to
+cherry-pick R1..F2 on top of R7, we will have to deal with a conflict. But that
+conflict has already been taken care of on M1. So, in order to leverage that
+work that has already been done, instead of looking back, we look forward.
+
+First, we create a (temporary) merge commit of both branches (M3)
+
+------------
+	R1---R2---R3---R4---R5---R6---R7---M3
+	 \         \              \       /
+	  F1---F2---M1---F3---F4---M2---F5
+------------
+
+At this point, all differences between M3 and R7 are the changes related to the
+feature branch, so we can run git reset --soft from M3 to R7 to put all those
+differeces in index, and then we create single revision that is both
+squashed/rebased for our feature branch.
+
+git rebuash will take care of all the details, even figuring out
+what the upstream branch is if it is not provided.
+
+git checkout feature1
+git rebuash -u upstream-branch -m "Feature 1 in a single revision"
+
+
+OPTIONS
+-------
+
+-m <msg>::
+	Set the commit message to be used for the merge commit
+
+-u <upstream>::
+	Set the upstream branch. If it is not provided, the upstream branch
+	from the currently checked out branch will be used.
+
+--abort::
+	Abort the current rebuash process and go back
+	to the original state before starting rebuash
+
+--continue::
+	If rebuash stopped (due to a conflict of an empty commit message),
+	allow the process to go on after solving the current issue that made it
+	stop.
+
diff --git a/Makefile b/Makefile
index f58bf14c7b..c2869d7c11 100644
--- a/Makefile
+++ b/Makefile
@@ -614,6 +614,7 @@  SCRIPT_SH += git-merge-resolve.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-quiltimport.sh
 SCRIPT_SH += git-legacy-stash.sh
+SCRIPT_SH += git-rebuash.sh
 SCRIPT_SH += git-request-pull.sh
 SCRIPT_SH += git-submodule.sh
 SCRIPT_SH += git-web--browse.sh
diff --git a/command-list.txt b/command-list.txt
index 3a9af104b5..cf1cb48fde 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -143,6 +143,7 @@  git-quiltimport                         foreignscminterface
 git-range-diff                          mainporcelain
 git-read-tree                           plumbingmanipulators
 git-rebase                              mainporcelain           history
+git-rebuash                             mainporcelain           history
 git-receive-pack                        synchelpers
 git-reflog                              ancillarymanipulators           complete
 git-remote                              ancillarymanipulators           complete
diff --git a/git-rebuash.sh b/git-rebuash.sh
new file mode 100644
index 0000000000..93c14cb9cc
--- /dev/null
+++ b/git-rebuash.sh
@@ -0,0 +1,324 @@ 
+#!/bin/bash
+# Copyright 2019 Edmundo Carmona Antoranz
+
+# Released under the terms of GPLv2
+
+# Rebuash will be used mainly when you want to squash and rebase
+# so that you don't have to go through the ordeal of rebasing
+# which would potencially bring conflicts that you would
+# rather avoid it at all possible.
+
+# rebuash takes a different strategy by merging with the target (upstream) branch
+# then resetting --soft onto the target branch to finish off with a new
+# revision
+
+# Valid options:
+# -u upstream branch (if it's not specified, it will be retrieved from current branch)
+# -m comment for the final revision (can be multiline)
+# --abort if the user wants to abort the rebuash operation
+# --continue if there was a previous conflict and the user wants to continue with the current rebuash operation
+
+UPSTREAM="" # upstream branch (will be retrieved from current branch if it is not provided)
+HEAD="" # the point where we started rebuash from (revision or branch, for messaging)
+HEAD_REV="" # point where we started the operation from (the revision)
+STEP="" # will be used when we need to continue to know in which step we are at the moment
+COMMENT="" # comment to be used on the final revision (might be empty)
+
+# actions
+START=true
+CONTINUE=false
+ABORT=false
+
+. git-sh-setup
+
+STATE_FILE="$GIT_DIR"/REBUASH_STATE
+
+function report_bug {
+    echo "You just hit a bug in git-rebuash"
+    echo "BUG: $1"
+    echo "Please, report this problem to the git mailing list and cc eantoranz at gmail.com"
+    exit 1
+}
+
+function save_state {
+    echo "UPSTREAM: $UPSTREAM" > "$STATE_FILE"
+    echo "HEAD: $HEAD" >> "$STATE_FILE"
+    echo "HEAD_REV: $HEAD_REV" >> "$STATE_FILE"
+    echo "STEP: $STEP" >> "$STATE_FILE"
+    echo "" >> "$STATE_FILE" # an empty line
+    echo "$COMMENT" >> "$STATE_FILE"
+}
+
+function read_state {
+    # read the content of state file and put it into the variables
+    if [ ! -f "$STATE_FILE" ]
+    then
+        echo Unexpected Error: Rebuash state file was expected to exist.
+        echo Aborting operation.
+        echo Please, notify the git mail list and explain them what the situation was
+        echo so that this bug can be taken care of.
+        exit 1
+    fi
+    UPSTREAM=$( head -n 1 "$STATE_FILE" | sed 's/UPSTREAM: //' )
+    HEAD=$( head -n 2 "$STATE_FILE" | tail -n 1 | sed 's/HEAD: //' )
+    HEAD_REV=$( head -n 3 "$STATE_FILE" | tail -n 1 | sed 's/HEAD_REV: //' )
+    STEP=$( head -n 4 "$STATE_FILE" | tail -n 1 | sed 's/STEP: //' )
+    # there is an empty line
+    COMMENT=$( tail -n +6 "$STATE_FILE" )
+}
+
+function remove_state {
+    if [ -f "$STATE_FILE" ]
+    then
+        rm -f "$STATE_FILE"
+    fi
+}
+
+function check_status {
+    if [ $CONTINUE == true ]
+    then
+        if [ $ABORT == true ]
+        then
+            echo Can\'t use --abort and --continue at the same time
+            exit 1
+        fi
+
+        # there has to be an state file
+        if [ ! -f "$STATE_FILE" ]
+        then
+            echo There\'s no rebuash session currently going on. Can\'t continue.
+            exit 1
+        fi
+    elif [ $ABORT == true ]
+    then
+        if [ ! -f "$STATE_FILE" ]
+        then
+            echo There\'s no rebuash session currently going on. Can\'t abort.
+            exit 1
+        fi
+    else
+        if [ $START != true ]
+        then
+            report_bug "START is set to false even though we were not aborting or continuing"
+        fi
+
+        if [ "$UPSTREAM" == "" ]
+        then
+            # as a fallback, try to get upstream from current branch
+            UPSTREAM=$( git rev-parse --abbrev-ref --symbolic-full-name @{u} 2> /dev/null )
+            if [ "$UPSTREAM" == "" ]
+            then
+                echo "Could not find out upstream branch. Please provide it with -u"
+                exit 1
+            else
+                echo Using $UPSTREAM as the upstream branch
+            fi
+        fi
+
+        # starting execution
+        # there must _not_ be anything going on
+        status=$( git status --short --untracked-files=no )
+        if [ "$status" != "" ]
+        then
+            echo Status is not clean before rebuashing.
+            echo Make sure to clean up your working tree before starting rebuash
+            exit 1
+        fi
+    fi
+}
+
+# Parse arguments
+function parse_options {
+
+    while true
+    do
+        value=$1
+        shift
+        if [ $? -ne 0 ]
+        then
+            # no more parameters
+            break
+        fi
+
+        if [ "$value" == "-u" ]
+        then
+            # user wants to specify the upstream branch
+            UPSTREAM="$1"
+            shift
+        elif [ "$value" == "-m" ]
+        then
+            # user wants to set up the comment for the commit
+            COMMENT="$1"
+            shift
+        elif [ "$value" == "--continue" ]
+        then
+            # user wants to resume execution
+            CONTINUE=true
+            START=false
+        elif [ "$value" == "--abort" ]
+        then
+            ABORT=true
+            START=false
+        fi
+    done
+}
+
+# Start execution of rebuash
+function start_rebuash {
+
+    # there must not exist a previous state file
+    if [ -f "$STATE_FILE" ]
+    then
+        echo You are in the middle of a previous rebuash execution
+        echo If that is not the case, remove the file "$STATE_FILE"
+        echo and also feel free to file a report with int git mail list
+        exit 1
+    fi
+
+    git show "$UPSTREAM" &> /dev/null
+    if [ $? -ne 0 ]
+    then
+        echo "Provided upstream ($UPSTREAM) does not exist"
+        exit 1
+    fi
+
+    # persist execution information
+    HEAD=$( git rev-parse --abbrev-ref --symbolic-full-name @ 2> /dev/null )
+    HEAD_REV=$( git show --quiet --pretty="%H" )
+    save_state
+
+    # start doing our magic
+    echo "Merging with upstream branch ($UPSTREAM)"
+    git merge --no-ff --no-commit "$UPSTREAM" 2> /dev/null
+
+    # we let the process move forward as if we are continuing
+    STEP=MERGE
+    save_state # continue_rebuash will read the state from file
+    CONTINUE=true
+    return
+
+}
+
+function continue_rebuash {
+    read_state
+    if [ "$STEP" == "" ]
+    then
+        report_bug "Bug: Can't determine in what step we are in order to do --continue."
+    fi
+
+    if [ "$STEP" == "MERGE" ]
+    then
+        git -c core.editor=/bin/true merge --continue &> /dev/null # do not open editor, use previous comment
+
+        if [ $? -ne 0 ]
+        then
+            save_state # just in case we add more _previous_ STEPS later on
+            echo "There are unmerged paths to take care of (or tracked and pending to be added to index)"
+            echo Check with git status
+            echo "Finish them (_DO NOT_ commit nor run git merge --continue). Then run:"
+            echo
+            echo git rebuash --continue
+            echo
+            echo You can also run git rebuash --abort if you would like to stop the whole process
+            echo and go back to where you were before rebuashing
+            exit 1
+        fi
+        STEP=RESET
+    fi
+
+    if [ "$STEP" == "RESET" ]
+    then
+        # move branch pointer to UPSTREAM
+        git reset --soft "$UPSTREAM"
+
+        # merge/reset went fine so we set the STEP to COMMIT
+        STEP=COMMIT
+    fi
+
+    if [ "$STEP" == "COMMIT" ]
+    then
+        # create new revision
+        if [ "$COMMENT" == "" ]
+        then
+            # no comment was provided
+            # what was checked out when we started?
+            if [ "$HEAD" == "HEAD" ]
+            then
+                # was working on detached head
+                TEMP_COMMENT="Rebuashing $HEAD_REV on top of $UPSTREAM"
+            else
+                TEMP_COMMENT="Rebuashing $HEAD on top of $UPSTREAM"
+            fi
+            git commit -m "$TEMP_COMMENT" --edit
+        else
+            git commit -m "$COMMENT"
+        fi
+
+        if [ $? -ne 0 ]
+        then
+            # there was some error while committing
+            save_state
+
+            echo There was an error while committing.
+            echo Aborting rebuash operation.
+            echo When you are ready to finish, run:
+            echo git rebuash --continue
+            exit 1
+        fi
+        STEP=END # just in case
+    fi
+
+    # everything went fine
+    remove_state
+    echo Rebuash operation was successful.
+    exit 0
+}
+
+function abort_rebuash {
+    # take it back to the original state
+    # make sure there was an state to start from
+    read_state
+
+    # most cases, we are in the middle of the merge operation
+    if [ "$STEP" == "" ]
+    then
+        echo Bug: Could not figure out the step we are in.
+        echo Please, report this problem to the git mail list
+        exit 1
+    fi
+    if [ "$STEP" == "MERGE" ]
+    then
+        # can ask to abort merge and we go back to where we were when we started
+        git merge --abort &> /dev/null
+    elif [ "$STEP" == "COMMIT" ]
+    then
+        # need to got back to where we were before
+        git reset --hard "$HEAD_REV" &> /dev/null
+    else
+        report_bug "Unknown step ($STEP)"
+    fi
+
+    remove_state
+
+    echo Rebuash was successfully aborted
+    exit 0
+}
+
+parse_options "$@"
+
+check_status
+
+if [ $START == true ]
+then
+    start_rebuash
+fi
+
+if [ $CONTINUE == true ]
+then
+    continue_rebuash
+fi
+
+if [ $ABORT == true ]
+then
+    abort_rebuash
+fi