[PATCH] New git-seek command with documentation and test.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add git-seek which allows for temporary excursions through the
revision history. With "git seek <revision>" one gets a working tree
corresponding to <revision>. When done with the excursion "git seek"
returns back to the original branch from where the first seek began.

Signed-off-by: Carl Worth <cworth@xxxxxxxxxx>

---

 git-seek could be used as a new basis for git-bisect. This patch does
 not do that, but even so, git-bisect and git-seek should play nicely
 with each other, (in the sense that either will refuse to do anything
 if .git/head-name already exists).

 On Tue, 14 Feb 2006 14:39:34 -0800, Junio C Hamano wrote:
 > Carl Worth <cworth@xxxxxxxxxx> writes:
 > > [arguments in favor of a new git-seek] 
 > 
 > I think this is a very valid point and I am happy to accept a
 > workable proposal (does not have to be a working patch, but a
 > general semantics that covers most of if not all the corner
 > cases).
 
 I had planned to just let this drop as my original need was some
 historical exploration that I've already finished. But now I've found
 a common use case in my everyday workflow that could benefit from
 git-seek. Here it is:
 
 I receive a bug-fix patch that updates a test case to demonstrate the
 bug. I can apply both the fix and the test case and see it succeed.
 But what I really want to do is first commit the test case, see it
 fail, and only then commit the fix and see the test now succeed.  I'd
 also like the history to reflect that order. So what I do is:
 
 	$ git-am
 	$ git update-index test.c ; git commit -m "Update test"
 	$ git update-index buggy.c ; git commit -m "Fix bug"
 
 At that point, without git-seek I can get by with:
 
 	$ git checkout -b tmp HEAD^
 	$ make check # to see failure
 	$ git checkout <branch_I_was_on_to_begin_with>
 	$ git branch -d tmp # easy to forget, but breaks the next time otherwise
 	$ make check # to see success
 
 But what I'd really like to do, (and can with the attached patch), is:
 
 	$ git seek HEAD^
 	$ make check # to see failure
 	$ git seek
 	$ make check # to see success
 
 This avoids me having to: 1) invent a throwaway name, 2) remember the
 branch I started on, 3) remember to actually throwaway the temporary
 branch.

 I've documented git-seek quite carefully and added a test that tries
 to cover every documented failure mode.

 -Carl

 .gitignore                 |    1 
 Documentation/git-seek.txt |   44 +++++++++++++++++++++
 Makefile                   |    4 +-
 git-seek.sh                |   94 ++++++++++++++++++++++++++++++++++++++++++++
 t/t3800-seek.sh            |   82 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 223 insertions(+), 2 deletions(-)
 create mode 100644 Documentation/git-seek.txt
 create mode 100644 git-seek.sh
 create mode 100755 t/t3800-seek.sh

2656ffb6e3fcbd9443c22b4675b13f23c031600e
diff --git a/.gitignore b/.gitignore
index 94f66d5..55484b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -85,6 +85,7 @@ git-rev-list
 git-rev-parse
 git-revert
 git-rm
+git-seek
 git-send-email
 git-send-pack
 git-sh-setup
diff --git a/Documentation/git-seek.txt b/Documentation/git-seek.txt
new file mode 100644
index 0000000..cb5c13d
--- /dev/null
+++ b/Documentation/git-seek.txt
@@ -0,0 +1,44 @@
+git-bisect(1)
+=============
+
+NAME
+----
+git-seek - Provide a temporary excursion through the revision history.
+
+
+SYNOPSIS
+--------
+'git seek' [<revision>]
+
+DESCRIPTION
+-----------
+When given a <revision>, git-seek updates the files in the working
+tree to the state of the given revision. It will do this by performing
+a checkout of <revision> to a new branch named "seek", or by resetting
+the seek branch if it already exists.
+
+When run with with no <revision> argument, git-seek will return to the
+original branch from which the initial git-seek operation was
+performed, (this original branch name is saved in $GIT_DIR/head-name).
+
+git-seek refuses to do anything if the working tree or index are
+modified with respect to HEAD. If you want to carry modifications
+around, use git-checkout rather than git-seek.
+
+git-seek will also fail if GIT_DIR/head-name exists when a seek is not
+already in progress, or if a seek branch already exists that is not a
+subset of the current branch, (that is, if it has unmerged commits).
+
+Author
+------
+Written by Carl Worth <cworth@xxxxxxxxxx>, based on git-bisect by
+Linus Torvalds <torvalds@xxxxxxxx>
+
+Documentation
+-------------
+Documentation by Carl Worth and the git-list <git@xxxxxxxxxxxxxxx>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Makefile b/Makefile
index 8e6bbce..f3383d8 100644
--- a/Makefile
+++ b/Makefile
@@ -120,8 +120,8 @@ SCRIPT_SH = \
 	git-merge-one-file.sh git-parse-remote.sh \
 	git-prune.sh git-pull.sh git-push.sh git-rebase.sh \
 	git-repack.sh git-request-pull.sh git-reset.sh \
-	git-resolve.sh git-revert.sh git-rm.sh git-sh-setup.sh \
-	git-tag.sh git-verify-tag.sh git-whatchanged.sh \
+	git-resolve.sh git-revert.sh git-rm.sh git-seek.sh \
+	git-sh-setup.sh git-tag.sh git-verify-tag.sh git-whatchanged.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-grep.sh \
diff --git a/git-seek.sh b/git-seek.sh
new file mode 100644
index 0000000..26f0b76
--- /dev/null
+++ b/git-seek.sh
@@ -0,0 +1,94 @@
+#!/bin/sh
+
+USAGE='[<revision>]'
+LONG_USAGE='git-seek provides a temporary excursion through the revision history.
+
+When given a <revision>, git-seek updates the files in the working
+tree to the state of the given revision. It will do this by performing
+a checkout of <revision> to a new branch named "seek", or by resetting
+the seek branch if it already exists.
+
+When run with with no <revision> argument, git-seek will return to the
+original branch from which the initial git-seek operation was
+performed, (this original branch name is saved in $GIT_DIR/head-name).
+
+git-seek refuses to do anything if the working tree or index are
+modified with respect to HEAD. If you want to carry modifications
+around, use git-checkout rather than git-seek.
+
+git-seek will also fail if GIT_DIR/head-name exists when a seek is not
+already in progress, or if a seek branch already exists that is not a
+subset of the current branch, (that is, if it has unmerged commits).'
+
+. git-sh-setup
+
+# Does $GIT_DIR/head-name contain the given revision
+# We use git-rev-parse to correctly resolve any aliases through references.
+head_name_contains() {
+	old_head=$(git-rev-parse $(cat "$GIT_DIR/head-name"))
+	new_head=$(git-rev-parse "$1")
+	[ "$old_head" = "$new_head" ]
+}
+
+seek_to() {
+	target="$1"
+	head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
+	die "Bad HEAD - I need a symbolic ref"
+	case "$head" in
+	refs/heads/seek)
+		# An explicit seek to head-name is treated as a reset
+		if head_name_contains "$target"; then
+			seek_reset
+		else
+			git reset --hard $target
+		fi
+		;;
+	refs/heads/*)
+		[ -s "$GIT_DIR/head-name" ] && die "Will not seek: $GIT_DIR/head-name is already in use"
+		echo "$head" | sed 's#^refs/heads/##' >"$GIT_DIR/head-name"
+		if git-rev-parse --verify seek >&/dev/null ; then
+			git-branch -d seek || exit
+		fi
+		git checkout -b seek $target
+		;;
+	*)
+		die "Bad HEAD - strange symbolic ref"
+		;;
+	esac
+}
+
+seek_reset() {
+	if [ -s "$GIT_DIR/head-name" ]; then
+		source=$(cat "$GIT_DIR/head-name") || exit
+	else
+		echo >&2 "No seek is in progress: returning to master."
+		source 
+	fi
+	git checkout "$source" &&
+	(git branch -d seek || err=$? ; git checkout seek ; exit $err) &&
+	rm -f "$GIT_DIR/head-name"
+}
+
+head=$(git-rev-parse --verify HEAD) || die "You do not have a valid HEAD"
+
+files_dirty=$(git-diff-index --name-only $head) || exit
+index_dirty=$(git-diff-index --cached --name-only $head) || exit
+if [ "$files_dirty" -o "$index_dirty" ]; then
+	die "Will not seek from a dirty state:
+	${index_dirty:+(dirty in index: $index_dirty)} ${files_dirty:+(dirty in working tree: $files_dirty)}
+You may want to commit these changes first or perhaps use git-checkout
+-m instead of git-seek."
+fi
+
+case "$#" in
+0)
+	seek_reset
+	;;
+1)
+	seek_to "$1"
+	;;
+*)
+	usage 
+	;;
+esac
+
diff --git a/t/t3800-seek.sh b/t/t3800-seek.sh
new file mode 100755
index 0000000..e5d8f90
--- /dev/null
+++ b/t/t3800-seek.sh
@@ -0,0 +1,82 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Carl D. Worth
+#
+
+test_description='Test of git-seek and all documented failure modes.'
+
+. ./test-lib.sh
+
+echo "first" > file
+git-add file && git-commit -m "add first revision of file"
+echo "second" > file
+git-commit -a -m "commit second revision"
+git tag second
+echo "third" > file
+git-commit -a -m "commit third revision"
+
+verify_revision() {
+    contents=$(cat file) && [ "$contents" = "$1" ]
+}
+
+test_expect_success \
+    'Test of initial "git-seek <revision>"' \
+    'git-seek HEAD~2 && verify_revision first'
+
+test_expect_success \
+    'Test of "git-seek <revision>" during seek' \
+    'git-seek second && verify_revision second'
+
+test_expect_success \
+    'Test that "git-seek" returns to starting point and resets seek state' \
+    'git-seek && verify_revision third &&
+     [ ! -f .git/refs/seek ] &&
+     [ ! -f .git/head-name ]'
+
+test_expect_success \
+    'Test that "git-seek master" also resets seek state' \
+    'git seek HEAD^1 &&
+     git seek master && verify_revision third &&
+     [ ! -f .git/refs/seek ] &&
+     [ ! -f .git/head-name ]'
+
+test_expect_success \
+    'Test that "git-seek <revision>" which aliases to master also resets seek state' \
+    'source=$(git-rev-parse HEAD) &&
+     git seek HEAD^1 &&
+     git seek $source && verify_revision third &&
+     [ ! -f .git/refs/seek ] &&
+     [ ! -f .git/head-name ]'
+
+echo modified > file
+test_expect_failure \
+    'Test that git-seek fails with local file modification' \
+    'git-seek HEAD^'
+git-reset --hard master
+
+echo modified > file
+git-update-index file
+test_expect_failure \
+    'Test that git-seek fails with a modified index' \
+    'git-seek HEAD^'
+git-reset --hard master
+
+echo master > .git/head-name
+test_expect_failure \
+    'Test that git-seek fails when .git/head-name exists and not seeking' \
+    'git-seek HEAD^'
+rm .git/head-name
+
+git-seek HEAD^
+echo new > new; git-add new; git-commit -m "Commit new file to seek branch"
+test_expect_failure \
+    'Test that git-seek fails when there are unmerged commits on seek branch' \
+    'git-seek'
+
+git checkout master
+git-pull . seek >&/dev/null
+test_expect_success \
+    'Test that git-seek works again after merging in the seek branch' \
+    'git-seek'
+
+test_done
-- 
1.2.3.g207a-dirty




Attachment: pgpOJcRSt4dA8.pgp
Description: PGP signature


[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]