All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work
@ 2011-04-23  7:22 Jon Seymour
  2011-04-23  7:22 ` [PATCH 01/23] Introduce git-test.sh and git-test-lib.sh Jon Seymour
                   ` (23 more replies)
  0 siblings, 24 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

This series is posted in order to solicit feedback about some commands I'd like to propose for inclusion in git. 

The commands are:
   git test
   git atomic
   git base
   git work

git work
========

git work depends on the other 3 commands. 

git work is the command I have been using every day myself for the last 8 months. It is the primary means
I use to manage my working tree.

The basic idea of git work is to help manage a private working tree using the following principles:

* all dependencies received from others are merged into the 'base' of the working tree

  git work merge some-dependency

     - merges some-dependency into $(git base) producing (1)
     - rebases $(git base)..HEAD onto (1) producing (2)
     - resets the current branch to (2)
     - updates $(git base) to refer to (1)

* all unpublished work is managed as a linear sequence of commits on top of the 'base' of the working tree

  so, 
   git work --as-refs # shows you a symbolic range for your current work
   git work # shows you a SHA1 range for your current work
   gitk $(git work) # shows you your unpublished work
   git diff $(git work) # shows you the diff of your current work

* prior to publishing work, you rebase it onto an appropriate base commit

  so,

  git branch topic upstream/master # choose the base commit for the topic
  git work update topic HEAD~3     # pull the top 3 commits off the working tree onto the topic

    - rebases HEAD~3..HEAD onto topic to produce (1)
    - merges topic into $(git base) to produce (2)
    - rebases $(git base)..HEAD~3 onto (2) to produce (3)
    - resets the current branch to (3)
    - resets $(git base) to (2)

The nice thing about managing your work tree this way is that your working tree remains
relatively stable (it always contains everything you have recently been working on)
and your topic branches remain clean (i.e. they never contain any other cruft from your 
working tree).

git base
========

git base is a command that is heavily relied on by git work, and is occasionally used by
the user to reset the base of their working tree.

git base tries to automagically maintain the base of the working tree by maintaining
an invariant that the path between the base and the tip of the current branch should
never traverse a merge. If it ever finds the invariant violated, it calls 'git base reset'
to attempt to restore the invariant. 

For more information about git base, refer to the Documentation/git-base.txt

git atomic
==========
git atomic is more an experiment than anything else. The idea is to run another git operation "atomically".
The git operation either succeeds or it fails. If it fails, git branch attempts to restore the
working tree and current branch to the state they were in to their original state prior to the comamnd being run.

The reason I need something like this is that git work performs several operations in sequence some
of which I can't guarantee will work. I don't want the user to work out what they have to do
to recover, so I try to restore to the state they were originally in.

Note: the current implementation doesn't handle rebase failures properly and would probably needed
to be cleaned up a little before being accepted into the mainline.

git test
========
This is another experiment. The idea is to provide a uniform way to test for various conditions
into the working tree, index and repo. For example:

    git test --not-unstaged --branch-exists foobar

will fail if there are unstaged files in the working tree or the branch foobar does not exist.

As I say, git atomic and git test are somewhat experimental. I don't really care about those commands
and if the consensus is that git doesn't need them, I am happy to rework git base and git work
to not use them.

However, I would like to propose git base and git work as being very useful additions to the git toolset.

Let me know if the consensus is that I should proceed with a submission and I will prepare one.

Jon Seymour (23):
  Introduce git-test.sh and git-test-lib.sh
  Introduce --unstaged.
  Introduce --staged
  Introduce --untracked.
  Introduce --conflicted
  Introduce --rebasing
  Introduce --detached
  Introduce --branch-exists
  Introduce --tag-exists
  Introduce --ref-exists
  Introduce --commit-exists.
  Introduce --checked-out
  Introduce --reachable
  Introduce --tree-same.
  Introduce --same
  misc
  whitespace fix.
  tests --conflicted.
  rebasing: add tests
  test: git test cleanups.
  Introduce git-atomic.
  Introduce git base.
  Introduce support for the git-work command.

 .gitignore                   |    7 +
 Documentation/config.txt     |   10 +
 Documentation/git-atomic.txt |   92 ++++++++
 Documentation/git-base.txt   |  216 ++++++++++++++++++
 Documentation/git-test.txt   |  182 +++++++++++++++
 Documentation/git-work.txt   |  163 ++++++++++++++
 Makefile                     |    7 +
 git-atomic-lib.sh            |   58 +++++
 git-atomic.sh                |    5 +
 git-base.sh                  |  378 +++++++++++++++++++++++++++++++
 git-conditions-lib.sh        |  176 +++++++++++++++
 git-test-lib.sh              |  188 ++++++++++++++++
 git-test.sh                  |   11 +
 git-work.sh                  |  323 +++++++++++++++++++++++++++
 t/t1520-test.sh              |  506 ++++++++++++++++++++++++++++++++++++++++++
 t/t3418-base.sh              |  214 ++++++++++++++++++
 t/t3419-atomic.sh            |   59 +++++
 t/t3421-work.sh              |  174 +++++++++++++++
 18 files changed, 2769 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/git-atomic.txt
 create mode 100644 Documentation/git-base.txt
 create mode 100644 Documentation/git-test.txt
 create mode 100644 Documentation/git-work.txt
 create mode 100644 git-atomic-lib.sh
 create mode 100755 git-atomic.sh
 create mode 100644 git-base.sh
 create mode 100644 git-conditions-lib.sh
 create mode 100644 git-test-lib.sh
 create mode 100755 git-test.sh
 create mode 100644 git-work.sh
 create mode 100755 t/t1520-test.sh
 create mode 100755 t/t3418-base.sh
 create mode 100755 t/t3419-atomic.sh
 create mode 100755 t/t3421-work.sh

-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 01/23] Introduce git-test.sh and git-test-lib.sh
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-27 19:11   ` Drew Northup
  2011-04-23  7:22 ` [PATCH 02/23] Introduce --unstaged Jon Seymour
                   ` (22 subsequent siblings)
  23 siblings, 1 reply; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

This command is intended provide a uniform command line interface
to a suite of assertions that can be made about the state of
the working tree, index and repository.

This commit introduces the core assert infrastructure. Subsequent
commits will introduce check functions that extend the infrastructure
in a modular way with additional tests.

Signed-off-by: Jon Seymour <jon.seymour@gmail.com>

Replace environment with git configuration.

test_must_fail: core
---
 .gitignore                 |    3 +
 Documentation/git-test.txt |  154 ++++++++++++++++++++++++++++++++++++
 Makefile                   |    3 +
 git-conditions-lib.sh      |   12 +++
 git-test-lib.sh            |  187 ++++++++++++++++++++++++++++++++++++++++++++
 git-test.sh                |   11 +++
 t/t1520-test.sh            |  185 +++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 555 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/git-test.txt
 create mode 100644 git-conditions-lib.sh
 create mode 100644 git-test-lib.sh
 create mode 100755 git-test.sh
 create mode 100755 t/t1520-test.sh

diff --git a/.gitignore b/.gitignore
index 2cf3ca5..aa0eb8fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,6 +27,7 @@
 /git-clone
 /git-commit
 /git-commit-tree
+/git-conditions-lib
 /git-config
 /git-count-objects
 /git-cvsexportcommit
@@ -141,6 +142,8 @@
 /git-symbolic-ref
 /git-tag
 /git-tar-tree
+/git-test
+/git-test-lib
 /git-unpack-file
 /git-unpack-objects
 /git-update-index
diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
new file mode 100644
index 0000000..1792c4e
--- /dev/null
+++ b/Documentation/git-test.txt
@@ -0,0 +1,154 @@
+git-test(1)
+===========
+
+NAME
+----
+git-test - evaluates one or more conditions about the state of the git working tree, index or repository
+
+SYNOPSIS
+--------
+The git test API is available in the form of a command and also as a shell library. 
+
+COMMAND SYNOPSIS
+----------------
+[verse]
+'git test' [-q] [--message message] [--<condition> [ arg ... ]] ...
+
+LIBRARY SYNOPSIS
+----------------
+[verse]
+'assert' [-q] [--message message] [--<condition> [ arg ... ]] ...
+'test_condition' [--<condition> [ arg ... ]] ...
+'require_condition_libs'
+
+
+DESCRIPTION
+-----------
+`git test` provides a uniform, extensible API for evaluating various conditions that
+pertain to the state of a git working tree, index and repository.
+
+Specified conditions are evaluated from left to right. If any condition evaluates to false, 
+the command conditionally prints a diagnostic message to stderr and sets a 
+non-zero status code. Otherwise, sets a status code of zero. 
+
+The message used to report a assertion failure may be overidden by specifying the --message option.
+
+Diagnostic output resulting from an assertion failure may be suppressed with the -q option. 
+Note that the -q option does not suppress diagnostic output that results from the failure to 
+successfully parse the arguments that configure the test API.
+
+The `assert` and `test_condition` functions differ according to how they handle failing conditions. 
+
+The `assert` function behaves like the test command but assertion failures will cause 
+the current shell to exit with a non-zero status code. To prevent this occurring, invoke
+the `assert` function within a subshell or use the `test_condition` function instead.
+
+The `test_condition` function will not exit the current shell in the event that an assertion failure
+is detected nor will it generate diagnostic relating to an assertion failure to stderr. 
+
+Note, however, that `test_condition` may still exit the current shell with a non-zero status code 
+if it is unable to successfully parse the arguments presented to it. Callers who need to protect 
+against this possibility either guarantee that the required arguments are available or 
+imbed calls to `test_condition` within a subshell.
+
+The `require_condition_libs` function may be used to include any condition libs listed
+in condition.lib variables of the the git configuration.
+
+
+OPTIONS
+-------
+'--message'::
+	The message to be used to report a failure if an assertion fails.
+'-q'::	
+        Suppress assertion messages.
+
+CONDITIONS
+----------
+
+EXTENDING THE CONDITION LIBRARY
+-------------------------------
+The library of conditions that the assert API can process may be extended by 
+adding functions of the form check_\{dehyphenated_condition_name\}_N to the 
+shell environment, where \{dehyphenated_condition_name\} is the result of 
+replacing any occurrence of \'-\' in the condition name with \'_\' and 
+N is the number of arguments that need to be passed to the function.
+
+For example, suppose you are writing a script, foo.sh, that includes the git test library 
+and that you want to add a new, 1-argument, condition, foo-X to the library of 
+conditions that can be tested by the git testion framework. 
+
+---------
+#/bin/sh
+. $(git --exec-path)/git-test-lib
+
+check_foo_X_1()
+{
+    if # some test of $1
+    then
+	echo "foo-X is true"
+    else
+	echo "foo-X is false"
+	false
+    fi
+}
+
+assert --foo-X "somearg"
+--------
+If its condition holds, the condition function must set a status
+code of zero and write a non-empty message describing the condition to stdout.
+If its condition does not hold, the condition function must set a non-zero
+status code of zero and write a non-empty message describing the condition
+that does hold to stdout.
+
+It is an error, and is reported as such, if a condition function
+executes without generating any output on stdout. The resulting
+state will then be interpreted as condition evaluation failure
+rather than an assertion failure.
+
+To make such conditions available to the git test command line, put the 
+function in a file called ~/foo-lib,sh add a reference 
+to the library to the git configuration, like so:
+
+---------
+git config --add condition.lib ~/foo-lib.sh
+git test --foo-X one-arg                     # now use foo-X from the git test command line
+---------
+
+CONFIGURATION
+-------------
+condition.lib::
+	Specifies a shell library that contains definitions of one or more
+	condition check functions that can be used to extend the range
+	of conditions that can be used with the git test API.
+
+EXAMPLES
+--------
+* Reset the working tree to the specified commit, but only the current HEAD is tree-same with that commit
+! git assert -q --tree-same HEAD upstream/master || git reset --hard upstream/master
+* Reset the working tree, but only if there are no staged or unstaged changes
++
+-----------
+git test --not-staged --not-unstaged && git reset --hard another-commit
+-----------
+* Import the git-test-lib library into another script
++
+-----------
+. $(git --exec-path)/git-test-lib
+assert --not-staged   	      	                              # die if there are any staged files
+assert --message "there are staged files" --not-staged	      # die with an alternative message 
+                                                              # if there are any staged files
+test_condition --not-staged || echo "there are staged files"  # check whether there are staged files, 
+                                                              # but do not die if there are
+-----------
+
+Author
+------
+Written by Jon Seymour <jon.seymour@gmail.com>
+
+Documentation
+-------------
+Documentation by Jon Seymour
+
+GIT
+---
+Part of the linkgit:git[7] suite
diff --git a/Makefile b/Makefile
index 92c1c5e..93ff6c6 100644
--- a/Makefile
+++ b/Makefile
@@ -376,11 +376,14 @@ SCRIPT_SH += git-repack.sh
 SCRIPT_SH += git-request-pull.sh
 SCRIPT_SH += git-stash.sh
 SCRIPT_SH += git-submodule.sh
+SCRIPT_SH += git-test.sh
 SCRIPT_SH += git-web--browse.sh
 
+SCRIPT_LIB += git-conditions-lib
 SCRIPT_LIB += git-mergetool--lib
 SCRIPT_LIB += git-parse-remote
 SCRIPT_LIB += git-sh-setup
+SCRIPT_LIB += git-test-lib
 
 SCRIPT_PERL += git-add--interactive.perl
 SCRIPT_PERL += git-difftool.perl
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
new file mode 100644
index 0000000..f462e22
--- /dev/null
+++ b/git-conditions-lib.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+#
+# (c) Copyright Jon Seymour 2010
+#
+if test -z "${__GIT_CONDITIONS_LIB_INCLUDED}"
+then
+__GIT_CONDITIONS_LIB_INCLUDED=t
+#
+# Provides a function that enables the evaluation of assertions.
+#
+
+fi
diff --git a/git-test-lib.sh b/git-test-lib.sh
new file mode 100644
index 0000000..159cea6
--- /dev/null
+++ b/git-test-lib.sh
@@ -0,0 +1,187 @@
+#!/bin/sh
+#
+# (c) Copyright Jon Seymour 2010
+#
+if test -z "${__GIT_ASSERT_LIB_INCLUDED}"
+then
+__GIT_ASSERT_LIB_INCLUDED=t
+
+. $(git --exec-path)/git-conditions-lib
+
+is_a_function()
+{
+    test "$(type $1 2>/dev/null | sed -n "s/is a .*function/is a function/p")" == "$1 is a function"
+}
+
+if ! is_a_function die
+then
+die() {
+	echo >&2 "$@"
+	exit 1
+}
+fi
+
+require_lib()
+{
+	if test -f "$1"
+	then
+		. "$1" || die "failed to read: '$1'"
+	else
+		echo "warning: condition library '$1' does not exist" 1>&2
+	fi
+}
+
+require_condition_libs() {
+	eval $(
+		git config --get-all condition.lib | while read lib
+		do 
+			echo "require_lib \"$lib\" \;"
+		done	
+	)
+}
+
+assertion_failed() {
+	rc=$1
+	shift 
+	message="${MESSAGE:-$*}"
+	if ! ${QUIET:-false}
+	then
+		echo "${message}" 1>&2
+	fi
+
+	if test -z "${EXIT_ON_FAILURE}"
+	then
+		return $rc
+	else
+		exit $rc;
+	fi
+}
+
+evaluation_failed() {
+	rc=$1
+	shift
+	echo "fatal: condition evaluation failed for $*" 1>&2
+	exit $rc
+}
+
+not() {
+	! "$@"
+}
+
+#
+# reviewers: is there a more concise way to express this in POSIX?
+#
+replace() {
+	word=$1
+	from=$2
+	to=$3
+	
+	prefix=${word%${from}*}
+	suffix=${word#${prefix}${from}}
+
+	if test "$prefix" = "$word"
+	then
+		echo $word
+	else
+		echo "$(replace ${prefix} '-' '_')${to}${suffix}"
+	fi
+}
+
+impl() {
+	MESSAGE=
+	QUIET=false
+	QUEUE=""
+	exprs=""
+	while test $# -gt 0
+	do
+		word=$1
+		shift
+		case $word in
+			--message)
+			MESSAGE=$1
+			shift
+			continue
+			;;
+			--include)
+				test $# -gt 0 || die "can't shift 1 argument for --include option"
+				test -f "$1" || die "'$1' must be a file"
+				require_lib "$1"
+				shift 
+				continue
+			;;
+			-q)
+			QUIET=true
+			continue;
+			;;
+			--not-*)
+			negation='not'
+			condition=${word#--not-}
+			;;
+			--*)
+			negation=''
+			condition=${word#--}
+			;;
+			*)
+			die "argument not recognised: $word"
+			;;
+		esac
+
+		dehyphenated=$(replace "$condition" "-" "_")
+
+		expr=
+		args=
+		try=0
+
+		while ! is_a_function check_${dehyphenated}_$try
+		do	
+			test -n "$1" || die "condition $condition is not supported or insufficient arguments were supplied"
+			test "${1#--}" = "$1" || die "condition $condition is not supported or insufficient arguments were supplied"
+			args="${args}${args:+ }$(git rev-parse --sq-quote $1)"
+			shift
+			try=$((try+1))
+		done
+
+		exprs="${exprs}${exprs:+ }${negation}${negation:+ }$word $try check_${dehyphenated}_$try $args"
+
+	done 
+
+	set -- $exprs
+	while test $# -gt 0
+	do
+		if test "$1" = "not"
+		then
+			negation=not
+			shift
+		else
+			negation=
+		fi
+		word=$1
+		nargs=$2
+		shift 2
+		message=$(eval $negation "$@") 
+		rc=$?
+		if test $rc -ne 0
+		then
+			if test -n "$message"
+			then
+				assertion_failed $rc "$message"
+			else
+				evaluation_failed $rc "$word $2 ..."
+			fi
+			return $rc
+		fi
+		shift $((nargs+1))
+	done
+}
+
+assert() {
+	EXIT_ON_FAILURE=t
+	impl "$@"
+}
+
+test_condition() {
+	EXIT_ON_FAILURE=
+	impl "$@" 2>/dev/null
+}
+
+fi
diff --git a/git-test.sh b/git-test.sh
new file mode 100755
index 0000000..4070ae5
--- /dev/null
+++ b/git-test.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+#
+# (c) Copyright Jon Seymour 2010
+#
+SUBDIRECTORY_OK=true
+. git-sh-setup
+. git-test-lib
+
+require_condition_libs
+
+assert "$@"
diff --git a/t/t1520-test.sh b/t/t1520-test.sh
new file mode 100755
index 0000000..9e3abe6
--- /dev/null
+++ b/t/t1520-test.sh
@@ -0,0 +1,185 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Jon Seymour
+#
+
+test_description='git-test tests
+
+Checks that the git-test conditions are correctly evaluated.
+'
+
+. ./test-lib.sh
+. $(git --exec-path)/git-test-lib.sh
+
+cat >test-assertions-lib.sh <<EOF
+check_always_fails_0_0()
+{
+	echo "always-fails-0"
+	false
+}
+
+check_always_fails_1_1()
+{
+	echo "always-fails-1 \$1"
+	false
+}
+
+check_never_fails_0_0()
+{
+	echo "never-fails-0"
+	true
+}
+
+check_never_fails_1_1()
+{
+	echo "never-fails-1 \$1"
+	true
+}
+EOF
+cat >empty-assertions-lib.sh <<EOF
+EOF
+
+git config condition.lib "$(pwd)/test-assertions-lib.sh"
+git config --add condition.lib "$(pwd)/empty-assertions-lib.sh"
+
+test_expect_assertion_failure()
+{
+	test=$1
+	message=$2
+	shift 
+	test_expect_success $1 \
+"
+	! actual_message=$(git test "$@" 1>&2) &&
+	test "$message" = "$actual_message"
+"
+}
+
+#        G
+#       /
+# base-A---M--C--D--D1--E
+#     \   / \
+#      -B-   -F
+
+test_expect_success 'setup' \
+'
+	git add test-assertions-lib.sh empty-assertions-lib.sh && 
+	test_commit base &&
+	test_commit A &&
+	git checkout A^1 &&
+	test_commit B && 
+	git checkout master &&
+	test_merge M B &&
+	echo C >> B.t &&
+	git tag STASH_UNSTAGED $(git stash create) &&
+	git add B.t &&
+	git tag STASH_STAGED $(git stash create) &&
+	test_commit C &&
+	test_commit D &&
+	git commit -m "allow-empty" --allow-empty &&
+	git tag D1 &&
+	test_commit E &&
+	git checkout M^0 -- &&
+        echo F >> B.t &&
+        git add B.t &&
+	test_commit F &&
+	git checkout A^0 -- &&
+	test_commit G &&
+	git checkout master &&
+	git reset --hard D     
+'
+
+test_expect_success 'git test # no arguments' \
+'
+	git test &&
+	test -z "$(git test)"
+'
+
+test_expect_success 'git test -q # -q only' \
+'
+	git test -q &&
+	test -z "$(git test)"
+'
+
+test_expect_success 'git test --message msg # with a message' \
+'
+	git test --message msg &&
+	test -z "$(git test)"
+'
+
+test_expect_success 'git test --message "" # with an empty message' \
+'
+	git test --message "" &&
+	test -z "$(git test)"
+'
+
+test_expect_success 'git test --message # should fail' \
+'
+	test_must_fail git test --message
+'
+
+test_expect_success 'git test --invalid-condition # should fail' \
+'
+	test_must_fail git test --invalid-condition
+'
+
+test_expect_success 'git test --not-invalid-condition # should fail' \
+'
+	test_must_fail git test --not-invalid-condition
+'
+
+test_expect_success 'git test --invalid-condition --never-fails-0 # should fail' \
+'
+	test_must_fail git test --invalid-condition --never-fails-0
+'
+
+test_expect_success 'git test --invalid-condition one-arg --never-fails-0 #should fail' \
+'
+	test_must_fail git test --invalid-condition one-arg --never-fails-0
+'
+
+test_expect_success 'git test --never-fails-0' \
+'
+	git test --never-fails-0
+'
+
+test_expect_success 'git test --never-fails-1 # missing argument - should fail' \
+'
+	test_must_fail git test --never-fails-1
+'
+
+test_expect_success 'git test --never-fails-1 one-arg' \
+'
+	git test --never-fails-1 one-arg
+'
+
+test_expect_success 'git test --not-never-fails-0 # should fail' \
+'
+	test_must_fail git test --not-never-fails-0
+'
+
+test_expect_success 'git test --always-fails-0 # should fail' \
+'
+	test_must_fail git test --always-fails-0
+'
+
+test_expect_success 'git test --always-fails-1 # should fail' \
+'
+	test_must_fail git test --always-fails-1 one-arg
+'
+
+test_expect_success 'git test --not-always-fails-1 one-arg' \
+'
+	git test --not-always-fails-1 one-arg
+'
+
+test_expect_success 'git test --not-always-fails-1 # should fail' \
+'
+	test_must_fail git test --not-always-fails-1
+'
+
+test_expect_success 'git test --not-always-fails-0' \
+'
+	git test --not-always-fails-0
+'
+'
+test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 02/23] Introduce --unstaged.
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
  2011-04-23  7:22 ` [PATCH 01/23] Introduce git-test.sh and git-test-lib.sh Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 03/23] Introduce --staged Jon Seymour
                   ` (21 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

test_must_fail: unstaged
---
 Documentation/git-test.txt |    2 ++
 git-conditions-lib.sh      |   11 +++++++++++
 t/t1520-test.sh            |   35 +++++++++++++++++++++++++++++++++++
 3 files changed, 48 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index 1792c4e..4b2c129 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -64,6 +64,8 @@ OPTIONS
 
 CONDITIONS
 ----------
+'--unstaged'|'--not-unstaged'::
+	Tests if there are (not) any unstaged changes in the working tree.
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index f462e22..f9ff0b9 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -8,5 +8,16 @@ __GIT_CONDITIONS_LIB_INCLUDED=t
 #
 # Provides a function that enables the evaluation of assertions.
 #
+check_unstaged_0()
+{
+	if test $(git diff-files --name-only | wc -l) -ne 0
+	then
+		echo "There are unstaged files."
+	else
+		echo "There are no unstaged files."
+		false
+	fi
+}
+
 
 fi
diff --git a/t/t1520-test.sh b/t/t1520-test.sh
index 9e3abe6..8543943 100755
--- a/t/t1520-test.sh
+++ b/t/t1520-test.sh
@@ -181,5 +181,40 @@ test_expect_success 'git test --not-always-fails-0' \
 '
 	git test --not-always-fails-0
 '
+
+test_expect_success 'git test --unstaged # should fail' \
+'
+	test_must_fail git test --unstaged
+'
+
+test_expect_success 'git test --not-unstaged' \
+'
+	git test --not-unstaged
+'
+
+test_expect_success 'git test --unstaged # when there are unstaged files' \
+'
+	test_when_finished "git reset --hard HEAD && git checkout master" && 
+	git checkout -f M^0 &&
+	git stash apply --index STASH_UNSTAGED &&
+	git test --unstaged
+'
+
+test_expect_success 'git test --not-unstaged # when there are unstaged files - should fail' \
+'
+	test_when_finished "git reset --hard HEAD && git checkout master" && 
+	git checkout -f M^0 &&
+	git stash apply --index STASH_UNSTAGED &&
+	test_must_fail git test --not-unstaged
+'
+
+test_expect_success 'git test --unstaged # when there are only staged files' \
+'
+	test_when_finished "git reset --hard HEAD && git checkout master" && 
+	git checkout -f M^0 &&
+	git stash apply --index STASH_STAGED &&
+	git test --not-unstaged
+'
+
 '
 test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 03/23] Introduce --staged
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
  2011-04-23  7:22 ` [PATCH 01/23] Introduce git-test.sh and git-test-lib.sh Jon Seymour
  2011-04-23  7:22 ` [PATCH 02/23] Introduce --unstaged Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 04/23] Introduce --untracked Jon Seymour
                   ` (20 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

test_must_fail: --not-staged
---
 Documentation/git-test.txt |    3 ++
 git-conditions-lib.sh      |   11 ++++++++++
 t/t1520-test.sh            |   45 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 59 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index 4b2c129..9264c90 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -66,6 +66,9 @@ CONDITIONS
 ----------
 '--unstaged'|'--not-unstaged'::
 	Tests if there are (not) any unstaged changes in the working tree.
+'--staged'|'--not-staged'::
+        Tests if there are (not) any staged changes in the index.
+
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index f9ff0b9..55b4131 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -19,5 +19,16 @@ check_unstaged_0()
 	fi
 }
 
+check_staged_0()
+{
+	if test $(git diff-index --cached --name-only HEAD | wc -l) -ne 0
+	then
+		echo "There are staged files."
+	else
+		echo "There are no staged files."
+		false
+	fi
+}
+
 
 fi
diff --git a/t/t1520-test.sh b/t/t1520-test.sh
index 8543943..7438f93 100755
--- a/t/t1520-test.sh
+++ b/t/t1520-test.sh
@@ -216,5 +216,50 @@ test_expect_success 'git test --unstaged # when there are only staged files' \
 	git test --not-unstaged
 '
 
+test_expect_success 'git test --staged # should fail' \
 '
+	test_must_fail git test --staged
+'
+
+test_expect_success 'git test --not-staged' \
+'
+	git test --not-staged
+'
+
+test_expect_success 'git test --staged # when there are staged files' \
+'
+	test_when_finished "git reset --hard HEAD && git checkout master" && 
+	git checkout -f M^0 &&
+	git stash apply --index STASH_STAGED &&
+	git test --staged
+'
+
+test_expect_success 'git test --not-staged # when there are staged files - should fail' \
+'
+	test_when_finished "git reset --hard HEAD && git checkout master" && 
+	git checkout -f M^0 &&
+	git stash apply --index STASH_STAGED &&
+	test_must_fail git test --not-staged
+'
+
+test_expect_success 'git test --staged # when there are only unstaged files' \
+'
+	test_when_finished "git reset --hard HEAD && git checkout master" && 
+	git checkout -f M^0 &&
+	git stash apply --index STASH_UNSTAGED &&
+	git test --not-staged
+'
+
+'
+    test_when_finished "git reset --hard HEAD && git checkout master" && 
+    git test --not-staged --not-unstaged && 
+    ! git test --staged && 
+    ! git test --unstaged && 
+    git checkout M^0 &&
+    git stash apply --index STASH_STAGED &&
+    git test --not-unstaged --staged &&
+    ! git test --unstaged &&
+    ! git test --not-staged 
+'
+
 test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 04/23] Introduce --untracked.
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (2 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 03/23] Introduce --staged Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 05/23] Introduce --conflicted Jon Seymour
                   ` (19 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

Add tests for --untracked, --not-untracked.
---
 Documentation/git-test.txt |    3 ++-
 git-conditions-lib.sh      |   11 +++++++++++
 t/t1520-test.sh            |   24 ++++++++++++++++++++++++
 3 files changed, 37 insertions(+), 1 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index 9264c90..9f6a4c3 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -68,7 +68,8 @@ CONDITIONS
 	Tests if there are (not) any unstaged changes in the working tree.
 '--staged'|'--not-staged'::
         Tests if there are (not) any staged changes in the index.
-
+'--untracked'|'--not-untracked'::
+	Tests if there are (not) any untracked files in the working tree.
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index 55b4131..7198e6a 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -30,5 +30,16 @@ check_staged_0()
 	fi
 }
 
+check_untracked_0()
+{
+	if test $(git ls-files -o --exclude-standard | wc -l) -ne 0
+	then
+		echo "There are untracked files."
+	else
+		echo "There are no untracked files."
+		false
+	fi
+}
+
 
 fi
diff --git a/t/t1520-test.sh b/t/t1520-test.sh
index 7438f93..ebff63c 100755
--- a/t/t1520-test.sh
+++ b/t/t1520-test.sh
@@ -250,6 +250,30 @@ test_expect_success 'git test --staged # when there are only unstaged files' \
 	git test --not-staged
 '
 
+test_expect_success 'git test --untracked # should fail' \
+'
+	test_must_fail git test --untracked
+'
+
+test_expect_success 'git test --not-untracked' \
+'
+	git test --not-untracked
+'
+
+test_expect_success 'git test --untracked # when there are untracked files' \
+'
+	test_when_finished "git clean -fd" && 
+	:> untracked &&
+	git test --untracked
+'
+
+test_expect_success 'git test --not-untracked # when there are untracked files - should fail' \
+'
+	test_when_finished "git clean -fd" && 
+	:> untracked &&
+	test_must_fail git test --not-untracked
+'
+
 '
     test_when_finished "git reset --hard HEAD && git checkout master" && 
     git test --not-staged --not-unstaged && 
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 05/23] Introduce --conflicted
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (3 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 04/23] Introduce --untracked Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 06/23] Introduce --rebasing Jon Seymour
                   ` (18 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

---
 Documentation/git-test.txt |    2 ++
 git-conditions-lib.sh      |   12 ++++++++++++
 2 files changed, 14 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index 9f6a4c3..4f86265 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -70,6 +70,8 @@ CONDITIONS
         Tests if there are (not) any staged changes in the index.
 '--untracked'|'--not-untracked'::
 	Tests if there are (not) any untracked files in the working tree.
+'--conflicted'|'--not-conflicted'::
+	Tests if there are (not) any merge conflicts in the index.
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index 7198e6a..7f3724e 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -41,5 +41,17 @@ check_untracked_0()
 	fi
 }
 
+check_conflicted_0()
+{
+	if test $(git ls-files --unmerged | wc -l) -ne 0
+	then
+		echo "There are unmerged files."
+	else
+		echo "There are no unmerged files."
+		false
+	fi
+
+}
+
 
 fi
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 06/23] Introduce --rebasing
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (4 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 05/23] Introduce --conflicted Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 07/23] Introduce --detached Jon Seymour
                   ` (17 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

---
 Documentation/git-test.txt |    2 ++
 git-conditions-lib.sh      |   11 +++++++++++
 2 files changed, 13 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index 4f86265..e872608 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -72,6 +72,8 @@ CONDITIONS
 	Tests if there are (not) any untracked files in the working tree.
 '--conflicted'|'--not-conflicted'::
 	Tests if there are (not) any merge conflicts in the index.
+'--rebasing'|'--not-rebasing'::
+        Tests if a rebase is (not) in progress.
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index 7f3724e..0d6e0a1 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -53,5 +53,16 @@ check_conflicted_0()
 
 }
 
+check_rebasing_0()
+{
+	if test -d "$(git rev-parse --git-dir)/rebase-apply"
+	then
+		echo "A rebase is in progress."
+	else
+		echo "There is no rebase in progress."
+		false
+	fi
+}
+
 
 fi
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 07/23] Introduce --detached
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (5 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 06/23] Introduce --rebasing Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 08/23] Introduce --branch-exists Jon Seymour
                   ` (16 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

detached
---
 Documentation/git-test.txt |    2 ++
 git-conditions-lib.sh      |   11 +++++++++++
 t/t1520-test.sh            |   30 +++++++++++++++++++++---------
 3 files changed, 34 insertions(+), 9 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index e872608..501725c 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -74,6 +74,8 @@ CONDITIONS
 	Tests if there are (not) any merge conflicts in the index.
 '--rebasing'|'--not-rebasing'::
         Tests if a rebase is (not) in progress.
+'--detached'|'--not-detached'::
+	Tests if the head is (not) detached.
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index 0d6e0a1..34de896 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -64,5 +64,16 @@ check_rebasing_0()
 	fi
 }
 
+check_detached_0()
+{
+	if ! git symbolic-ref -q HEAD >/dev/null
+	then
+		echo "HEAD is detached."
+	else
+		echo "HEAD is not detached."
+		false
+	fi
+}
+
 
 fi
diff --git a/t/t1520-test.sh b/t/t1520-test.sh
index ebff63c..bb39050 100755
--- a/t/t1520-test.sh
+++ b/t/t1520-test.sh
@@ -274,16 +274,28 @@ test_expect_success 'git test --not-untracked # when there are untracked files -
 	test_must_fail git test --not-untracked
 '
 
+test_expect_success 'git test --not-detached' \
 '
-    test_when_finished "git reset --hard HEAD && git checkout master" && 
-    git test --not-staged --not-unstaged && 
-    ! git test --staged && 
-    ! git test --unstaged && 
-    git checkout M^0 &&
-    git stash apply --index STASH_STAGED &&
-    git test --not-unstaged --staged &&
-    ! git test --unstaged &&
-    ! git test --not-staged 
+	git test --not-detached
+'
+
+test_expect_success 'git test --detached # should fail' \
+'
+	test_must_fail git test --detached
+'
+
+test_expect_success 'git test --not-detached # when detached, should fail' \
+'
+	test_when_finished "git checkout -f master" && 
+	git checkout HEAD^0 &&
+	test_must_fail git test --not-detached
+'
+
+test_expect_success 'git test --detached # when detached' \
+'
+	test_when_finished "git checkout -f master" && 
+	git checkout HEAD^0 &&
+	git test --detached
 '
 
 test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 08/23] Introduce --branch-exists
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (6 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 07/23] Introduce --detached Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 09/23] Introduce --tag-exists Jon Seymour
                   ` (15 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

tweak branch

tweak branch
---
 Documentation/git-test.txt |    2 ++
 git-conditions-lib.sh      |   12 ++++++++++++
 2 files changed, 14 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index 501725c..ea54d20 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -76,6 +76,8 @@ CONDITIONS
         Tests if a rebase is (not) in progress.
 '--detached'|'--not-detached'::
 	Tests if the head is (not) detached.
+'--branch-exists'|'--not-branch-exists branch'::
+        Tests if the specified branch does (not) exist.				    
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index 34de896..5d7a7f8 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -75,5 +75,17 @@ check_detached_0()
 	fi
 }
 
+check_branch_exists_1()
+{
+	symbolic=$(git rev-parse --quiet --symbolic-full-name --verify "$1")
+ 	if test "${symbolic#refs/heads/}" != "${symbolic}"
+	then
+		echo "Branch '$1' exists."
+	else
+		echo "Branch '$1' does not exist."
+		false
+	fi
+}
+
 
 fi
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 09/23] Introduce --tag-exists
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (7 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 08/23] Introduce --branch-exists Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 10/23] Introduce --ref-exists Jon Seymour
                   ` (14 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

tweak tag

tweak tag
---
 Documentation/git-test.txt |    2 ++
 git-conditions-lib.sh      |   12 ++++++++++++
 2 files changed, 14 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index ea54d20..69c8710 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -78,6 +78,8 @@ CONDITIONS
 	Tests if the head is (not) detached.
 '--branch-exists'|'--not-branch-exists branch'::
         Tests if the specified branch does (not) exist.				    
+'--tag-exists'|'--not-tag-exists tag'::
+        Tests if the specified tag does (not) exist.
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index 5d7a7f8..93a916a 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -87,5 +87,17 @@ check_branch_exists_1()
 	fi
 }
 
+check_tag_exists_1()
+{
+	symbolic=$(git rev-parse --quiet --symbolic-full-name --verify "$1")
+ 	if test "${symbolic#refs/tags/}" != "${symbolic}"
+	then
+		echo "Tag '$1' exists."
+	else
+		echo "Tag '$1' does not exist."
+		false
+	fi
+}
+
 
 fi
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 10/23] Introduce --ref-exists
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (8 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 09/23] Introduce --tag-exists Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 11/23] Introduce --commit-exists Jon Seymour
                   ` (13 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

tweak ref
---
 Documentation/git-test.txt |    2 ++
 git-conditions-lib.sh      |   12 ++++++++++++
 2 files changed, 14 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index 69c8710..259a04c 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -80,6 +80,8 @@ CONDITIONS
         Tests if the specified branch does (not) exist.				    
 '--tag-exists'|'--not-tag-exists tag'::
         Tests if the specified tag does (not) exist.
+'--ref-exists'|'--not-ref-exists tag'::
+        Tests if the specified reference does (not) exist.
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index 93a916a..c21d669 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -99,5 +99,17 @@ check_tag_exists_1()
 	fi
 }
 
+check_ref_exists_1()
+{
+	symbolic=$(git rev-parse --quiet --symbolic-full-name --verify "$1")
+ 	if test "${symbolic#refs/}" != "${symbolic}"
+	then
+		echo "Reference '$1' exists."
+	else
+		echo "Reference '$1' does not exist."
+		false
+	fi
+}
+
 
 fi
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 11/23] Introduce --commit-exists.
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (9 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 10/23] Introduce --ref-exists Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 12/23] Introduce --checked-out Jon Seymour
                   ` (12 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

tweak commit
---
 Documentation/git-test.txt |    2 ++
 git-conditions-lib.sh      |   11 +++++++++++
 2 files changed, 13 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index 259a04c..9a4e06f 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -82,6 +82,8 @@ CONDITIONS
         Tests if the specified tag does (not) exist.
 '--ref-exists'|'--not-ref-exists tag'::
         Tests if the specified reference does (not) exist.
+'--commit-exists'|'--not-commit-exists commit'::
+	Tests if the specified commit does (not) exist.
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index c21d669..af376d1 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -111,5 +111,16 @@ check_ref_exists_1()
 	fi
 }
 
+check_commit_exists_1()
+{
+	if test "$(git cat-file -t "$1")" = 'commit'
+	then	
+		echo "Commit '$1' exists."
+	else
+		echo "Commit '$1' does not exist."
+		false
+	fi
+}
+
 
 fi
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 12/23] Introduce --checked-out
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (10 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 11/23] Introduce --commit-exists Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 13/23] Introduce --reachable Jon Seymour
                   ` (11 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

---
 Documentation/git-test.txt |    2 ++
 git-conditions-lib.sh      |   14 ++++++++++++++
 2 files changed, 16 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index 9a4e06f..f76aa38 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -84,6 +84,8 @@ CONDITIONS
         Tests if the specified reference does (not) exist.
 '--commit-exists'|'--not-commit-exists commit'::
 	Tests if the specified commit does (not) exist.
+'--checked-out'|'--not-checked-out branch'::
+        Tests if the specified branch is (not) checked out.
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index af376d1..3eccc19 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -122,5 +122,19 @@ check_commit_exists_1()
 	fi
 }
 
+check_checked_out_1()
+{
+	branch="$(git rev-parse --quiet --symbolic-full-name --verify "$1")"
+	headref="$(git symbolic-ref HEAD)"
+	if test "${headref}" = "${branch}" -a -n "${branch}"
+	then
+		echo "'$1' is checked out."
+	else
+		echo "'$1' is not checked out."
+		false
+	fi
+
+}
+
 
 fi
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 13/23] Introduce --reachable
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (11 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 12/23] Introduce --checked-out Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 14/23] Introduce --tree-same Jon Seymour
                   ` (10 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

reachable
---
 Documentation/git-test.txt |    2 +
 git-conditions-lib.sh      |   14 ++++++
 t/t1520-test.sh            |  101 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 117 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index f76aa38..e28ed88 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -86,6 +86,8 @@ CONDITIONS
 	Tests if the specified commit does (not) exist.
 '--checked-out'|'--not-checked-out branch'::
         Tests if the specified branch is (not) checked out.
+'--reachable'|'--not-reachable' first second::
+	Tests if the first commit is (not) reachable from the second.
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index 3eccc19..0223fcd 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -136,5 +136,19 @@ check_checked_out_1()
 
 }
 
+check_reachable_2()
+{
+	first=$(git rev-parse --quiet --verify "$1" 2>/dev/null) || die "'$1' is not a commit"
+	second=$(git rev-parse --quiet --verify "$2" 2>/dev/null) || die "'$2' is not a commit"
+	if test "$first" = "$second" \
+                -o -z "$(git rev-list -n1 "$first" ^"$second")" 
+	then
+		echo "'$1' is reachable from '$2'."
+	else
+		echo "'$1' is not reachable from '$2'."
+		false
+	fi     
+}
+
 
 fi
diff --git a/t/t1520-test.sh b/t/t1520-test.sh
index bb39050..3e33f8a 100755
--- a/t/t1520-test.sh
+++ b/t/t1520-test.sh
@@ -298,4 +298,105 @@ test_expect_success 'git test --detached # when detached' \
 	git test --detached
 '
 
+
+test_expect_success 'git test --reachable #should fail' \
+'
+	test_must_fail git test --reachable
+'
+
+test_expect_success 'git test --not-reachable #should fail' \
+'
+	test_must_fail git test --reachable
+'
+
+test_expect_success 'git test --reachable A #should fail' \
+'
+	test_must_fail git test --reachable A
+'
+
+test_expect_success 'git test --not-reachable A #should fail' \
+'
+	test_must_fail git test --not-reachable A
+'
+
+test_expect_success 'git test --reachable does-not-exist-1 does-not-exist-2 #should fail' \
+'
+	test_must_fail git test --reachable does-not-exist-1 does-not-exist-2
+'
+
+test_expect_success 'git test --not-reachable does-not-exist-1 does-not-exist-2 #should fail' \
+'
+	test_must_fail git test --not-reachable does-not-exist-1 does-not-exist-2
+'
+
+test_expect_success 'git test --reachable does-not-exist-1 A #should fail' \
+'
+	test_must_fail git test --reachable does-not-exist-1 A
+'
+
+test_expect_success 'git test --not-reachable does-not-exist-1 A #should fail' \
+'
+	test_must_fail git test --not-reachable does-not-exist-1 A
+'
+
+test_expect_success 'git test --reachable A does-not-exist-2 #should fail' \
+'
+	test_must_fail git test --reachable A does-not-exist-2
+'
+
+test_expect_success 'git test --not-reachable A does-not-exist-2 #should fail' \
+'
+	test_must_fail git test --not-reachable A does-not-exist-2
+'
+
+test_expect_success 'git test --reachable A C' \
+'
+       git test --reachable A C
+'
+
+test_expect_success 'git test --not-reachable A C # should fail' \
+'
+       test_must_fail git test --not-reachable A C
+'
+
+test_expect_success 'git test --reachable C A # should fail' \
+'
+       test_must_fail git test --reachable C A
+'
+
+test_expect_success 'git test --not-reachable C A # should fail' \
+'
+       git test --not-reachable C A
+'
+
+test_expect_success 'git test --reachable C C' \
+'
+       git test --reachable C C
+'
+
+test_expect_success 'git test --not-reachable C C' \
+'
+       test_must_fail git test --not-reachable C C
+'
+
+test_expect_success 'git test --reachable C F' \
+'
+       test_must_fail git test --reachable C F
+'
+
+test_expect_success 'git test --not-reachable C F' \
+'
+       git test --not-reachable C F
+'
+
+test_expect_success 'git test --reachable F C' \
+'
+       test_must_fail git test --reachable F C
+'
+
+test_expect_success 'git test --not-reachable F C' \
+'
+       git test --not-reachable F C
+'
+
 test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 14/23] Introduce --tree-same.
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (12 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 13/23] Introduce --reachable Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 15/23] Introduce --same Jon Seymour
                   ` (9 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

---
 Documentation/git-test.txt |    2 ++
 git-conditions-lib.sh      |   12 ++++++++++++
 t/t1520-test.sh            |    9 +++++++++
 3 files changed, 23 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index e28ed88..6709fdd 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -88,6 +88,8 @@ CONDITIONS
         Tests if the specified branch is (not) checked out.
 '--reachable'|'--not-reachable' first second::
 	Tests if the first commit is (not) reachable from the second.
+'--tree-same'|'--not-tree-same' first second::
+        Tests if the first commit is (not) tree-same to the second commit.
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index 0223fcd..2b7a5d1 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -150,5 +150,17 @@ check_reachable_2()
 	fi     
 }
 
+check_tree_same_2()
+{
+	if git diff-tree --quiet "$1" "$2"
+	then
+		echo "'$1' has the same tree as '$2'."
+	else
+		echo "'$1' does not have the same tree as '$2'."
+		false
+	fi
+
+}
+
 
 fi
diff --git a/t/t1520-test.sh b/t/t1520-test.sh
index 3e33f8a..1fd5122 100755
--- a/t/t1520-test.sh
+++ b/t/t1520-test.sh
@@ -399,4 +399,13 @@ test_expect_success 'git test --not-reachable F C' \
        git test --not-reachable F C
 '
 
+test_expect_success 'tree-same' \
+'
+   git test \
+     --tree-same master HEAD \
+     --tree-same D D1 \
+     --not-tree-same C D
+
+'
+
 test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 15/23] Introduce --same
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (13 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 14/23] Introduce --tree-same Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 16/23] misc Jon Seymour
                   ` (8 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

---
 Documentation/git-test.txt |    2 ++
 git-conditions-lib.sh      |   10 ++++++++++
 t/t1520-test.sh            |    8 ++++++++
 3 files changed, 20 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index 6709fdd..b7c3161 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -90,6 +90,8 @@ CONDITIONS
 	Tests if the first commit is (not) reachable from the second.
 '--tree-same'|'--not-tree-same' first second::
         Tests if the first commit is (not) tree-same to the second commit.
+'--same'|'--not-same' first second::
+	Tests if the first object has (does not have) the same SHA1 has as the second object.
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index 2b7a5d1..ec9b516 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -162,5 +162,15 @@ check_tree_same_2()
 
 }
 
+check_same_2()
+{
+	if test "$(git rev-parse "$1")" = "$(git rev-parse "$2")"
+	then
+		echo "'$1' is the same as '$2'."
+	else
+		echo "'$1' is not the same as '$2'."
+		false
+	fi
+}
 
 fi
diff --git a/t/t1520-test.sh b/t/t1520-test.sh
index 1fd5122..1d0776d 100755
--- a/t/t1520-test.sh
+++ b/t/t1520-test.sh
@@ -408,4 +408,12 @@ test_expect_success 'tree-same' \
 
 '
 
+test_expect_success 'same' \
+'
+   git test \
+      --same master HEAD \
+      --not-same D D1 \
+      --not-same C D 
+'
+
 test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 16/23] misc
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (14 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 15/23] Introduce --same Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 17/23] whitespace fix Jon Seymour
                   ` (7 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

---
 t/t1520-test.sh |   26 ++++++++++++++++++++++++++
 1 files changed, 26 insertions(+), 0 deletions(-)

diff --git a/t/t1520-test.sh b/t/t1520-test.sh
index 1d0776d..78f31fe 100755
--- a/t/t1520-test.sh
+++ b/t/t1520-test.sh
@@ -416,4 +416,30 @@ test_expect_success 'same' \
       --not-same C D 
 '
 
+test_expect_success 'clean' \
+'
+    git test \
+       --not-staged \
+       --not-unstaged \
+       --not-detached \
+       --not-untracked \
+       --not-rebasing \
+       --not-conflicted 
+'
+
+test_expect_success 'existence' \
+'
+    git test \
+       --commit-exists M \
+       --not-commit-exists N \
+       --ref-exists refs/tags/M \
+       --not-ref-exists refs/tags/N \
+       --branch-exists master \
+       --not-branch-exists N \
+       --tag-exists D \
+       --not-tag-exists N \
+       --not-tag-exists master \
+       --not-branch-exists A 
+ '
+
 test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 17/23] whitespace fix.
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (15 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 16/23] misc Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 18/23] tests --conflicted Jon Seymour
                   ` (6 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

---
 t/t1520-test.sh |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/t/t1520-test.sh b/t/t1520-test.sh
index 78f31fe..ddca519 100755
--- a/t/t1520-test.sh
+++ b/t/t1520-test.sh
@@ -440,6 +440,6 @@ test_expect_success 'existence' \
        --not-tag-exists N \
        --not-tag-exists master \
        --not-branch-exists A 
- '
+'
 
 test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 18/23] tests --conflicted.
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (16 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 17/23] whitespace fix Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 19/23] rebasing: add tests Jon Seymour
                   ` (5 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

---
 t/t1520-test.sh |   26 ++++++++++++++++++++++++++
 1 files changed, 26 insertions(+), 0 deletions(-)

diff --git a/t/t1520-test.sh b/t/t1520-test.sh
index ddca519..a18c1d7 100755
--- a/t/t1520-test.sh
+++ b/t/t1520-test.sh
@@ -442,4 +442,30 @@ test_expect_success 'existence' \
        --not-branch-exists A 
 '
 
+test_expect_success 'git test --conflicted # should fail' \
+'
+	test_must_fail git test --conflicted
+'
+
+test_expect_success 'git test --not-conflicted' \
+'
+	git test --not-conflicted
+'
+
+test_expect_success 'git test --conflicted' \
+'
+	test_when_finished "git reset --hard HEAD" && 
+        ! git merge F &&
+	git test --conflicted 
+	
+'
+
+test_expect_success 'git test --not-conflicted # should fail when there are conflcted files' \
+'
+	test_when_finished "git reset --hard HEAD" && 
+        ! git merge F &&
+	test_must_fail git test --not-conflicted 
+	
+'
+
 test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 19/23] rebasing: add tests
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (17 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 18/23] tests --conflicted Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 20/23] test: git test cleanups Jon Seymour
                   ` (4 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

---
 t/t1520-test.sh |   35 +++++++++++++++++++++++++++++++++++
 1 files changed, 35 insertions(+), 0 deletions(-)

diff --git a/t/t1520-test.sh b/t/t1520-test.sh
index a18c1d7..1db3598 100755
--- a/t/t1520-test.sh
+++ b/t/t1520-test.sh
@@ -468,4 +468,39 @@ test_expect_success 'git test --not-conflicted # should fail when there are conf
 	
 '
 
+test_expect_success 'git test --rebasing # should fail' \
+'
+	test_must_fail git test --rebasing
+'
+
+test_expect_success 'git test --not-rebasing' \
+'
+	git test --not-rebasing
+'
+
+test_expect_success 'git test --rebasing' \
+'
+	test_when_finished "
+		git reset --hard HEAD &&
+		git checkout -f master && 
+		git branch -D rebase
+	" && 
+        git branch rebase F &&
+        ! git rebase --onto D F~1 F
+	git test --rebasing 
+	
+'
+
+test_expect_success 'git test --not-rebasing' \
+'
+	test_when_finished "
+		git reset --hard HEAD &&
+		git checkout -f master &&
+		git branch -D rebase
+	" && 
+        git branch rebase F &&
+        ! git rebase --onto D F~1 F
+	test_must_fail git test --not-rebasing
+'
+
 test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 20/23] test: git test cleanups.
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (18 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 19/23] rebasing: add tests Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 21/23] Introduce git-atomic Jon Seymour
                   ` (3 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

---
 Documentation/git-test.txt |   48 +++++++++++++++++++++---------------------
 git-conditions-lib.sh      |    4 +-
 git-test-lib.sh            |   11 +++++----
 t/t1520-test.sh            |   50 ++++++++++++++++++++++----------------------
 4 files changed, 57 insertions(+), 56 deletions(-)

diff --git a/Documentation/git-test.txt b/Documentation/git-test.txt
index b7c3161..4fa765b 100644
--- a/Documentation/git-test.txt
+++ b/Documentation/git-test.txt
@@ -7,7 +7,7 @@ git-test - evaluates one or more conditions about the state of the git working t
 
 SYNOPSIS
 --------
-The git test API is available in the form of a command and also as a shell library. 
+The git test API is available in the form of a command and also as a shell library.
 
 COMMAND SYNOPSIS
 ----------------
@@ -27,28 +27,28 @@ DESCRIPTION
 `git test` provides a uniform, extensible API for evaluating various conditions that
 pertain to the state of a git working tree, index and repository.
 
-Specified conditions are evaluated from left to right. If any condition evaluates to false, 
-the command conditionally prints a diagnostic message to stderr and sets a 
-non-zero status code. Otherwise, sets a status code of zero. 
+Specified conditions are evaluated from left to right. If any condition evaluates to false,
+the command conditionally prints a diagnostic message to stderr and sets a
+non-zero status code. Otherwise, sets a status code of zero.
 
 The message used to report a assertion failure may be overidden by specifying the --message option.
 
-Diagnostic output resulting from an assertion failure may be suppressed with the -q option. 
-Note that the -q option does not suppress diagnostic output that results from the failure to 
+Diagnostic output resulting from an assertion failure may be suppressed with the -q option.
+Note that the -q option does not suppress diagnostic output that results from the failure to
 successfully parse the arguments that configure the test API.
 
-The `assert` and `test_condition` functions differ according to how they handle failing conditions. 
+The `assert` and `test_condition` functions differ according to how they handle failing conditions.
 
-The `assert` function behaves like the test command but assertion failures will cause 
+The `assert` function behaves like the test command but assertion failures will cause
 the current shell to exit with a non-zero status code. To prevent this occurring, invoke
 the `assert` function within a subshell or use the `test_condition` function instead.
 
 The `test_condition` function will not exit the current shell in the event that an assertion failure
-is detected nor will it generate diagnostic relating to an assertion failure to stderr. 
+is detected nor will it generate diagnostic relating to an assertion failure to stderr.
 
-Note, however, that `test_condition` may still exit the current shell with a non-zero status code 
-if it is unable to successfully parse the arguments presented to it. Callers who need to protect 
-against this possibility either guarantee that the required arguments are available or 
+Note, however, that `test_condition` may still exit the current shell with a non-zero status code
+if it is unable to successfully parse the arguments presented to it. Callers who need to protect
+against this possibility either guarantee that the required arguments are available or
 imbed calls to `test_condition` within a subshell.
 
 The `require_condition_libs` function may be used to include any condition libs listed
@@ -77,7 +77,7 @@ CONDITIONS
 '--detached'|'--not-detached'::
 	Tests if the head is (not) detached.
 '--branch-exists'|'--not-branch-exists branch'::
-        Tests if the specified branch does (not) exist.				    
+	Tests if the specified branch does (not) exist.
 '--tag-exists'|'--not-tag-exists tag'::
         Tests if the specified tag does (not) exist.
 '--ref-exists'|'--not-ref-exists tag'::
@@ -95,15 +95,15 @@ CONDITIONS
 
 EXTENDING THE CONDITION LIBRARY
 -------------------------------
-The library of conditions that the assert API can process may be extended by 
-adding functions of the form check_\{dehyphenated_condition_name\}_N to the 
-shell environment, where \{dehyphenated_condition_name\} is the result of 
-replacing any occurrence of \'-\' in the condition name with \'_\' and 
+The library of conditions that the assert API can process may be extended by
+adding functions of the form check_\{dehyphenated_condition_name\}_N to the
+shell environment, where \{dehyphenated_condition_name\} is the result of
+replacing any occurrence of \'-\' in the condition name with \'_\' and
 N is the number of arguments that need to be passed to the function.
 
-For example, suppose you are writing a script, foo.sh, that includes the git test library 
-and that you want to add a new, 1-argument, condition, foo-X to the library of 
-conditions that can be tested by the git testion framework. 
+For example, suppose you are writing a script, foo.sh, that includes the git test library
+and that you want to add a new, 1-argument, condition, foo-X to the library of
+conditions that can be tested by the git testion framework.
 
 ---------
 #/bin/sh
@@ -133,8 +133,8 @@ executes without generating any output on stdout. The resulting
 state will then be interpreted as condition evaluation failure
 rather than an assertion failure.
 
-To make such conditions available to the git test command line, put the 
-function in a file called ~/foo-lib,sh add a reference 
+To make such conditions available to the git test command line, put the
+function in a file called ~/foo-lib,sh add a reference
 to the library to the git configuration, like so:
 
 ---------
@@ -163,9 +163,9 @@ git test --not-staged --not-unstaged && git reset --hard another-commit
 -----------
 . $(git --exec-path)/git-test-lib
 assert --not-staged   	      	                              # die if there are any staged files
-assert --message "there are staged files" --not-staged	      # die with an alternative message 
+assert --message "there are staged files" --not-staged	      # die with an alternative message
                                                               # if there are any staged files
-test_condition --not-staged || echo "there are staged files"  # check whether there are staged files, 
+test_condition --not-staged || echo "there are staged files"  # check whether there are staged files,
                                                               # but do not die if there are
 -----------
 
diff --git a/git-conditions-lib.sh b/git-conditions-lib.sh
index ec9b516..8933321 100644
--- a/git-conditions-lib.sh
+++ b/git-conditions-lib.sh
@@ -141,13 +141,13 @@ check_reachable_2()
 	first=$(git rev-parse --quiet --verify "$1" 2>/dev/null) || die "'$1' is not a commit"
 	second=$(git rev-parse --quiet --verify "$2" 2>/dev/null) || die "'$2' is not a commit"
 	if test "$first" = "$second" \
-                -o -z "$(git rev-list -n1 "$first" ^"$second")" 
+	    -o -z "$(git rev-list -n1 "$first" ^"$second")"
 	then
 		echo "'$1' is reachable from '$2'."
 	else
 		echo "'$1' is not reachable from '$2'."
 		false
-	fi     
+	fi
 }
 
 check_tree_same_2()
diff --git a/git-test-lib.sh b/git-test-lib.sh
index 159cea6..e0395eb 100644
--- a/git-test-lib.sh
+++ b/git-test-lib.sh
@@ -34,7 +34,7 @@ require_lib()
 require_condition_libs() {
 	eval $(
 		git config --get-all condition.lib | while read lib
-		do 
+		do
 			echo "require_lib \"$lib\" \;"
 		done	
 	)
@@ -42,7 +42,7 @@ require_condition_libs() {
 
 assertion_failed() {
 	rc=$1
-	shift 
+	shift
 	message="${MESSAGE:-$*}"
 	if ! ${QUIET:-false}
 	then
@@ -98,6 +98,7 @@ impl() {
 		shift
 		case $word in
 			--message)
+			test $# -gt 0 || die "--message requires the following argument to be a message"
 			MESSAGE=$1
 			shift
 			continue
@@ -106,7 +107,7 @@ impl() {
 				test $# -gt 0 || die "can't shift 1 argument for --include option"
 				test -f "$1" || die "'$1' must be a file"
 				require_lib "$1"
-				shift 
+				shift
 				continue
 			;;
 			-q)
@@ -143,7 +144,7 @@ impl() {
 
 		exprs="${exprs}${exprs:+ }${negation}${negation:+ }$word $try check_${dehyphenated}_$try $args"
 
-	done 
+	done
 
 	set -- $exprs
 	while test $# -gt 0
@@ -158,7 +159,7 @@ impl() {
 		word=$1
 		nargs=$2
 		shift 2
-		message=$(eval $negation "$@") 
+		message=$(eval $negation "$@")
 		rc=$?
 		if test $rc -ne 0
 		then
diff --git a/t/t1520-test.sh b/t/t1520-test.sh
index 1db3598..3e0571b 100755
--- a/t/t1520-test.sh
+++ b/t/t1520-test.sh
@@ -46,7 +46,7 @@ test_expect_assertion_failure()
 {
 	test=$1
 	message=$2
-	shift 
+	shift
 	test_expect_success $1 \
 "
 	! actual_message=$(git test "$@" 1>&2) &&
@@ -62,11 +62,11 @@ test_expect_assertion_failure()
 
 test_expect_success 'setup' \
 '
-	git add test-assertions-lib.sh empty-assertions-lib.sh && 
+	git add test-assertions-lib.sh empty-assertions-lib.sh &&
 	test_commit base &&
 	test_commit A &&
 	git checkout A^1 &&
-	test_commit B && 
+	test_commit B &&
 	git checkout master &&
 	test_merge M B &&
 	echo C >> B.t &&
@@ -85,7 +85,7 @@ test_expect_success 'setup' \
 	git checkout A^0 -- &&
 	test_commit G &&
 	git checkout master &&
-	git reset --hard D     
+	git reset --hard D
 '
 
 test_expect_success 'git test # no arguments' \
@@ -194,7 +194,7 @@ test_expect_success 'git test --not-unstaged' \
 
 test_expect_success 'git test --unstaged # when there are unstaged files' \
 '
-	test_when_finished "git reset --hard HEAD && git checkout master" && 
+	test_when_finished "git reset --hard HEAD && git checkout master" &&
 	git checkout -f M^0 &&
 	git stash apply --index STASH_UNSTAGED &&
 	git test --unstaged
@@ -202,7 +202,7 @@ test_expect_success 'git test --unstaged # when there are unstaged files' \
 
 test_expect_success 'git test --not-unstaged # when there are unstaged files - should fail' \
 '
-	test_when_finished "git reset --hard HEAD && git checkout master" && 
+	test_when_finished "git reset --hard HEAD && git checkout master" &&
 	git checkout -f M^0 &&
 	git stash apply --index STASH_UNSTAGED &&
 	test_must_fail git test --not-unstaged
@@ -210,7 +210,7 @@ test_expect_success 'git test --not-unstaged # when there are unstaged files - s
 
 test_expect_success 'git test --unstaged # when there are only staged files' \
 '
-	test_when_finished "git reset --hard HEAD && git checkout master" && 
+	test_when_finished "git reset --hard HEAD && git checkout master" &&
 	git checkout -f M^0 &&
 	git stash apply --index STASH_STAGED &&
 	git test --not-unstaged
@@ -228,7 +228,7 @@ test_expect_success 'git test --not-staged' \
 
 test_expect_success 'git test --staged # when there are staged files' \
 '
-	test_when_finished "git reset --hard HEAD && git checkout master" && 
+	test_when_finished "git reset --hard HEAD && git checkout master" &&
 	git checkout -f M^0 &&
 	git stash apply --index STASH_STAGED &&
 	git test --staged
@@ -236,7 +236,7 @@ test_expect_success 'git test --staged # when there are staged files' \
 
 test_expect_success 'git test --not-staged # when there are staged files - should fail' \
 '
-	test_when_finished "git reset --hard HEAD && git checkout master" && 
+	test_when_finished "git reset --hard HEAD && git checkout master" &&
 	git checkout -f M^0 &&
 	git stash apply --index STASH_STAGED &&
 	test_must_fail git test --not-staged
@@ -244,7 +244,7 @@ test_expect_success 'git test --not-staged # when there are staged files - shoul
 
 test_expect_success 'git test --staged # when there are only unstaged files' \
 '
-	test_when_finished "git reset --hard HEAD && git checkout master" && 
+	test_when_finished "git reset --hard HEAD && git checkout master" &&
 	git checkout -f M^0 &&
 	git stash apply --index STASH_UNSTAGED &&
 	git test --not-staged
@@ -262,14 +262,14 @@ test_expect_success 'git test --not-untracked' \
 
 test_expect_success 'git test --untracked # when there are untracked files' \
 '
-	test_when_finished "git clean -fd" && 
+	test_when_finished "git clean -fd" &&
 	:> untracked &&
 	git test --untracked
 '
 
 test_expect_success 'git test --not-untracked # when there are untracked files - should fail' \
 '
-	test_when_finished "git clean -fd" && 
+	test_when_finished "git clean -fd" &&
 	:> untracked &&
 	test_must_fail git test --not-untracked
 '
@@ -286,14 +286,14 @@ test_expect_success 'git test --detached # should fail' \
 
 test_expect_success 'git test --not-detached # when detached, should fail' \
 '
-	test_when_finished "git checkout -f master" && 
+	test_when_finished "git checkout -f master" &&
 	git checkout HEAD^0 &&
 	test_must_fail git test --not-detached
 '
 
 test_expect_success 'git test --detached # when detached' \
 '
-	test_when_finished "git checkout -f master" && 
+	test_when_finished "git checkout -f master" &&
 	git checkout HEAD^0 &&
 	git test --detached
 '
@@ -413,7 +413,7 @@ test_expect_success 'same' \
    git test \
       --same master HEAD \
       --not-same D D1 \
-      --not-same C D 
+      --not-same C D
 '
 
 test_expect_success 'clean' \
@@ -424,7 +424,7 @@ test_expect_success 'clean' \
        --not-detached \
        --not-untracked \
        --not-rebasing \
-       --not-conflicted 
+       --not-conflicted
 '
 
 test_expect_success 'existence' \
@@ -439,7 +439,7 @@ test_expect_success 'existence' \
        --tag-exists D \
        --not-tag-exists N \
        --not-tag-exists master \
-       --not-branch-exists A 
+       --not-branch-exists A
 '
 
 test_expect_success 'git test --conflicted # should fail' \
@@ -454,17 +454,17 @@ test_expect_success 'git test --not-conflicted' \
 
 test_expect_success 'git test --conflicted' \
 '
-	test_when_finished "git reset --hard HEAD" && 
+	test_when_finished "git reset --hard HEAD" &&
         ! git merge F &&
-	git test --conflicted 
+	git test --conflicted
 	
 '
 
 test_expect_success 'git test --not-conflicted # should fail when there are conflcted files' \
 '
-	test_when_finished "git reset --hard HEAD" && 
+	test_when_finished "git reset --hard HEAD" &&
         ! git merge F &&
-	test_must_fail git test --not-conflicted 
+	test_must_fail git test --not-conflicted
 	
 '
 
@@ -482,12 +482,12 @@ test_expect_success 'git test --rebasing' \
 '
 	test_when_finished "
 		git reset --hard HEAD &&
-		git checkout -f master && 
+		git checkout -f master &&
 		git branch -D rebase
-	" && 
+	" &&
         git branch rebase F &&
         ! git rebase --onto D F~1 F
-	git test --rebasing 
+	git test --rebasing
 	
 '
 
@@ -497,7 +497,7 @@ test_expect_success 'git test --not-rebasing' \
 		git reset --hard HEAD &&
 		git checkout -f master &&
 		git branch -D rebase
-	" && 
+	" &&
         git branch rebase F &&
         ! git rebase --onto D F~1 F
 	test_must_fail git test --not-rebasing
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 21/23] Introduce git-atomic.
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (19 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 20/23] test: git test cleanups Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 22/23] Introduce git base Jon Seymour
                   ` (2 subsequent siblings)
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

git atomic is intended to provide a simple way to for porcelains to
execute atomic operations on the git repository.

An atomic operation either completes successfully or the
working tree, index and selected references are returned to their
original state.

Signed-off-by: Jon Seymour <jon.seymour@gmail.com>
---
 .gitignore                   |    2 +
 Documentation/git-atomic.txt |   92 ++++++++++++++++++++++++++++++++++++++++++
 Makefile                     |    2 +
 git-atomic-lib.sh            |   58 ++++++++++++++++++++++++++
 git-atomic.sh                |    5 ++
 t/t3419-atomic.sh            |   59 +++++++++++++++++++++++++++
 6 files changed, 218 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/git-atomic.txt
 create mode 100644 git-atomic-lib.sh
 create mode 100755 git-atomic.sh
 create mode 100755 t/t3419-atomic.sh

diff --git a/.gitignore b/.gitignore
index aa0eb8fb..5efc43c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,8 @@
 /git-apply
 /git-archimport
 /git-archive
+/git-atomic
+/git-atomic-lib
 /git-bisect
 /git-bisect--helper
 /git-blame
diff --git a/Documentation/git-atomic.txt b/Documentation/git-atomic.txt
new file mode 100644
index 0000000..02bbee6
--- /dev/null
+++ b/Documentation/git-atomic.txt
@@ -0,0 +1,92 @@
+git-atomic(1)
+===========
+
+NAME
+----
+git-atomic - conditionally execute a command and, if it fails, restore the working tree, index and HEAD to their original state.
+
+SYNOPSIS
+--------
+[verse]
+'git atomic' [options] [--] [cmd args...]
+. $(git --exec-path)/git-atomic-lib.sh
+
+DESCRIPTION
+-----------
+The first form conditionally executes a command depending on whether the pre-conditions are satisified. If the command exits with a non-zero exit code, restores the
+working tree, index and HEAD to their orignal state. If no command is specified, sets the exit code according to the pre-conditions.
+
+'git atomic' exits with:
+
+0::
+	if the pre-conditions are satisified and the command executes successfully
+128::
+	if the command exited with an exit code of 128, 129, 130 or 131.
+129::
+	if git atomic failed during setup or argument parsing
+130::
+	if the pre-conditions were not satisified.
+131::
+	if the command failed and the original state could not be restored.
+
+Otherwise, exits with the actual exit code of the command.
+
+The second form is used to import the definition of a shell function called atomic that can be called by shell scripts that perform git operations. This is useful when the operations to be given atomic behaviour are themselves shell functions rather than external commands.
+
+OPTIONS
+-------
+The following options specify pre-condition tests on various lists which are assumed to be empty in the clean state and non-empty in the dirty state.
+
+The default required state for each specified pre-condition option is clean. The default required state for each unspecified pre-condition option is any, which
+means no tests are applied.
+
+--unstaged [any|clean|dirty]::
+	Fail unless the list of unstaged changes is empty (clean) or non-empty (dirty).	
+--staged   [any|clean|dirty]::
+	Fail unless the list of staged changes is empty (clean) or non-empty (dirty).
+--untracked [any|clean|dirty]::
+	Fail unless the list of untracked files is empty (clean) or non-empty (dirty).
+--tracked [any|clean|dirty]::
+	Fail unless the list of staged and unstaged changes is empty (clean) or non-empty (dirty).
+--unmerged [any|clean|dirty]::
+	Fail unless the list of unmerged files is empty (clean) or non-empty (dirty).
+--rebase [any|clean|dirty]::
+	Fail unless a rebase is not (clean) or is (dirty) in progress.
+--all::
+	Fail unless the list of staged, unstaged changes and untracked files is empty (clean) or non-empty (dirty)
+
+EXAMPLES
+--------
+* Reset the tree only if there are no staged or unstaged changes and no untracked files.
++
+-----------
+git atomic --all clean git reset --hard upstream/master
+-----------
+* Conditionally perform a merge, but rollback and die if it fails.
++
+-----------
+git atomic --all clean git merge topic || die "unable to merge"
+-----------
+* Import git-atomic-lib.sh into a script and make the execution of the function foo atomic with respect to the state of the git workspace
++
+-----------
+. $(git --exec-path)/git-atomic-lib.sh
+foo()
+{
+    git merge $1
+}
+
+atomic foo $1
+-----------
+
+Author
+------
+Written by Jon Seymour <jon.seymour@gmail.com>
+
+Documentation
+-------------
+Documentation by Jon Seymour
+
+GIT
+---
+Part of the linkgit:git[7] suite
diff --git a/Makefile b/Makefile
index 93ff6c6..9ed877a 100644
--- a/Makefile
+++ b/Makefile
@@ -360,6 +360,7 @@ TEST_PROGRAMS_NEED_X =
 unexport CDPATH
 
 SCRIPT_SH += git-am.sh
+SCRIPT_SH += git-atomic.sh
 SCRIPT_SH += git-bisect.sh
 SCRIPT_SH += git-difftool--helper.sh
 SCRIPT_SH += git-filter-branch.sh
@@ -379,6 +380,7 @@ SCRIPT_SH += git-submodule.sh
 SCRIPT_SH += git-test.sh
 SCRIPT_SH += git-web--browse.sh
 
+SCRIPT_LIB += git-atomic-lib
 SCRIPT_LIB += git-conditions-lib
 SCRIPT_LIB += git-mergetool--lib
 SCRIPT_LIB += git-parse-remote
diff --git a/git-atomic-lib.sh b/git-atomic-lib.sh
new file mode 100644
index 0000000..db48300
--- /dev/null
+++ b/git-atomic-lib.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+#
+# Provides a function that provides for robust recovery from
+#
+. git-test-lib
+atomic()
+{
+    assert --not-conflicted --message "cannot perform an atomic operation while there are merge conflicts"
+    HEAD=$(git rev-parse --verify HEAD) || setup_failed "failed to resolve HEAD"
+    if REF=$(git symbolic-ref -q HEAD)
+    then
+          BRANCH=${REF#refs/heads/}
+    else
+        BRANCH=${HEAD}
+    fi
+
+    STASH=$(git stash create) || setup_failed "failed to stash"
+    REF=$(git rev-parse --symbolic-full-name HEAD) || setup_failed "failed to acquire REF"
+    REBASE_COUNT=1
+    test_condition -q --rebasing || REBASE_COUNT=0
+
+    (
+      "$@"
+    ) || (
+       RC=$?
+
+       command_failed()
+       {
+           rc=$1
+	   shift
+           echo "command failed: $* rc=$rc" 1>&2
+           exit 1
+       }
+
+       restore_failed()
+       {
+           echo "restore failed: $*" 1>&2
+           exit 2
+       }
+
+       if test $REBASE_COUNT -eq 0 && test -d "$REBASE_DIR"
+       then
+            git rebase --abort || restore_failed "failed to abort rebase"
+       fi
+
+	{
+		git reset --hard HEAD &&
+		git checkout -q ${BRANCH}
+	} || restore_failed "failed to checkout ${BRANCH}"
+
+	if test -n "$STASH"
+	then
+		git stash apply --index "$STASH" || restore_failed "failed to reapply stash $STASH to $HEAD"
+		echo "restored $REF to $(git describe --always --abbrev=6 $HEAD), reapplied stash $(git describe --always --abbrev=6 $STASH)" 1>&2
+	fi
+	command_failed $RC "$*"
+    )
+}
diff --git a/git-atomic.sh b/git-atomic.sh
new file mode 100755
index 0000000..aa14e68
--- /dev/null
+++ b/git-atomic.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+SUBDIRECTORY_OK=true
+. git-sh-setup
+. git-atomic-lib
+atomic "$@"
diff --git a/t/t3419-atomic.sh b/t/t3419-atomic.sh
new file mode 100755
index 0000000..2b651d2
--- /dev/null
+++ b/t/t3419-atomic.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Jon Seymour
+#
+
+test_description='git atomic tests
+
+Performs tests on the functions of git atomic
+'
+. ./test-lib.sh
+
+GIT_AUTHOR_NAME=author@name
+GIT_AUTHOR_EMAIL=bogus@email@address
+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
+
+test_expect_success \
+    'setup' \
+    '
+    test_commit A &&
+    test_commit B &&
+    test_commit C &&
+    test_commit D &&
+    git checkout -b branch A &&
+    test_commit X &&
+    echo >> B.t &&
+    git add B.t &&
+    test_commit Y
+    true
+'
+
+test_expect_success 'no arguments' '
+    git atomic
+'
+
+test_expect_success 'successful command' \
+'
+    git atomic true
+'
+
+test_expect_success 'unsuccessful command' \
+'
+    ! git atomic false
+'
+
+test_expect_success 'rebase' \
+'
+     git reset --hard HEAD &&
+     git checkout master &&
+     MASTER=$(git rev-parse HEAD) &&
+     ! git rebase --onto D A Y &&
+     git test --conflicted &&
+     git rebase --abort &&
+     git checkout master &&
+     ! git atomic git rebase --onto D A Y &&
+     git test --same HEAD refs/heads/master &&
+     git test --same HEAD $MASTER
+'
+
+test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 22/23] Introduce git base.
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (20 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 21/23] Introduce git-atomic Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  7:22 ` [PATCH 23/23] Introduce support for the git-work command Jon Seymour
  2011-04-23  9:13 ` [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Peter Baumann
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

git base is intended to provide a way to track the 'base'
of the current branch, where the base is one of several possible
commits that satisfies the following invariant.

The base is reachable from the head the output
git rev-list base..head --merges is empty.

git base is intended to provide a sane way to keep
track of the commit at the base of the current branch
and thereby simplify the process of rebasing, or more
generally, dependency managment.

For example of how it is used, see the following git-work
command.

Signed-off-by: Jon Seymour <jon.seymour@gmail.com>
---
 .gitignore                 |    1 +
 Documentation/config.txt   |   10 ++
 Documentation/git-base.txt |  216 +++++++++++++++++++++++++
 Makefile                   |    1 +
 git-base.sh                |  378 ++++++++++++++++++++++++++++++++++++++++++++
 t/t3418-base.sh            |  214 +++++++++++++++++++++++++
 6 files changed, 820 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/git-base.txt
 create mode 100644 git-base.sh
 create mode 100755 t/t3418-base.sh

diff --git a/.gitignore b/.gitignore
index 5efc43c..54fa567 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@
 /git-archive
 /git-atomic
 /git-atomic-lib
+/git-base
 /git-bisect
 /git-bisect--helper
 /git-blame
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 750c86d..52f149b 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -636,6 +636,16 @@ branch.autosetuprebase::
 	branch to track another branch.
 	This option defaults to never.
 
+branch.<name>.baseresetoptions::
+	The options passed to 'git base set' when 'git base' detects
+	that the current base reference is either not set or no longer
+	satisfies the base invariant.
+	See the description of the `init` command in linkgit:git-base[1]
+	for details of how this value is initialized.
+	See the description of the `set` command in linkgit:git-base[1]
+	for details about this value is interpreted.
+	Defaults to --fail unless set.
+
 branch.<name>.remote::
 	When in branch <name>, it tells 'git fetch' and 'git push' which
 	remote to fetch from/push to.  It defaults to `origin` if no remote is
diff --git a/Documentation/git-base.txt b/Documentation/git-base.txt
new file mode 100644
index 0000000..7a7a710
--- /dev/null
+++ b/Documentation/git-base.txt
@@ -0,0 +1,216 @@
+git-base(1)
+===========
+
+NAME
+----
+git-base - display, check, set, clear and reset the base of a working branch
+
+SYNOPSIS
+--------
+[verse]
+'git base' [-b branch] [--as-ref]|[<default-commit>]
+'git base' [-b branch] 'check' <commit>
+'git base' [-b branch] 'set' [-f] <commit>
+'git base' [-b branch] 'clear'
+'git base' [-b branch] 'init' [-d]|[<reset-cmd>]
+'git base' [-b branch] 'reset'
+
+DESCRIPTION
+-----------
+In the discussion that follows, the current branch or the branch specified by -b is referred to by the symbol `<branch>` and the commit at the base of `<branch>` is referred to by the symbol `<base>`. `<head>` is used to refer to the commit at the tip of `<branch>` as distinct from the branch reference itself.
+
+`git base` helps to track the base of a working branch. For the purposes of the exposition here, the base of the working branch is defined as the
+last commit in the history that is not part of your current work - it is the basis of your current work, but is not part of the work itself. Furthermore,
+to be classified as a base, the path from the tip of the branch to the base must not traverse a merge commit. This constraint means that the range
+`<base>..<bramch>` is always a linear series of commits that can be easily rebased and re-organized as required.
+
+COMMANDS
+--------
+Without positional arguments, this command checks whether the base reference satisfies the base invariant. If it does, prints the SHA1 hash of the base commit
+to stdout and exits with a status code of 0.
+
+Otherwise, if `<default-commit>` is specified, calls `set <default-commit>`. If this fails to establish the base invariant or if no `<default-commit>`
+is specified then calls `reset`. If the base reference now satisfies the base invariant, print its SHA1 has to stdout and
+exits with a status code of 0. Otherwise, prints a warning message to stderr and exits with a non-zero status code.
+
+If `--as-ref` is specifed, then the symbolic name of the base reference is printed instead of the SHA1 hash but `reset` is not called, even
+if the base reference does not satisfy the base invariant.
+
+If a `<default-commit>` is specified `set <default-commit>` is called
+
+`init [-d]|[<reset-cmd>]`::
+
+Initializes the command to be used when `reset` is called. It must be one of `set <commit>`, `clear` or `check`.
+`<reset-cmd>` defaults to the `set <upstream-branch>` if there is one or to `set <head>`, otherwise.
+Use a `<reset-cmd>` of `check` to prevent the default `git base` command automatically
+adjusting an inconsistent base reference. Use a `<reset-cmd>` of `clear` to automatically clear an inconsistent base reference.
+
+Use -d to delete the base reference and reset configuration associated with the branch.
+
+`clear`::
+
+Clears the base reference, if any. The status code is always set to a non-zero value and a warning message is printed to stderr. No output is
+printed to stdout.
+
+`set [-f] <commit>`::
+
+Checks that the specified base satisifies the base invariant of `<branch>`, and if so, updates the base reference with the specified commit.
+Otherwise, updates the base reference to the commit closest to both `<commit>` and `<branch>` which does satisfy the base invariant. The selected
+commit will always be reachable from `<branch>` but may not be reachable from `<commit>`. In particular, if the merge base of `<commit>` and `<branch>`
+is hidden by a merge commit, then the selected commit will be (the only) merge commit that satisfies the base invariant of `<branch>`.
++
+The status and output are set/generated as per `git base`.
++
+`-f` can be used to force the update of the base reference to the specified `<commit>` even if the `<commit>` does not satify the base
+invariant. Note: however, that unless the effective `reset` command is set to `check`, this value will not stick beyond the next
+call to `git base`.
+
+`check` [<commit>]::
+
+If `<commit>` is specified, performs the same function as `set` but does not actually update the base reference. The status code and output are set/generated as they would be if `set` had been called.
++
+If `<commit>` is not specified, sets the status code according to whether (0) or not (non-zero) the base reference is well-defined and consistent with the base invariant. The output that is generated is the same output that would be generated if `set` was called with the same arguments.
++
+`check` NEVER adjusts the base reference.
+
+`reset`::
+
+Resets the base reference by calling the git base with the configured `<reset-cmd>` or `clear` if there is no such configuration. The status
+and output are set/generated as per `git base`.
+
+OUTPUT
+------
+Unless --as-ref is used, the only output git base generates is the SHA1 hash of a commit that satisfies the base invariant.  If `git base`, `git base set` or `git check` (without arguments) generate output, then the output will be the current base reference at the time the command completes.  If `git check` is called with an
+argument, then the output is set as if `git set` had been called. If --as-ref is used, the generated output is always the symbolic name of the base reference, whether or not the base reference actually exists or satisfies the base invariant.
+
+EXIT CODE
+---------
+Except for the `init` command, `git base` exits with status code of zero if command has written the SHA1 hash of a commit that satisfies the base invariant to stdout, and exits with a non-zero status code otherwise.
+
+OPTIONS
+-------
+-b,--branch branch::
+	If this option is specified and supported, the commands apply to the specified branch. Otherwise, the commands apply to the current branch.
+-f::
+	Force the update of the base reference even if the specified value does not satisfy the base invariant.
+-q::
+	Use this option to suppress data output, information and warning messages.
+--as-ref::
+	Print the SHA1 hash of the base as a symbolic reference.
+
+CONFIGURATION
+-------------
+branch.<name>.baseresetcmd::
+	Configures the `git base` subcommand used by `git base reset`. Must be one of `set <commit>`, `check` or `clear`. Defaults to `clear`, if not specified.
+
+THE BASE INVARIANT
+------------------
+The base invariant applies to all commits that can be described as a base of a branch.
+
+The invariant is that the base commit is reachable from the head of the branch and that the path between the head of the branch and the base contains no merge commits other than, possibly, the base commit itself.
+
+The head of a branch is also a base commit for the branch. As such, each branch has at least one commit that satisfies the base invariant.
+For any given branch there is at most one merge commit that satisifies the base invariant.
+
+The base invariant is defined as it is because the history between a commit satisifying the base invariant and the head of the branch is, by definition,
+guaranteed to be linear.
+
+BASE REFERENCE
+--------------
+The base reference is semi-automatically managed by the git.
+
+It is explicitly updated by the `set` and `clear` commands and sometimes by the `reset` command, depending on how it has been configured.
+It may be implicitly updated by the default command (via a call to `reset`) but only when it is undefined or does not satisfy the base reference.
+It is never updated by the `check` or `init` commands. Other git tools such as linkgit:git-work[1] use git base to update the base reference as required.
+
+Users may use the `set` command with the -f option to modify the base reference to any value but values that fail to match the base invariant will quickly
+be reset by the automatic operation of `git base` unless the reset operation is configured to be `check`.
+
+A non-empty output from `git base`, `git base reset` or `git base set` is guaranteed to be consistent with the base invariant immediately after the command
+completes.
+
+THE STATE MACHINE VIEW
+----------------------
+A good way to think about `git base` is as the controls of a 3-state state machine: a machine that has the states: UNDEFINED, INCONSISTENT and CONSISTENT
+which correspond to an undefined base reference, a base reference that does not satisfy the base invariant and a base reference that does satisfy
+the base invariant, respectively.
+
+The state machine prefers to be in the CONSISTENT state. `git base` without arguments will leave the state of the base reference unchanged if is CONSISTENT
+or will call `git base reset` otherwise.  `git base reset` uses the `<reset-cmd>` defined with `git base init` to implement the auto-recovery policy
+of the state machine when it finds itself in the INCONSISTENT or UNDEFINED states.
+
+`git base check` tests the state of the base reference, but leaves its state unchanged.
+
+`git base clear` unconditionally clears base reference forcing the state to be UNDEFINED.
+
+`git base set` without a `-f` option is used to force the machine into a CONSISTENT state.
+
+EXAMPLES
+--------
+* Initialize the branch so that the base reference is always reset, when required, relative to upstream/master
++
+----------
+git base init set upstream/master
+----------
+* Display the current base
++
+---------
+git base
+---------
+* Display the current base reference without updating it
++
+---------
+git base check
+---------
+* Display the current base as a symbolic reference
++
+---------
+git base --as-ref
+---------
+* Set the base of the current branch to the 3rd commit from the tip
++
+---------
+git base set HEAD~3
+---------
+* Interactively rebase the current work
++
+---------
+git rebase -i $(git base) HEAD
+---------
+* Perform a rebase, but only if the base reference is currently valid
++
+---------
+git base check -q && git rebase -i $(git base) HEAD
+---------
+* Set the base relative to an upstream branch
++
+---------
+git base set origin/master
+---------
+
+
+FILES
+-----
+
+.git/refs/bases/`<branch>`::
+	Contains the last calculated value of the base for a specific branch. This value is not guaranteed to be correct except immediately after a successful execution of git base. Users that require an accurate value should use the output of git base -b `<branch>`.
+
+.git/refs/BASE::
+	If HEAD is a symbolic reference of the form `refs/heads/<branch>`, then BASE will be a symbolic reference of the form `refs/bases/<branch>`. Otherwise,
+	it contains the base commit corresponding to the detached HEAD, if one has been specified.
+
+SEE ALSO
+--------
+linkgit:git-work[1]
+
+Author
+------
+Written by Jon Seymour <jon.seymour@gmail.com>
+
+Documentation
+-------------
+Documentation by Jon Seymour
+
+GIT
+---
+Part of the linkgit:git[7] suite
diff --git a/Makefile b/Makefile
index 9ed877a..6aee4a8 100644
--- a/Makefile
+++ b/Makefile
@@ -361,6 +361,7 @@ unexport CDPATH
 
 SCRIPT_SH += git-am.sh
 SCRIPT_SH += git-atomic.sh
+SCRIPT_SH += git-base.sh
 SCRIPT_SH += git-bisect.sh
 SCRIPT_SH += git-difftool--helper.sh
 SCRIPT_SH += git-filter-branch.sh
diff --git a/git-base.sh b/git-base.sh
new file mode 100644
index 0000000..14c64c9
--- /dev/null
+++ b/git-base.sh
@@ -0,0 +1,378 @@
+#!/bin/sh
+#
+# (c) Copyright Jon Seymour 2010
+#
+USAGE='[help|set|check|reset|init]'
+LONG_USAGE='
+git base [--as-ref]
+        print the SHA1 hash of the base reference or its symbolic name (--as-ref)
+git base set [-f] <commit>
+        set the base of the branch to commit nearest <commit> that satisifies the base invariant.
+git base clear
+        As per set, but never update the base reference.
+git base check <commit>
+        test if the specified commit satisfies the base invariant
+git base init <reset-cmd>
+	Initialise the git base command to be called when git base reset is invoked.
+git base reset
+	Invoke the configured reset command (see git base init) or git base clear, if not such command is configured.
+git base help
+        print this help
+
+Please use "git help base" to get the full man page.'
+
+OPTIONS_SPEC=
+
+SUBDIRECTORY_OK=true
+. git-sh-setup
+require_work_tree
+
+warn()
+{
+    test -n "$QUIET" || echo "$*" 1>&2
+}
+
+info()
+{
+    test -n "$QUIET" || echo "$*" 1>&2
+}
+
+data()
+{
+	if test "$*" != ""
+	then
+		echo "$*"
+		true
+	else
+		false
+	fi
+}
+
+quietly()
+{
+	if test -n "$QUIET"
+	then
+		"$@" >/dev/null 2>/dev/null
+	else
+		"$@"
+	fi
+}
+
+assert_valid_branch()
+{
+	test -n "${VALID_BRANCH}" || die "${BRANCH} is not a branch"
+}
+
+invariant_state()
+{
+	commit=$1
+	if test -z "$commit"
+	then
+		echo "UNDEFINED"
+		false
+	elif ! git rev-parse --quiet --verify "$commit" >/dev/null
+	then
+		echo "INVALID"
+		false
+	elif ! git test -q --reachable $commit $HEAD
+	then
+		echo "UNREACHABLE"
+		false
+	elif ! test -z "$(last_merge $HEAD $commit)"
+	then
+		echo "HIDDEN"
+		false
+	else
+		echo "CONSISTENT"
+		true
+	fi
+}
+
+closest()
+{
+	commit=$1
+	state=${2:-$(invariant_state $commit)}
+	
+	case $state in	
+		UNDEFINED|INVALID)
+			:
+		;;
+		UNREACHABLE)
+			closest $(git merge-base $HEAD $commit)
+		;;
+		HIDDEN)
+			last_merge $HEAD $commit
+		;;
+		CONSISTENT)
+			echo $commit
+		;;
+	esac
+}
+
+describe()
+{
+	commit=$1
+	state=$2
+	
+	case $state in
+		UNDEFINED)
+			echo "No commit specified."
+		;;
+		INVALID)
+			echo "$commit is not a valid reference."
+		;;
+		HIDDEN)
+			echo "The commit $(short_ref $commit) is not a base of ${BRANCH} because it is hidden by the merge commit $(short_ref $(last_merge $HEAD $commit))."
+		;;
+		UNREACHABLE)
+			echo "The commit $(short_ref $commit) is not a base of ${BRANCH} because it is unreachable from $(short_ref $HEAD)."
+		;;
+		CONSISTENT)
+			echo "The commit $(short_ref $commit) satisfies the base invariant of $(short_ref $HEAD)."
+		;;
+	esac
+}
+
+last_merge()
+{
+    head=$1
+    commit=$2
+    data $(git rev-list --max-count=1 --merges ${head} ^$commit)
+}
+
+short_ref()
+{
+    data "($(git rev-parse --short $1))"
+}
+
+base_default()
+{
+	assert_valid_branch
+
+	revs=$(git rev-parse --revs-only "$@")
+
+	if state=$(invariant_state "${BASEREF}")	
+	then
+		if test -z "$ASREF"
+		then
+			git rev-parse ${BASEREF}
+		else
+			echo "${BASEREF}"
+		fi
+	else
+		if test -z "$ASREF"
+		then
+			if test -n "$revs"
+			then
+				base_set $revs && return 0
+			fi
+	
+			describe "${BASEREF}" $state 1>&2
+			base_reset
+		else
+			describe "${BASEREF}" $state 1>&2
+			echo "${BASEREF}"
+			false
+		fi
+	fi
+}
+
+base_init()
+{
+	if test -n "$DELETE"
+	then
+		git update-ref -d ${BASEREF} &&
+		git config branch.${BRANCH}.baseresetcmd clear &&
+		git config --unset branch.${BRANCH}.baseresetcmd
+		return 0
+	fi
+
+	assert_valid_branch
+
+	if test $# -eq 0
+	then
+		set -- $(git config branch.${BRANCH}.merge)
+		MERGE=$1
+		set -- $(git config branch.${BRANCH}.remote)
+		case "$1" in	
+		.)
+			OPTION="set ${MERGE}"
+		;;
+		"")
+			OPTION="set $(git rev-parse ${BRANCH})"
+		;;
+		*)
+			OPTION="set $1/${MERGE#refs/heads/}"
+		;;
+		esac
+		set -- ${OPTION}
+	else
+		case "$1" in	
+			set|clear|check)
+			:
+			;;
+		*)
+			die "$1 is not a valid 'git base' command"
+			;;
+		esac
+	fi
+
+	warn "The reset command for ${BRANCH} is now '$*'."
+	git config branch.${BRANCH}.baseresetcmd "$*" || die "failed to update branch.${BRANCH}.baseresetcmd"
+}
+
+base_reset()
+{
+	assert_valid_branch
+
+	options=$(git config branch.${BRANCH}.baseresetcmd)
+	options=${options:-clear}
+
+	warn "Resetting the base of ${BRANCH} with 'git base ${options}'."
+	git base -b ${BRANCH} ${options}
+}
+
+base_check()
+{
+	assert_valid_branch
+
+	specified=$1
+	commit=${specified:-${BASEREF}}
+
+	if state=$(invariant_state "$commit")
+	then
+		git rev-parse $commit
+	else
+		describe "$commit" $state 1>&2
+		false
+	fi
+}
+
+base_set()
+{
+	assert_valid_branch
+
+	USAGE="usage: git base set [-f] <commit>"
+	test -n "$1" || die "$USAGE"
+
+	commit=$1
+	
+	if state=$(invariant_state "$commit")
+	then
+		git update-ref "${BASEREF}" "$commit" || die "failed to update $BASEREF"
+		echo $(git rev-parse $commit)
+	else
+
+		case $state in	
+			INVALID)
+				die "The specified reference $commit is not a valid."
+			;;
+			HIDDEN|UNREACHABLE)
+				closest=$(closest $commit)
+				describe "$commit" "$state" 1>&2
+				if test -z "$FORCE"
+				then
+					warn "Updating the base of ${BRANCH} to a consistent value $(short_ref $closest)."
+					git update-ref ${BASEREF} $closest || die "failed to update $BASEREF"
+					echo $closest
+					true
+				else
+					warn "Updating the base of ${BRANCH} to an inconsistent value $(short_ref $commit)."
+					git update-ref ${BASEREF} $commit || die "failed to update $BASEREF"
+					false
+				fi
+			;;
+			CONSISTENT)
+				git update-ref ${BASEREF} $commit || die "failed to update $BASEREF"
+				echo $commit
+				true
+			;;
+			*)
+				die "should never happen - invalid state $state"
+			;;
+		esac
+
+	fi
+}
+
+base_clear()
+{
+   git update-ref -d ${BASEREF} >/dev/null || die "failed to clear $BASEREF"
+   warn "The base of ${BRANCH} has been cleared."
+   false
+}
+
+base_help()
+{
+   git base -h "$@"
+}
+
+VALID_BRANCH=
+QUIET=
+POSITIONAL=
+FORCE=
+DELETE=
+
+BRANCHREF=$(git symbolic-ref -q HEAD) || BRANCHREF=HEAD
+BRANCH=${BRANCHREF#refs/heads/}
+VALID_BRANCH=t
+
+while test $# -gt 0
+do
+	arg=$1
+	shift
+
+	case "$arg" in
+	       -b)
+			test -n "$1" || die "-b requires a branch to be specified"
+		        BRANCH=$1 && shift
+			BRANCHREF=$(git rev-parse --quiet --symbolic-full-name --verify "${BRANCH}" --)
+			BRANCH=${BRANCHREF#refs/heads/}
+			test "${BRANCH}" = "${BRANCHREF}" || VALID_BRANCH=t
+			test "${BRANCH}" = "HEAD" && VALID_BRANCH=t
+			test -n "${BRANCHREF}"  || VALID_BRANCH=
+		;;
+		-f)
+			FORCE=-f;
+		;;
+                -d)
+                        DELETE=-d;
+                ;;
+		-q)
+			QUIET=-q
+	        ;;
+		--)
+			break;			
+		;;
+		--as-ref)	
+			ASREF=--as-ref;
+		;;	
+		default|check|clear|init|reset|set|help)
+			if test -z "$CMD"
+			then	
+				CMD=$arg
+			else
+				POSITIONAL="${POSITIONAL}${POSITIONAL:+ }$arg"
+			fi
+		;;
+		*)
+			POSITIONAL="${POSITIONAL}${POSITIONAL:+ }$arg"
+	        ;;
+	esac
+done
+
+CMD=${CMD:-default}
+
+set -- $POSITIONAL
+
+if test "${BRANCH}" = "HEAD"
+then
+	BASEREF=BASE
+else
+	BASEREF=refs/bases/${BRANCH}
+fi
+
+HEAD=$(git rev-parse --quiet --verify ${BRANCHREF})
+BASE=$(git rev-parse --quiet --verify ${BASEREF})
+
+quietly base_$CMD "$@"
diff --git a/t/t3418-base.sh b/t/t3418-base.sh
new file mode 100755
index 0000000..8e2defb
--- /dev/null
+++ b/t/t3418-base.sh
@@ -0,0 +1,214 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Jon Seymour
+#
+
+test_description='git base tests
+
+Checks that git base implements its specification.
+
+'
+
+#
+#          G
+#         /
+# base - A - M - C - D - master - E
+#     \     / \
+#        B     F
+#
+
+. ./test-lib.sh
+
+GIT_AUTHOR_NAME=author@name
+GIT_AUTHOR_EMAIL=bogus@email@address
+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
+
+ensure_initial_state()
+{
+    git reset --hard HEAD &&
+    git checkout master &&
+    git reset --hard D && {
+        git update-ref -d refs/bases/master || true
+    } && {
+        git config --unset branch.master.baseresetcmd || true
+    } &&
+    ! git base clear
+}
+
+test_expect_success 'setup' \
+'
+    test_commit base &&
+    test_commit A &&
+    git checkout A^1 &&
+    test_commit B &&
+    git checkout master &&
+    test_merge M B &&
+    test_commit C &&
+    test_commit D &&
+    test_commit E &&
+    git checkout M^0 -- &&
+    test_commit F &&
+    git checkout A^0 -- &&
+    test_commit G &&
+    git checkout master &&
+    git reset --hard D
+'
+
+test_expect_success 'empty check when base missing' \
+'
+	ensure_initial_state &&
+	! git base clear &&
+	! git base check &&
+	! git rev-parse --verify refs/bases/master
+'
+
+test_expect_success 'empty check when base hidden (A)' \
+'
+	ensure_initial_state &&
+	! git base set -f A &&
+	! git base check &&
+	test "$(git base check)" = "" &&
+	git rev-parse --verify refs/bases/master &&
+	test "$(git rev-parse refs/bases/master)" = "$(git rev-parse A)"
+'
+
+test_expect_success 'empty check when base unreachable (E)' \
+'
+	ensure_initial_state &&
+	! git base set -f E &&
+	! git base check &&
+	test "$(git base check)" = "" &&
+	git rev-parse --verify refs/bases/master &&
+	test "$(git rev-parse refs/bases/master)" = "$(git rev-parse E)"
+'
+
+test_expect_success 'empty check when base unreachable and hidden (G)' \
+'
+	ensure_initial_state &&
+	! git base set -f G &&
+	! git base check &&
+	test "$(git base check)" = "" &&
+	git rev-parse --verify refs/bases/master &&
+	test "$(git rev-parse refs/bases/master)" = "$(git rev-parse G)"
+'
+
+test_expect_success 'empty check when base reachable and visible (M)' \
+'
+	ensure_initial_state &&
+	git base set -f M &&
+	git base check &&
+	test "$(git base check)" = "$(git rev-parse M)" &&
+	git rev-parse --verify refs/bases/master &&
+	test "$(git rev-parse refs/bases/master)" = "$(git rev-parse M)"
+'
+
+test_expect_success 'test default commit when clear' \
+'
+	ensure_initial_state &&
+        ! git base clear &&
+	git base M &&
+	test "$(git base check)" = "$(git rev-parse M)"
+'
+
+test_expect_success 'test default commit when set' \
+'
+	ensure_initial_state &&
+        git base set C &&
+	git base M &&
+	test "$(git base check)" = "$(git rev-parse C)"
+'
+
+test_expect_success 'git base - base not set initially' \
+'
+    ensure_initial_state &&
+    ! git base clear &&
+    ! git rev-parse --verify refs/bases/master &&
+    ! git base &&
+    ! git rev-parse --verify refs/bases/master &&
+    test -z "$(git base)"
+'
+test_expect_success 'test default when reference stale' \
+'
+    ensure_initial_state &&
+    ! git base set -f base &&
+    ! git base &&
+    ! git rev-parse --verify refs/bases/master &&
+    test -z "$(git base)"
+'
+
+test_expect_success 'test --as-ref does not create or check reference even when empty or stale' \
+'
+    ! git base clear &&
+    test "$(git base --as-ref)" = "refs/bases/master" &&
+    ! git base check &&
+    ! git base set -f base &&
+    ! git base --as-ref &&
+    test "$(git rev-parse refs/bases/master)" = "$(git rev-parse base)"
+'
+
+test_expect_success 'test set to stale reference ' \
+'
+    ! git base clear &&
+    git base set A &&
+    test "$(git base)" = $(git rev-parse M) &&
+    git base set B &&
+    test $(git base) = $(git rev-parse M) &&
+    git base set F &&
+    test $(git base) = $(git rev-parse M) &&
+    git base set G &&
+    test $(git base) = $(git rev-parse M) &&
+    git base set E &&
+    test $(git base) = $(git rev-parse D)
+'
+
+test_expect_success 'test set to good reference ' \
+'
+    ! git base clear &&
+    git base set C &&
+    test $(git base) = $(git rev-parse C) &&
+    git base set D &&
+    test $(git base) = $(git rev-parse D)
+'
+
+test_expect_success 'exit codes: ! clear && default' \
+'
+    ! git base clear &&
+    ! git base
+'
+
+test_expect_success 'exit codes: ! clear && !check && ! default' \
+'
+    ! git base clear &&
+    ! git base check &&
+    ! git base
+'
+
+test_expect_success 'exit codes: ! clear && ! set && ! default' \
+'
+    ! git base clear &&
+    ! git base set &&
+    ! git base
+'
+
+test_expect_success 'test detached head' \
+'
+    git checkout master^0 &&
+    test "$(git base --as-ref)" = "BASE" &&
+    git base set base &&
+    git base check &&
+    git base set M &&
+    test "$(git base)" = "$(git rev-parse M)"
+'
+
+test_expect_success 'test -b master' \
+'
+    ! git base -b master clear &&
+    git checkout master^0 &&
+    test "$(git base -b master --as-ref)" = "refs/bases/master" &&
+    git base -b master set base &&
+    git base -b master check &&
+    git base -b master set M &&
+    test "$(git base -b master)" = "$(git rev-parse M)"
+'
+
+test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH 23/23] Introduce support for the git-work command.
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (21 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 22/23] Introduce git base Jon Seymour
@ 2011-04-23  7:22 ` Jon Seymour
  2011-04-23  9:13 ` [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Peter Baumann
  23 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-23  7:22 UTC (permalink / raw)
  To: git; +Cc: Jon Seymour

The git work command uses the implicit base defined by git base
to help perform dependency management on a working branch.

See the Documentation for a description of the philosophy
underlying git-work.

Signed-off-by: Jon Seymour <jon.seymour@gmail.com>
---
 .gitignore                 |    1 +
 Documentation/git-work.txt |  163 ++++++++++++++++++++++
 Makefile                   |    1 +
 git-work.sh                |  323 ++++++++++++++++++++++++++++++++++++++++++++
 t/t3421-work.sh            |  174 ++++++++++++++++++++++++
 5 files changed, 662 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/git-work.txt
 create mode 100644 git-work.sh
 create mode 100755 t/t3421-work.sh

diff --git a/.gitignore b/.gitignore
index 54fa567..5fb3119 100644
--- a/.gitignore
+++ b/.gitignore
@@ -159,6 +159,7 @@
 /git-verify-tag
 /git-web--browse
 /git-whatchanged
+/git-work
 /git-write-tree
 /git-core-*/?*
 /gitk-git/gitk-wish
diff --git a/Documentation/git-work.txt b/Documentation/git-work.txt
new file mode 100644
index 0000000..1cae786
--- /dev/null
+++ b/Documentation/git-work.txt
@@ -0,0 +1,163 @@
+git-work(1)
+===========
+
+NAME
+----
+git-work - manage the current branch as a working branch
+
+SYNOPSIS
+--------
+[verse]
+'git work'
+'git work' 'list'   ['commit'|'dependency']
+'git work' 'merge'  [merge-options] [dependency ...]
+'git work' 'remerge' [--keep-tree]
+'git work' 'rebase' ['-i'|[dependency [rebase-options]]]
+'git work' 'pivot'  [pivot]
+'git work' 'create' ['--pivot-first'] topic [pivot [on-base]]
+'git work' 'update' ['--pivot-first'] topic [pivot]
+
+DESCRIPTION
+-----------
+In the discussion that follows, the current branch is referred to by the symbol \{branch\} and the commit at the base of \{branch\} is referred to by the symbol \{base\}. The symbol \{head\} is used to refer to the commit at the tip of \{branch\}.
+
+COMMANDS
+--------
+Without arguments, reports the base and head of the current branch as a range suitable for use with git rev-list. Use --as-refs to print the range with symbolic references.
+
+'list'::
+	Lists the commits in the working branch or the dependencies of the working branch.
+
+'merge'::
+	Merge the specified dependencies with \{base\}, then rebase \{base\}..\{head\} onto that merge. The head reference is updated with the result of the merge. The base reference is updated with dependency. If not specified, dependency defaults to the tracked branch, if any.
+
+'remerge'::
+	Rebuild the base of the branch using the current dependencies. Unless --keep-tree is specified, perform a piece-wise merge of each dependency. If --keep-tree is specified, then perform an information only merge of the current dependencies and preserve the tree of existing base.
+
+'rebase'::
+	Rebase \{base\}..\{head\} onto dependency and the base reference is updated to refer to dependency. If the -i option is specified, invokes git rebase -i \{base\}..\{branch\}. If not specified, dependency defaults to the tracked branch, if any.
+
+'pivot'::
+	Rebase pivot..\{head\} onto \{base\} and then rebase \{base\}..pivot onto the result of the first rebase. The head reference of the branch is updated to refer to the result of the final rebase. The pivot argument must satisfy the base invariant of \{branch\}.
+
+'create'::
+
+	Rebase pivot..\{branch\} onto on-base and initialize the topic head reference to the resulting commit; the topic base reference to on-base and the head reference of \{branch\} to pivot. Then merge the topic head into the base of \{branch\} using git work merge. If specified, pivot must satisfy the base invariant. If not specified, on-base and pivot default to \{base\}.
+
+'update'::
+	Performs the same operation as create but uses the head of an existing topic as on-base.
+
+Unless otherwise specified, if this command completes successfully the base reference will be updated to reflect the current calculated base for \{branch\}.
+
+OPTIONS
+-------
+--as-refs::
+	For the default subcommand, report the SHA1 hashes as symbolic reference names, not as SHA1 hashes.
+--pivot-first::
+	If this option is specified on a create or update subcommand, a pivot operation is logically performed first, in effect allowing
+        the commits below, rather than above, the pivot point to be moved from the current topic to the specified topic.
+
+ERROR HANDLING
+--------------
+Any rebases and merges performed by git work must succeed otherwise the working tree, index and references touched by git base are rolled back to their initial state, which must be clean to begin with.
+
+DISCUSSION
+----------
+git work is designed to support workflows where a developer's workspace perpetually contains a mixture of work items at different levels of maturity. Examples of such work items might be:
+
+* the upstream integration branch
+* published topics that are yet to be integrated into the integraton branch
+* completed, but unpublished topics
+* published topics received from other developers that have not yet been integrated in the integration branch
+* adhoc patches
+
+One way to manage such complexity is to maintain separate topic branches for each work item and then create temporary or throw-away branches to test the combined work. Such a practice has the advantage of keeping the artifacts of each work stream separate but can potentially incur significant process overheads and can be confusing for a developer to manage since at any given point in time a given work item that was recently worked on may, or may not be, integrated into the current work tree.
+
+From an individual developer's point of view, it can be more productive to work on a single working branch which accretes recent work and dependencies over time and only use isolated topic branches for the purposes of sharing stabilised work with others.
+
+The key to making this work is to ensure that dependencies are always merged into the base of the working branch, new work is done on the head of the branch, and mature work is rebased from the head of the working branch to heads of stable topic branches and then reintegrated back into the base of the working branch.
+
+With such a practice, the developer gains the convenience of a single working branch without sacrificing the cleanliness of topic-based development.
+
+git work is a porcelain that enables such work practices by providing commands that understand the concept of a branch base, how it relates to the branch head and the importance of merging dependencies into the branch base, rather than into the branch head. In particular, git work knows how to discover, establish and maintain the base of a branch as the maturity of work items merged into the branch evolves over time.
+
+In summary, git work allows a developer to keep his working tree stable while keeping his commit history sane.
+
+EXAMPLES
+--------
+* print the range of commits in the current working branch
++
+---------
+$ git work
+---------
+* lists the commits in the current working branch
++
+---------
+$ git work list
+---------
+* list the dependencies of the current working branch
++
+---------
+$ git work list dependency
+---------
+* start gitk, showing only the current work
++
+---------
+$ git work $(git work)
+---------
+* list the commits in the current working branch with a one line description
++
+---------
+$ git rev-list --oneline $(git work)
+---------
+* merge the upstream/master into the base of the current branch
++
+---------
+$ git work merge upstream/master
+---------
+* rebase the working branch onto the upstream/master
++
+---------
+$ git work rebase upstream/master
+---------
+* create a topic branch based on upstream/master from the top 2 commits of the current branch
++
+---------
+$ git work create mytopic HEAD~2 upstream/master
+---------
+* create a topic branch using the commits under the top 2 commits of the current branch
++
+---------
+$ git work --pivot-first create mytopic HEAD~2 upstream/master
+---------
+* update an existing topic branch with the top commit of the current branch
++
+---------
+$ git work update mytopic HEAD~1
+---------
+* clean up the base history by rewriting the base as an octopus merge with the same tree as the current base
++
+---------
+$ git work remerge --keep-tree
+---------
+* visualize the merge structure of the dependencies of the current work
++
+---------
+gitk $(git base) --not $(git work list dependency)
+---------
+
+SEE ALSO
+--------
+linkgit:git-base[1]
+
+Author
+------
+Written by Jon Seymour <jon.seymour@gmail.com>
+
+Documentation
+-------------
+Documentation by Jon Seymour
+
+GIT
+---
+Part of the linkgit:git[7] suite
diff --git a/Makefile b/Makefile
index 6aee4a8..42854cb 100644
--- a/Makefile
+++ b/Makefile
@@ -380,6 +380,7 @@ SCRIPT_SH += git-stash.sh
 SCRIPT_SH += git-submodule.sh
 SCRIPT_SH += git-test.sh
 SCRIPT_SH += git-web--browse.sh
+SCRIPT_SH += git-work.sh
 
 SCRIPT_LIB += git-atomic-lib
 SCRIPT_LIB += git-conditions-lib
diff --git a/git-work.sh b/git-work.sh
new file mode 100644
index 0000000..f6b378d
--- /dev/null
+++ b/git-work.sh
@@ -0,0 +1,323 @@
+#!/bin/sh
+USAGE='[help|list|merge|remerge|rebase|pivot|create|update]'
+LONG_USAGE='
+git work
+    Print the range that defines the boundaries of the branch
+git work list [commit|dependency]
+    List the commits or dependencies of the current working branch.
+git work merge [dependency ...]
+    Merge the specified dependencies into the base of the branch
+git work remerge [--keep-tree]
+    Rebuild the base of the current working branch from the dependencies
+git work pivot [pivot]
+    Swap two segments of the working branch at the commit specified by the pivot
+git work rebase [-i]
+    Invoke an interactive rebase on the branch
+git work rebase [dependency]
+    Rebase the current working branch onto the specified dependency.
+git work create [--pivot-first] topic [pivot] [on-base]
+    Rebase the top N commits onto new topic based on a specified commit, then merge the result into the base of the current branch
+git work update [--pivot-first] topic [pivot]
+    Rebase the top N commits onto an existing topic, then merge the updated topic into the base of the current branch
+git work help
+    Print this help
+
+Please use "git help work" to get the full man page.'
+
+SUBDIRECTORY_OK=true
+OPTIONS_SPEC="
+$LONG_USAGE
+--
+pivot-first perform a pivot first, then do..
+as-refs print the range as references
+X pass-thru options to merge or rebase
+i,interactive perform an interactive rebase
+"
+. git-sh-setup
+. git-atomic-lib
+
+warn()
+{
+    echo "warn: $*" 1>&2
+}
+
+not_implemented()
+{
+    die "not yet implemented"
+}
+
+work_default()
+{
+   if $AS_REFS
+   then
+       echo $BASEREF..$BRANCH
+   else
+       echo $(git base)..$(git rev-parse HEAD)
+   fi
+}
+
+work_list_dependency()
+{
+    limits=''
+    while true
+    do
+         top=$(git rev-list $BASE $limits --no-merges --max-count=1)
+	 if test -z "$top"
+         then
+             break;
+         else
+	     echo $top
+             limits="$limits ^$top"
+         fi
+    done
+}
+
+work_list()
+{
+    type=$1
+    case "$type" in
+       ""|commit)
+	    git rev-list $(work_default)
+       ;;
+       dependency)
+            work_list_dependency "$@"
+       ;;
+       *)
+	    die "$type is not a supported type for this command"
+       ;;
+    esac
+}
+
+work_merge()
+{
+   DEPENDENCY=$1
+   shift
+
+   test -n "${DEPENDENCY}" || die "usage: git work merge [dependency [merge-options]]"
+
+   assert --not-staged --not-unstaged --commit-exists "${DEPENDENCY}"
+
+   atomic eval \
+"
+	DEPENDENCY=${DEPENDENCY} &&
+	BASE=${BASE} &&
+	BASEREF=${BASEREF} &&
+	BRANCH=${BRANCH} &&
+	MERGE_OPTIONS='${MERGE_OPTIONS}'
+	git checkout -q \${BASE} &&
+	git merge -q \${DEPENDENCY} \${MERGE_OPTIONS} &&
+	MERGE=\$(git rev-parse HEAD) &&
+	git rebase --onto HEAD \${BASE} \${BRANCH} &&
+	git update-ref \${BASEREF} \${MERGE}
+"
+}
+
+work_remerge()
+{
+   assert --not-staged --not-unstaged
+   not_yet_implemented
+}
+
+work_rebase()
+{
+   git base -q check || die "use 'git base' to establish a base for this branch"
+   if test -n "${INTERACTIVE}"
+   then
+        PIVOT=${1:-$(git base)}
+	git base -q check ${PIVOT} || die "${PIVOT} is not a valid pivot commit"
+	git rebase -i ${PIVOT} HEAD "$@"
+   else
+
+      DEPENDENCY=$1
+
+      assert --not-staged --not-unstaged --commit-exists "${DEPENDENCY}"
+      atomic eval "
+      git rebase --onto \${DEPENDENCY} \${BASE} \${BRANCH} &&
+      git update-ref \${BASEREF} \${DEPENDENCY}
+"
+   fi
+}
+
+work_create()
+{
+   TOPIC=$1
+   PIVOT=$2
+   DEPENDENCY=$3
+
+   test -n "${DEPENDENCY}" || die "usage: git work create topic [pivot [dependency]]"
+
+   PIVOT=$(git rev-parse --verify "$PIVOT" 2>/dev/null) || die "$2 is not a commit"
+
+   git base check "${PIVOT}" > /dev/null || die "$PIVOT does not lie between $BASE and $HEAD"
+
+   assert --commit-exists "${DEPENDENCY}" --not-branch-exists "${TOPIC}" --not-staged --not-unstaged
+
+   if $PIVOT_FIRST
+   then
+   atomic eval "
+	BASE=${BASE} &&
+	TOPIC=${TOPIC} &&
+	PIVOT=${PIVOT} &&
+	HEAD=${HEAD} &&
+	BRANCHREF=${BRANCHREF} &&
+	DEPENDENCY=${DEPENDENCY} &&
+	git rebase -q --onto \${DEPENDENCY} \${BASE} \${PIVOT} &&
+	git branch \${TOPIC} &&
+	git base -q -b \${TOPIC} set \${DEPENDENCY} &&
+	git rebase -q --onto \${BASE} \${PIVOT} \${BRANCH} &&
+	git work merge \${TOPIC}
+"
+   else
+   atomic eval "
+	TOPIC=${TOPIC} &&
+	PIVOT=${PIVOT} &&
+	HEAD=${HEAD} &&
+	BRANCHREF=${BRANCHREF} &&
+	DEPENDENCY=${DEPENDENCY} &&
+	git rebase -q --onto \${DEPENDENCY} \${PIVOT} \${HEAD} &&
+	git branch \${TOPIC} &&
+	git base -b \${TOPIC} set \${DEPENDENCY} &&
+	git update-ref \${BRANCHREF} \${PIVOT} &&
+	git checkout \${BRANCH} &&
+	git work merge \${TOPIC}
+"
+   fi
+
+}
+
+work_update()
+{
+   TOPIC=$1
+   PIVOT=$2
+
+   test -n "${PIVOT}" || die "usage: git work update topic [pivot [dependency]]"
+
+   PIVOT=$(git rev-parse --verify "$PIVOT" 2>/dev/null) || die "$2 is not a commit"
+
+   git base check "${PIVOT}" > /dev/null || die "$PIVOT does not lie between $BASE and $HEAD"
+
+   assert --branch-exists "${TOPIC}" --not-staged --not-unstaged
+
+   if $PIVOT_FIRST
+   then
+   atomic eval "
+	TOPIC=${TOPIC} &&
+	PIVOT=${PIVOT} &&
+	BASE=${BASE} &&
+	HEAD=${HEAD} &&
+	BRANCHREF=${BRANCHREF} &&
+	git base -q -b \${TOPIC} \${TOPIC} &&
+	git rebase -q --onto \${TOPIC} \${BASE} \${PIVOT} &&
+	git branch -f \${TOPIC} &&
+	git rebase -q --onto \${BASE} \${PIVOT} \${BRANCH} &&
+	git work merge \${TOPIC}
+"
+   else
+   atomic eval "
+	TOPIC=${TOPIC} &&
+	PIVOT=${PIVOT} &&
+	HEAD=${HEAD} &&
+	BRANCHREF=${BRANCHREF} &&
+	git base -q -b \${TOPIC} \${TOPIC} &&
+	git rebase -q --onto \${TOPIC} \${PIVOT} \${HEAD} &&
+	git branch -f \${TOPIC} &&
+	git update-ref \${BRANCHREF} \${PIVOT} &&
+	git checkout \${BRANCH} &&
+	git work merge \${TOPIC}
+"
+   fi
+
+}
+
+work_pivot()
+{
+   PIVOT=$1
+
+   test -n "${PIVOT}" || die "usage: git work update topic [pivot [dependency]]"
+
+   PIVOT=$(git rev-parse --verify "$PIVOT" 2>/dev/null) || die "$1 is not a commit"
+
+   git base check "${PIVOT}" > /dev/null || die "$PIVOT does not lie between $BASE and $HEAD"
+
+   assert --not-staged --not-unstaged
+
+   atomic eval "
+      git rebase -q --onto \${BASE} \${PIVOT} \${HEAD} &&
+      git rebase -q --onto HEAD \${BASE} \${PIVOT} &&
+      git update-ref \${BRANCHREF} HEAD &&
+      git checkout -q \${BRANCH}
+"
+}
+
+INTERACTIVE=
+CURRENT_BRANCH=$(git branch | grep "^\* [^(]" | cut -c3-)
+AS_REFS=false
+PIVOT_FIRST=false
+if test -n "$CURRENT_BRANCH"
+then
+   BRANCHREF=refs/heads/${CURRENT_BRANCH}
+   BASEREF=refs/bases/${CURRENT_BRANCH}
+   BRANCH=${CURRENT_BRANCH}
+else
+   BRANCHREF=HEAD
+   BASEREF=BASE
+   BRANCH=HEAD
+fi
+
+while test $# != 0
+do
+	case $1 in
+		-b)
+		shift
+		BRANCHREF=refs/heads/$1
+		BASEREF=refs/bases/$1
+		shift
+		(git rev-parse --verify $BRANCHREF 1>/dev/null) || die "$1 is not a valid local branch"
+		;;
+		-i)
+			INTERACTIVE=-i
+			shift
+		;;
+		--as-refs)
+		shift
+		AS_REFS=true
+		;;
+		--pivot-first)
+		shift
+		PIVOT_FIRST=true
+		;;
+		--)
+		shift
+		break;
+		;;
+		*)
+		break;
+		;;
+	esac
+done
+
+
+# ensure that a base is established.
+git base -q || die "Please use 'git base init' to initialise the reset command for this branch."
+
+HEAD=$(git rev-parse --verify ${BRANCHREF} 2>/dev/null)
+BASE=$(git rev-parse --verify ${BASEREF} 2>/dev/null)
+
+test -n "$BASE" || die "can't derive BASE from BASEREF=${BASEREF}"
+
+case "$#" in
+0)
+    work_default "$@" ;;
+*)
+    cmd="$1"
+    shift
+    if test $cmd = 'trace'; then set -x; cmd=$1; shift; fi
+    case "$cmd" in
+    help)
+	    git work -h "$@" ;;
+    list|merge|remerge|rebase|pivot|create|update)
+	    work_$cmd "$@" ;;
+    *)
+	    usage "$@" ;;
+    esac
+esac
diff --git a/t/t3421-work.sh b/t/t3421-work.sh
new file mode 100755
index 0000000..9c6ed35
--- /dev/null
+++ b/t/t3421-work.sh
@@ -0,0 +1,174 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Jon Seymour
+#
+
+test_description='git work tests
+
+Performs tests on the functions of git work
+'
+#
+#       H-I
+#      /
+# A-B-C---M-P-Q
+#  \     /
+#   X-Y-Z
+#
+
+. ./test-lib.sh
+. $(git --exec-path)/git-test-lib
+
+GIT_AUTHOR_NAME=author@name
+GIT_AUTHOR_EMAIL=bogus@email@address
+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
+
+reset_test()
+{
+    git reset --hard HEAD &&
+    git checkout master &&
+    git base set M && {
+       test_condition -q --not-branch-exists test ||
+       git branch -D test
+    } &&
+    test_condition -q --not-branch-exists topic || {
+        git base init -d -b topic &&
+	git branch -D topic
+    }
+}
+
+test_expect_success 'setup' \
+    '
+    test_commit A &&
+    test_commit B &&
+    test_commit C &&
+    git checkout -b fork-X A &&
+    test_commit X &&
+    test_commit Y &&
+    test_commit Z &&
+    git checkout master &&
+    test_merge M Z &&
+    git base -q set HEAD &&
+    test_commit P &&
+    test_commit Q &&
+    git checkout -b fork-H C &&
+    test_commit H &&
+    test_commit I &&
+    git checkout master &&
+    true
+'
+
+test_expect_success 'work --as-refs' \
+'
+   test "$(git work --as-refs)" = "refs/bases/master..master"
+'
+
+test_expect_success 'work' \
+'
+   test "$(git work)" = "$(git rev-parse M)..$(git rev-parse Q)"
+'
+
+test_expect_success 'work list' \
+'
+   test "$(git rev-parse Q; git rev-parse P)" = "$(git work list)"
+'
+
+test_expect_success 'git work rebase Z <=> rebase --onto Z M..Q' \
+'
+    reset_test &&
+    git checkout -b test Q &&
+    git base -q set M &&
+    git work rebase Z &&
+    test_condition --same $(git base) Z &&
+    test_condition --checked-out test &&
+    test "$(git diff M Q | git patch-id)" = "$(git diff $(git work) | git patch-id)"
+'
+
+test_expect_success 'git work merge I <=> rebase --onto I M..Q' \
+'
+    reset_test &&
+    git checkout -b test Q &&
+    git base -q set M &&
+    git work merge I &&
+    test_condition --not-same I $(git base) &&
+    test_condition --checked-out test &&
+    test "$(git diff M Q | git patch-id)" = "$(git diff $(git work) | git patch-id)"
+'
+
+test_expect_success 'git work create topic I from P..Q' \
+'
+    reset_test &&
+    git checkout -b test Q &&
+    git base -q set M &&
+    git work create topic P I &&
+    test_condition --same $(git base -b topic) I &&
+    test_condition --checked-out test &&
+    test "$(git diff I topic | git patch-id)" = "$(git diff P Q | git patch-id)" &&
+    test "$(git diff $(git base) test | git patch-id)" = "$(git diff M P | git patch-id)"
+'
+
+test_expect_success 'git work create topic --pivot-first I from P..Q' \
+'
+    reset_test &&
+    git checkout -b test Q &&
+    git base -q set M &&
+    git work create --pivot-first topic P I &&
+    test_condition  --same $(git base -b topic) I &&
+    test_condition  --checked-out test &&
+    test "$(git diff I topic | git patch-id)" = "$(git diff M P | git patch-id)" &&
+    test "$(git diff $(git base)  test | git patch-id)" = "$(git diff P Q | git patch-id)"
+'
+
+test_expect_success 'git work update topic I from P..Q' \
+'
+    reset_test &&
+    git checkout -b test Q &&
+    git base -q set M &&
+    git branch topic I &&
+    git work update topic P &&
+    test_condition  --same "$(git base -b topic)" I &&
+    test_condition  --checked-out test &&
+    test "$(git diff I topic | git patch-id)" = "$(git diff P Q | git patch-id)" &&
+    test "$(git diff $(git base)  test | git patch-id)" = "$(git diff M P | git patch-id)"
+'
+
+test_expect_success 'git work update --pivot-first topic I from P..Q' \
+'
+    reset_test &&
+    git checkout -b test Q &&
+    git base -q set M &&
+    git branch topic I &&
+    git work update --pivot-first topic P &&
+    test_condition  --same "$(git base -b topic)" I &&
+    test_condition  --checked-out test &&
+    test "$(git diff I topic | git patch-id)" = "$(git diff M P | git patch-id)" &&
+    test "$(git diff $(git base)  test | git patch-id)" = "$(git diff P Q | git patch-id)"
+'
+
+test_expect_success 'git work update topic I from P..Q - unitialized topic' \
+'
+    git base init -d topic &&
+    reset_test &&
+    git checkout -b test Q &&
+    git base -q set M &&
+    git branch topic I &&
+    git work update topic P &&
+    test_condition  --same "$(git base -b topic)" I &&
+    test_condition  --checked-out test &&
+    test "$(git diff I topic | git patch-id)" = "$(git diff P Q | git patch-id)" &&
+    test "$(git diff $(git base)  test | git patch-id)" = "$(git diff M P | git patch-id)"
+'
+
+test_expect_success 'git work pivot P' \
+'
+    reset_test &&
+    git checkout -b test Q &&
+    git base -q set M &&
+    git work pivot P &&
+    test_condition  --same "$(git base -b test)" M &&
+    test_condition  --checked-out test &&
+    test "$(git diff M test | git patch-id)" = "$(git diff M Q | git patch-id)" &&
+    test "$(git diff M test~1 | git patch-id)" = "$(git diff P Q | git patch-id)" &&
+    test "$(git diff test~1 test | git patch-id)" = "$(git diff M P | git patch-id)"
+'
+
+test_done
-- 
1.7.5.rc1.23.g7f622

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* Re: [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work
  2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
                   ` (22 preceding siblings ...)
  2011-04-23  7:22 ` [PATCH 23/23] Introduce support for the git-work command Jon Seymour
@ 2011-04-23  9:13 ` Peter Baumann
  2011-04-23 17:16   ` Jon Seymour
  23 siblings, 1 reply; 29+ messages in thread
From: Peter Baumann @ 2011-04-23  9:13 UTC (permalink / raw)
  To: Jon Seymour; +Cc: git

On Sat, Apr 23, 2011 at 05:22:29PM +1000, Jon Seymour wrote:
> This series is posted in order to solicit feedback about some commands I'd like to propose for inclusion in git. 
> 
> The commands are:
>    git test
>    git atomic
>    git base
>    git work
> 
> git work
> ========
> 
> git work depends on the other 3 commands. 
> 
> git work is the command I have been using every day myself for the last 8 months. It is the primary means
> I use to manage my working tree.
> 
> The basic idea of git work is to help manage a private working tree using the following principles:
> 
> * all dependencies received from others are merged into the 'base' of the working tree
> 
>   git work merge some-dependency
> 
>      - merges some-dependency into $(git base) producing (1)
>      - rebases $(git base)..HEAD onto (1) producing (2)
>      - resets the current branch to (2)
>      - updates $(git base) to refer to (1)
> 
> * all unpublished work is managed as a linear sequence of commits on top of the 'base' of the working tree
> 
>   so, 
>    git work --as-refs # shows you a symbolic range for your current work
>    git work # shows you a SHA1 range for your current work
>    gitk $(git work) # shows you your unpublished work
>    git diff $(git work) # shows you the diff of your current work
> 
> * prior to publishing work, you rebase it onto an appropriate base commit
> 
>   so,
> 
>   git branch topic upstream/master # choose the base commit for the topic
>   git work update topic HEAD~3     # pull the top 3 commits off the working tree onto the topic
> 
>     - rebases HEAD~3..HEAD onto topic to produce (1)
>     - merges topic into $(git base) to produce (2)
>     - rebases $(git base)..HEAD~3 onto (2) to produce (3)
>     - resets the current branch to (3)
>     - resets $(git base) to (2)
> 
> The nice thing about managing your work tree this way is that your working tree remains
> relatively stable (it always contains everything you have recently been working on)
> and your topic branches remain clean (i.e. they never contain any other cruft from your 
> working tree).

Reading this I have to admit I don't fully grok it, but it *sounds* like a solution
to my problematic workflow. Perhaps you can supply some examples, because reading 
the unit tests for git work hasn't enlightened me.

Ok, let me explain what I am aiming for:

I am stuck with git-svn (our customer is not yet ready to use git) and therefore have
a remote branch remotes/trunk tracking the trunk in SVN. Then there is at least one
debug branch which contains several commits only used to debug problems or to speed
up testing (e.g. avoid popping up error dialogs that some hardware is missing on one
of our test platforms, or to add more logging statements not suitable for the mainline).
Furthermore, there are several feature branches, lets call them topic{1, ...N}


               . -  q - q     <- topic3
              /
           . -s - s - s       <- topic2
          /
         /    t - t - t       <- topic1
        /   / 
       /   /
   o - o - o - o - o - o - o  <- remotes/trunk, master
                \
                  d  - d      <- debug 
           


If I want to deploy the software onto our target machine, I want to have
the code of several branches merged/combined to test them in integration.
If I suspect problems in one of the topics, I at least must have the 
topicX, debug and the upstream branch remotes/trunk.
 
So my workflow would have been - if it weren't clumsy - the following

  git checkout master
  git merge debug topic1 topic2 topic3
  ... compile ... deploy ... test

               . -  q - q -.       <- topic3
              /             \
           . -s - s - s ---. \     <- topic2
          /                 \|
         /    t - t - t - - -\     <- topic1
        /   /                 m    <- master
       /   /                / | 
   o - o - o - o - o - o - o  |    <- remotes/trunk
                \            /
                  d  - d - -       <- debug 
 
Having fond a small error, I can't commit it directly on the merge m, because
It is actually a fix a topic branch. Running git checkout topicX before doing
any change is also not an option, because I often forget to checkout the topic
before commiting. And rebasing afterwards is also not that easy, because there
is merge m in between.  So this isn't what I actually use, but would be *iff*
there where some sort of tool support to help me. 

What I end up doing is the following:

- o - o   <- remotes/trunk
       \
         d - d   <- debug
              \
                t - t -t  <- topic1
                        \
                          s - s -s  <- topic2
                                  \
                                   q - q   <- topic3, master
                                        
                                        
The topic branches are just there for illustration, I normally do not track
those explicitly with git branches, as master is rebase often on top of trunk,
thanks to git-svn. Now if I do want to commit a topic branch to SVN, I rebase
so that my history is now   trunk - topic1 - debug - topic2 - topic3, master
and then run git checkout topic1; git svn dcommit to make sure only the commits
in topic1 are commited.

All in all, this workflow is not perfect, but at least it sort of works.

Now back to my initial question: 
Could your new "git work" command help me to adjust my workflow and ease the pain?

Greetings,
Peter


> git base
> ========
> 
> git base is a command that is heavily relied on by git work, and is occasionally used by
> the user to reset the base of their working tree.
> 
> git base tries to automagically maintain the base of the working tree by maintaining
> an invariant that the path between the base and the tip of the current branch should
> never traverse a merge. If it ever finds the invariant violated, it calls 'git base reset'
> to attempt to restore the invariant. 
> 
> For more information about git base, refer to the Documentation/git-base.txt
> 
> git atomic
> ==========
> git atomic is more an experiment than anything else. The idea is to run another git operation "atomically".
> The git operation either succeeds or it fails. If it fails, git branch attempts to restore the
> working tree and current branch to the state they were in to their original state prior to the comamnd being run.
> 
> The reason I need something like this is that git work performs several operations in sequence some
> of which I can't guarantee will work. I don't want the user to work out what they have to do
> to recover, so I try to restore to the state they were originally in.
> 
> Note: the current implementation doesn't handle rebase failures properly and would probably needed
> to be cleaned up a little before being accepted into the mainline.
> 
> git test
> ========
> This is another experiment. The idea is to provide a uniform way to test for various conditions
> into the working tree, index and repo. For example:
> 
>     git test --not-unstaged --branch-exists foobar
> 
> will fail if there are unstaged files in the working tree or the branch foobar does not exist.
> 
> As I say, git atomic and git test are somewhat experimental. I don't really care about those commands
> and if the consensus is that git doesn't need them, I am happy to rework git base and git work
> to not use them.
> 
> However, I would like to propose git base and git work as being very useful additions to the git toolset.
> 
> Let me know if the consensus is that I should proceed with a submission and I will prepare one.
> 
> Jon Seymour (23):
>   Introduce git-test.sh and git-test-lib.sh
>   Introduce --unstaged.
>   Introduce --staged
>   Introduce --untracked.
>   Introduce --conflicted
>   Introduce --rebasing
>   Introduce --detached
>   Introduce --branch-exists
>   Introduce --tag-exists
>   Introduce --ref-exists
>   Introduce --commit-exists.
>   Introduce --checked-out
>   Introduce --reachable
>   Introduce --tree-same.
>   Introduce --same
>   misc
>   whitespace fix.
>   tests --conflicted.
>   rebasing: add tests
>   test: git test cleanups.
>   Introduce git-atomic.
>   Introduce git base.
>   Introduce support for the git-work command.
> 
>  .gitignore                   |    7 +
>  Documentation/config.txt     |   10 +
>  Documentation/git-atomic.txt |   92 ++++++++
>  Documentation/git-base.txt   |  216 ++++++++++++++++++
>  Documentation/git-test.txt   |  182 +++++++++++++++
>  Documentation/git-work.txt   |  163 ++++++++++++++
>  Makefile                     |    7 +
>  git-atomic-lib.sh            |   58 +++++
>  git-atomic.sh                |    5 +
>  git-base.sh                  |  378 +++++++++++++++++++++++++++++++
>  git-conditions-lib.sh        |  176 +++++++++++++++
>  git-test-lib.sh              |  188 ++++++++++++++++
>  git-test.sh                  |   11 +
>  git-work.sh                  |  323 +++++++++++++++++++++++++++
>  t/t1520-test.sh              |  506 ++++++++++++++++++++++++++++++++++++++++++
>  t/t3418-base.sh              |  214 ++++++++++++++++++
>  t/t3419-atomic.sh            |   59 +++++
>  t/t3421-work.sh              |  174 +++++++++++++++
>  18 files changed, 2769 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/git-atomic.txt
>  create mode 100644 Documentation/git-base.txt
>  create mode 100644 Documentation/git-test.txt
>  create mode 100644 Documentation/git-work.txt
>  create mode 100644 git-atomic-lib.sh
>  create mode 100755 git-atomic.sh
>  create mode 100644 git-base.sh
>  create mode 100644 git-conditions-lib.sh
>  create mode 100644 git-test-lib.sh
>  create mode 100755 git-test.sh
>  create mode 100644 git-work.sh
>  create mode 100755 t/t1520-test.sh
>  create mode 100755 t/t3418-base.sh
>  create mode 100755 t/t3419-atomic.sh
>  create mode 100755 t/t3421-work.sh
> 
> -- 
> 1.7.5.rc1.23.g7f622
> 
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work
  2011-04-23  9:13 ` [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Peter Baumann
@ 2011-04-23 17:16   ` Jon Seymour
  2011-04-23 18:37     ` Jon Seymour
  0 siblings, 1 reply; 29+ messages in thread
From: Jon Seymour @ 2011-04-23 17:16 UTC (permalink / raw)
  To: Peter Baumann; +Cc: git

On Saturday, April 23, 2011, Peter Baumann <waste.manager@gmx.de> wrote:
> On Sat, Apr 23, 2011 at 05:22:29PM +1000, Jon Seymour
>   git checkout master
>   git merge debug topic1 topic2 topic3
>   ... compile ... deploy ... test
>
>                . -  q - q -.       <- topic3
>               /             \
>            . -s - s - s ---. \     <- topic2
>           /                 \|
>          /    t - t - t - - -\     <- topic1
>         /   /                 m    <- master
>        /   /                / |
>    o - o - o - o - o - o - o  |    <- remotes/trunk
>                 \            /
>                   d  - d - -       <- debug
>
> Having fond a small error, I can't commit it directly on the merge m, because
> It is actually a fix a topic branch. Running git checkout topicX before doing
> any change is also not an option, because I often forget to checkout the topic
> before commiting. And rebasing afterwards is also not that easy, because there
> is merge m in between.  So this isn't what I actually use, but would be *iff*
> there where some sort of tool support to help me.
>
> What I end up doing is the following:
>
> - o - o   <- remotes/trunk
>        \
>          d - d   <- debug
>               \
>                 t - t -t  <- topic1
>                         \
>                           s - s -s  <- topic2
>                                   \
>                                    q - q   <- topic3, master
>
>
> The topic branches are just there for illustration, I normally do not track
> those explicitly with git branches, as master is rebase often on top of trunk,
> thanks to git-svn. Now if I do want to commit a topic branch to SVN, I rebase
> so that my history is now   trunk - topic1 - debug - topic2 - topic3, master
> and then run git checkout topic1; git svn dcommit to make sure only the commits
> in topic1 are commited.
>
> All in all, this workflow is not perfect, but at least it sort of works.
>
> Now back to my initial question:
> Could your new "git work" command help me to adjust my workflow and ease the pain?
>
> > Peter
>

Hi Peter,

Yes, git work is good for this use case.

You would tend to manage you master branch as in your first diagram above.

Suppose you found a problem on topicX. So, you fix the issue inplace
on the top of your master that includes all 5 dependencies. Call this
commit m'.

You have decided that the fix really belongs on topic2. So what you do is:

git work update topic2 HEAD~1

This will move that commit onto the tip of topic2 (producing topic2'),
merge that into m (producing m''), and reset master to m''.

One thing to note is that you never share master. It will be a merge
hell. You only ever share topics when they become stable.

As the trunk gets updated you do:

git work merge remotes/trunk

It will merge remotes/trunk into the base of your unpublished work,
then rebase your unpublished work onto that merge.

git work is also excellent for keeping your debug branch available in
your working tree but isolated from your topics.

Things get little tricker when you have a commit that spans multiple
topics. However, there is a straight forward methodology for managing
such issues. There are also techniques for dealing with conflicts with
any dependency, especially remotes/trunk which I can explain in
another note if there is interest.

Jon.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work
  2011-04-23 17:16   ` Jon Seymour
@ 2011-04-23 18:37     ` Jon Seymour
  2011-04-24 15:47       ` Jon Seymour
  0 siblings, 1 reply; 29+ messages in thread
From: Jon Seymour @ 2011-04-23 18:37 UTC (permalink / raw)
  To: Peter Baumann; +Cc: git

BTW anyone interested in trying git work can fetch the tag containing
this series from my github fork

Web link here:

https://github.com/jonseymour/git/tree/work-20110423

git://github.com/jonseymour/git, tag  work-20110423

jon.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work
  2011-04-23 18:37     ` Jon Seymour
@ 2011-04-24 15:47       ` Jon Seymour
  0 siblings, 0 replies; 29+ messages in thread
From: Jon Seymour @ 2011-04-24 15:47 UTC (permalink / raw)
  To: Peter Baumann; +Cc: git

Also, anyone interested in browsing the HTML documentation for these
commands can read:

   https://s3.amazonaws.com/jonseymour/git-work.html
   https://s3.amazonaws.com/jonseymour/git-base.html
   https://s3.amazonaws.com/jonseymour/git-atomic.html
   https://s3.amazonaws.com/jonseymour/git-test.html

jon.

On Sun, Apr 24, 2011 at 4:37 AM, Jon Seymour <jon.seymour@gmail.com> wrote:
> BTW anyone interested in trying git work can fetch the tag containing
> this series from my github fork
>
> Web link here:
>
> https://github.com/jonseymour/git/tree/work-20110423
>
> git://github.com/jonseymour/git, tag  work-20110423
>
> jon.
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 01/23] Introduce git-test.sh and git-test-lib.sh
  2011-04-23  7:22 ` [PATCH 01/23] Introduce git-test.sh and git-test-lib.sh Jon Seymour
@ 2011-04-27 19:11   ` Drew Northup
  0 siblings, 0 replies; 29+ messages in thread
From: Drew Northup @ 2011-04-27 19:11 UTC (permalink / raw)
  To: Jon Seymour; +Cc: git

On Sat, 2011-04-23 at 17:22 +1000, Jon Seymour wrote:
> This command is intended provide a uniform command line interface
> to a suite of assertions that can be made about the state of
> the working tree, index and repository.
> 
> This commit introduces the core assert infrastructure. Subsequent
> commits will introduce check functions that extend the infrastructure
> in a modular way with additional tests.
.....
> +'test_condition' [--<condition> [ arg ... ]] ...
> +'require_condition_libs'
> +
> +
> +DESCRIPTION
> +-----------
> +`git test` provides a uniform, extensible API for evaluating various conditions that
> +pertain to the state of a git working tree, index and repository.
> +
> +Specified conditions are evaluated from left to right. If any condition evaluates to false, 
> +the command conditionally prints a diagnostic message to stderr and sets a 
> +non-zero status code. Otherwise, sets a status code of zero. 
> +
> +The message used to report a assertion failure may be overidden by specifying the --message option.
> +
> +Diagnostic output resulting from an assertion failure may be suppressed with the -q option. 
> +Note that the -q option does not suppress diagnostic output that results from the failure to 
> +successfully parse the arguments that configure the test API.
.....
Is this supposed to be a porcelain or plumbing? 

The name could probably be better to avoid confusion with the "unit
testing" code in t/.

Additionally, I'd like to know why this shouldn't be implemented as some
sort of 
'git status --assert="XXXX"' 
call...

-- 
-Drew Northup
________________________________________________
"As opposed to vegetable or mineral error?"
-John Pescatore, SANS NewsBites Vol. 12 Num. 59

^ permalink raw reply	[flat|nested] 29+ messages in thread

end of thread, other threads:[~2011-04-27 19:12 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-04-23  7:22 [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Jon Seymour
2011-04-23  7:22 ` [PATCH 01/23] Introduce git-test.sh and git-test-lib.sh Jon Seymour
2011-04-27 19:11   ` Drew Northup
2011-04-23  7:22 ` [PATCH 02/23] Introduce --unstaged Jon Seymour
2011-04-23  7:22 ` [PATCH 03/23] Introduce --staged Jon Seymour
2011-04-23  7:22 ` [PATCH 04/23] Introduce --untracked Jon Seymour
2011-04-23  7:22 ` [PATCH 05/23] Introduce --conflicted Jon Seymour
2011-04-23  7:22 ` [PATCH 06/23] Introduce --rebasing Jon Seymour
2011-04-23  7:22 ` [PATCH 07/23] Introduce --detached Jon Seymour
2011-04-23  7:22 ` [PATCH 08/23] Introduce --branch-exists Jon Seymour
2011-04-23  7:22 ` [PATCH 09/23] Introduce --tag-exists Jon Seymour
2011-04-23  7:22 ` [PATCH 10/23] Introduce --ref-exists Jon Seymour
2011-04-23  7:22 ` [PATCH 11/23] Introduce --commit-exists Jon Seymour
2011-04-23  7:22 ` [PATCH 12/23] Introduce --checked-out Jon Seymour
2011-04-23  7:22 ` [PATCH 13/23] Introduce --reachable Jon Seymour
2011-04-23  7:22 ` [PATCH 14/23] Introduce --tree-same Jon Seymour
2011-04-23  7:22 ` [PATCH 15/23] Introduce --same Jon Seymour
2011-04-23  7:22 ` [PATCH 16/23] misc Jon Seymour
2011-04-23  7:22 ` [PATCH 17/23] whitespace fix Jon Seymour
2011-04-23  7:22 ` [PATCH 18/23] tests --conflicted Jon Seymour
2011-04-23  7:22 ` [PATCH 19/23] rebasing: add tests Jon Seymour
2011-04-23  7:22 ` [PATCH 20/23] test: git test cleanups Jon Seymour
2011-04-23  7:22 ` [PATCH 21/23] Introduce git-atomic Jon Seymour
2011-04-23  7:22 ` [PATCH 22/23] Introduce git base Jon Seymour
2011-04-23  7:22 ` [PATCH 23/23] Introduce support for the git-work command Jon Seymour
2011-04-23  9:13 ` [PATCH 00/23] RFC: Introducing git-test, git-atomic, git-base and git-work Peter Baumann
2011-04-23 17:16   ` Jon Seymour
2011-04-23 18:37     ` Jon Seymour
2011-04-24 15:47       ` Jon Seymour

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.