[RFC][PATCH] Branch history

Subsystems: kernel build + files below scripts/ (unless maintained elsewhere), the rest

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

[RFC][PATCH] Branch history

From: Eric W. Biederman <hidden>
Date: 2016-06-15 22:42:35

The problem:
git-rebase, stgit and the like destructively edit the commit history
on a branch.  Making it a challenge to go back to a known good point.

revlog and the like sort of help this but they don't address the
issues that they capture irrelevant points and are not git-prune safe.

With current git the best technique I have found is to always make
a new branch before I would call git-rebase.




After thinking about the problem some more I believe I have found
a rather simple solution to the problem of keeping branch history.

For each branch you want to keep the history of keep 2 branches.
A normal working branch, and a second archive branch that records
the history of the branch you are editing.

The history can be kept simply by placing an additional commit on the
top of each branch.  The new commit on top of each branch will point
to the same tree object as the previous top commit on the branch but
it will have 2 parent commit objects.  The first parent commit object
is the previous top commit object of the branch.  The second parent
commit object is the commit object on top of the previous version of
this branch.

The work flow is you edit a branch to your hearts comment then when
you get to an interesting point you commit the branch to your archive
branch so you can keep track of things.

To gitk and friends the archive branch looks like a series of branch
merges where one input branch is always the same as the merge result.
So all of the git tools work normally.

The implementation is trivial.

The neat thing is that it gives an immutable history of a branch that
is actively being edited.  So if you export your archive branch people
will never see time roll backward.



Below is my patch to implement this idea.  Currently I am storing
the archive branch in .git/refs/archive/$branchname.  And calling
the command to commit a branch git-archive-branch.

I think my initial naming is most likely lacking so suggestions
for something better would be appreciated.

Comments?


Eric
diff --git a/Makefile b/Makefile
index 700c77f..411ae95 100644
--- a/Makefile
+++ b/Makefile
@@ -150,7 +150,7 @@ SCRIPT_SH = \
 	git-applymbox.sh git-applypatch.sh git-am.sh \
 	git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
 	git-merge-resolve.sh git-merge-ours.sh \
-	git-lost-found.sh git-quiltimport.sh
+	git-lost-found.sh git-quiltimport.sh git-archive-branch.sh
 
 SCRIPT_PERL = \
 	git-archimport.perl git-cvsimport.perl git-relink.perl \
diff --git a/git-archive-branch.sh b/git-archive-branch.sh
new file mode 100755
index 0000000..00638de
--- /dev/null
+++ b/git-archive-branch.sh
@@ -0,0 +1,131 @@
+#!/bin/sh
+
+USAGE='[-m <message> | -F logfile] [-e]'
+
+. git-sh-setup
+
+headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')
+headsha1=$(git-rev-parse "$headref")
+archiveref="refs/archive/$headref"
+
+
+logfile=
+edit_flag=
+no_edit=
+log_given=
+log_message=
+while case "$#" in 0) break;; esac
+do
+  case "$1" in
+  -F|--F|-f|--f|--fi|--fil|--file)
+      case "$#" in 1) usage ;; esac
+      shift
+      no_edit=t
+      log_given=t$log_given
+      logfile="$1"
+      shift
+      ;;
+  -F*|-f*)
+      no_edit=t
+      log_given=t$log_given
+      logfile=`expr "z$1" : 'z-[Ff]\(.*\)'`
+      shift
+      ;;
+  --F=*|--f=*|--fi=*|--fil=*|--file=*)
+      no_edit=t
+      log_given=t$log_given
+      logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+      shift
+      ;;
+  -e|--e|--ed|--edi|--edit)
+      edit_flag=t
+      shift
+      ;;
+  -m|--m|--me|--mes|--mess|--messa|--messag|--message)
+      case "$#" in 1) usage ;; esac
+      shift
+      log_given=m$log_given
+      if test "$log_message" = ''
+      then
+          log_message="$1"
+      else
+          log_message="$log_message
+
+$1"
+      fi
+      no_edit=t
+      shift
+      ;;
+  -m*)
+      log_given=m$log_given
+      if test "$log_message" = ''
+      then
+          log_message=`expr "z$1" : 'z-m\(.*\)'`
+      else
+          log_message="$log_message
+
+`expr "z$1" : 'z-m\(.*\)'`"
+      fi
+      no_edit=t
+      shift
+      ;;
+  --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
+      log_given=m$log_given
+      if test "$log_message" = ''
+      then
+          log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+      else
+          log_message="$log_message
+
+`expr "z$1" : 'zq-[^=]*=\(.*\)'`"
+      fi
+      no_edit=t
+      shift
+      ;;
+  esac
+done
+case "$edit_flag" in t) no_edit= ;; esac
+
+if test "$log_message" != ""
+then
+	echo "$log_message"
+elif test "$logfile" != ""
+then
+	if test "$logfile" = -
+	then
+		test -t 0 &&
+		echo >&2 "(read log message from standard input)"
+		cat
+	else
+		cat <"$logfile"
+	fi
+fi | git-stripspace > "$GIT_DIR"/COMMIT_EDITMSG
+
+case "$no_edit" in
+'')
+	case "${VISUAL:-$EDITOR},$TERM" in
+	,dumb)
+		echo >&2 "Terminal is dumb but no VISUAL nor EDITOR defined."
+		echo >&2 "Please supply the commit log message using either"
+		echo >&2 "-m or -F option.  A boilerplate log message has"
+		echo >&2 "been prepared in $GIT_DIR/COMMIT_EDITMSG"
+		exit 1
+		;;
+	esac
+	git-var GIT_AUTHOR_IDENT > /dev/null || die
+	git-var GIT_COMMITTER_IDENT > /dev/null || die
+	${VISUAL:-${EDITOR:-vi}} "$GIT_DIR/COMMIT_EDITMSG"
+	;;
+esac
+
+cat $GIT_DIR/COMMIT_EDITMSG | git-stripspace > "$GIT_DIR"/COMMIT_MSG
+
+parents="-p $headsha1"
+if git-rev-parse --verify $archiveref > /dev/null 2> /dev/null; then
+	parents="$parents -p $(git-rev-parse $archiveref)"
+fi
+
+tree=$(git-cat-file commit $headsha1 | sed -n -e 's/^tree \(.*\)$/\1/p') &&
+commit=$(cat $GIT_DIR/COMMIT_MSG | git-commit-tree $tree $parents) 
+git-update-ref "$archiveref" $commit 
+rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG"

Re: [RFC][PATCH] Branch history

From: Shawn Pearce <hidden>
Date: 2016-06-15 22:42:35

"Eric W. Biederman" [off-list ref] wrote:
The problem:
git-rebase, stgit and the like destructively edit the commit history
on a branch.  Making it a challenge to go back to a known good point.

revlog and the like sort of help this but they don't address the
issues that they capture irrelevant points and are not git-prune safe.
How are the points irrelevant?  Each commit/rebase/am/update-ref
is recorded.  That's each change to the branch head.  It appears
as though you are mainly interested in tracking across rebases,
which a reflog would do, assuming you filtered the events down to
only those caused by rebase and ignored the others.

But yea, a reflog is not prune-safe, but it wouldn't be hard to
modify git-prune to also consider the reflog associated with
a ref if its using that ref as a root that must be preserved.
Assuming anyone really wants that as a feature...
 
After thinking about the problem some more I believe I have found
a rather simple solution to the problem of keeping branch history.

For each branch you want to keep the history of keep 2 branches.
A normal working branch, and a second archive branch that records
the history of the branch you are editing.
It would appear as though you are really only tracking rebase events,
as everything else done on the branch is preserved since the work
branch is itself parent #1 for the archive branch commit.  So the
archive branch shows every commit ever done along the main branch,
but also shows itself joining back quite frequently.  Further if you
archive away the work branch without during a rebase since the last
archive then there's really nothing happening except saving a tag
(but as a commit!) on the archive branch.

This creates for a rather messy history, and is more-or-less what
pg does when patches get pushed onto a stack and they can't be
pushed by a simple fast-forward operation.  Reading this history
in gitk is "interesting" at best.  This is the main reason I've
been trying to write `tb` (a topic branch manager, fashioned after
Junio's TO script) but I can't seem to find enough time to get it
finished.
The neat thing is that it gives an immutable history of a branch that
is actively being edited.  So if you export your archive branch people
will never see time roll backward.
Right.  That's an interesting way of handling it, but that branch
is also quite messy as its full of merge commits.  Although it may
be useful to export its going to carry along with it all of the bad
edits and prior rebases made on that branch.  You probably wouldn't
want to merge that branch into a mainline, which means that branch
is likely to be discarded at some point in the future.  When that
happens then nobody can track it anymore and that immutable history
just got mutated out of existance.

I think the right way to deal with these types of branches is to
publicly publish whether or not the branch is going to be expected
to roll backwards in time (due to a rebase type of event) then
let clients always update those branches during pulls, rather
than needing to explicitly mark them with '+' on the client side.

Further good remege tools (git-rerere on steriods) would help
re-resolve conflicts resulting from continous rebasing.  This would
make it easier to maintain such a branch and carry the thing forward;
or to leave it on its original base but to continously remerge
it and the current mainline into a temporary working branch for
testing purposes.

This is largely the policy that Junio uses for the `pu` and
the `next` branches, as well as for the topic branches that he
carries for everyone else doing GIT development.  It appears to be
working rather well, but it certainly could be streamlined better.
My git-rerere2 and tb tools are an attempt to do this, but sadly
they aren't in a useful state yet.  Maybe because they are both
far more complex then what you are doing here.  :-)


Nonethless it is an interesting contribution.  Thank you for taking
the time to send it.

-- 
Shawn.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help