Git development
 help / color / mirror / Atom feed
* Re: Official Git Homepage change? Re: git-scm.com
From: Scott Chacon @ 2008-07-26  6:43 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Petr Baudis, git
In-Reply-To: <7v4p6dnv5k.fsf@gitster.siamese.dyndns.org>

On Fri, Jul 25, 2008 at 9:09 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Petr Baudis <pasky@suse.cz> writes:
>
>> .... Of course, I would be transferring the control of the homepage
>> from my hands so I would like to poll the community about how do people
>> feel about this - opinion of core Git contributors would be especially
>> welcome...
>> ...
>>   - The new site is affiliated with a commercial entity - GitHub.
>> The website maintainer also has commercial interest in some published
>> Git learning materials, which might generate certain conflict of
>> interests; we must trust them that they handle this well.
>>   - Both GitHub and Scott seem to be rather distanced from the "core"
>> Git development community. This might or might not be an issue.
>
> These two are directly related.  They might be friendly and well-meaning
> folks, but I agree that they haven't earned our trust yet.
>
> But I do not think it matters that much.
>
> The thing is, git.or.cz may have been the closest thing to the "official"
> homepage we have had, but that is not because Linus or I or Shawn declared
> the site is official and/or that the site is trustworthy.  It was because
> you put efforts preparing the contents worthy to be one-stop shop for git
> related information, back when there was no such thing.  And the members
> of the comminity found it a good site.  And you have the wiki there, where
> there truly have been community participation to enhance the contents.
>
> For me personally, pages outside the wiki have never felt like "the
> official git homepage", not because the contents you prepared were
> inadequate, but because I did not see much community participation to help
> enrich it.
>
> So I wish the new site success, but the definition of success from my
> point of view is not how many random visitors it will attract, but how
> well the site makes the contributors (both to git software itself, and to
> the site's contents) feel welcomed.  Maybe in time it will become
> successful enough by _my_ definition of success, and I may recommend
> kernel.org folks to point at it from http://git.kernel.org/ (link with
> text "overview") if/when that happens, and I may start mentioning them in
> the "Note".  We'll see.
>
>>   The negatives section writeup is longer, but in fact I think the
>> positives win here; I also have a bit of bad conscience about not giving
>> git.or.cz the amount of time it would deserve...
>
> Let me thank you for maintaining not just git.or.cz/ but also repo.or.cz/
> and the wiki.  I personally never visited the "Homepage" but the
> repositories and the wiki are valuable services you gave back to the
> community.
>
> It's also somewhat interesting to observe that several people I have never
> heard of in the git circle are simultaneously doing new git books,
> apparently never asking for much technical advice from core git people, by
> the way.
>

To be honest, I have asked for a fair amount of technical advice from
many helpful people in the IRC channel over the past few years.  In my
case, one of my best friends - the guy I've been working with for the
last 4 years - is Nick Hengeveld, who has something like 50 commits in
there - why email the list when I can yell a question over the cube
wall?  I'm sure you all have more important things to do than review
my book for newbies - I asked Nick to do it.

If I could code C worth a lick, I'm sure I would have contributed more
to this list, but since I have nothing that I feel would be helpful to
you, I've passively followed the list.  I'm sorry that you do not
consider me a "git community member" just because I don't code C, and
so I can't contribute helpfully to core.

However, I have evangelized Git in person to literally thousands of
people, and tens of thousands more online.  GitHub hosts over 10,000
public git projects completely for free, and has contributed a ton
back to the community, both in code and proselytization efforts.

I hope these things can be taken as proof that we are not simply
friendly and well meaning, but that we have contributed meaningfully
to the adoption of Git and are just as committed to it's improvement
and success as nearly anyone on this list.

We want to help - help you with resources, help new people learn git
quickly and easily, and help the unconverted see the light.  We highly
respect you guys and most of the time you don't hear from us because
we don't want to bother you and take your time away from improving our
favorite tool.

Feel free to contact or email me at any time with questions, or
suggestions for improvement - schacon on IRC, schacon at gmail, or
thescottchacon on AIM.

Scott

^ permalink raw reply

* Re: git-scm.com
From: david @ 2008-07-26  6:27 UTC (permalink / raw)
  To: Scott Chacon; +Cc: Petr Baudis, Patrick Aljord, git list
In-Reply-To: <d411cc4a0807252230v76670d3cp2205e40826acc6e2@mail.gmail.com>

On Fri, 25 Jul 2008, Scott Chacon wrote:

> On Fri, Jul 25, 2008 at 7:47 PM,  <david@lang.hm> wrote:
>> On Sat, 26 Jul 2008, Petr Baudis wrote:
>>
>>>  Hi,
>>>
>>> On Fri, Jul 25, 2008 at 07:28:32PM -0700, Scott Chacon wrote:
>>>>
>>>> I am more concerned about the logo at the bottom, and Petr and I are
>>>> discussing this - I can remove the logo, but then I'd have to pay for
>>>> this out of my pocket instead of having a small logo on the page.
>>>
>>>  I actually think that this is *one* reference to GitHub that is
>>> perfectly and 100% okay; if it is sponsoring the hosting, it deserves
>>> the logo, and it is fairly non-intrusive. I _am_ watching out warily
>>> for excessive GitHub references within the rest of the site - if only
>>> because I have kind of personal interest in a competitor of GitHub and
>>> thus don't want GitHub to get unwarranted free advertising. :-)
>>>
>>>                                Petr "Pasky" Baudis
>>
>> since this is a Ruby on Rails site, could the 'five links' that have been
>> bothering people be randomly selected? if every time you go to the site you
>> get a different list of projects it show how broadly git is used. it's not
>> as 'in your face' as managing to select five that cause people to say "wow,
>> they're using this", but different people will react to different sites.
>>
>> if this table gets populated by GitHub, kernel.org, and a couple other
>> sources it should be vendor independant enough (and we need a table like
>> this anyway for the 'list of projects that use git', so it serves two
>> purposes)
>>
>> David Lang
>>
>
> I would really like to have the big ones there all the time ('Linux',
> 'Ruby on Rails', 'WINE', 'X.org', etc)  Prototype and MooTools are
> pretty big in the web dev world, which a lot of people are starting to
> come from - at least Prototype should be there all the time.  For the
> rest, if we want to pool a bunch of other projects from different
> places, that would be cool, but they should be active - I don't want
> people clicking on something above the fold and getting a dead
> project.  If someone wants to help me vet a list, I'd be happy to do
> that.

I can see things going either way on this, and I'm sure that the algorithm 
for the 'best' way to select projects can be tweaked endlessly. I am not 
that afraid of someone hitting a dead link, especially if you were to list 
them as 'projects 2,4895,9287,104,18439 of xxxxxx project that have 
reported using git' with numbers that large people expect that some 
projects will have gone dead, and even if they are all live today, how 
frequently did you plan to re-check them to decide they are dead? (and 
what is your definition of dead?)

> However, that being said, it's going to be difficult to have Github
> projects not dominate the list a bit.  The fact is that it hosts far,
> far more projects than any other single hosting service.  Just in
> fully public projects, the current stats (from the website pages) are
> something like this:
>
> kernel.org : 475
> repo.or.cz : 1,553
> gitorious   : 780
> github       : 10,560
>
> It hosts far more than that if you include private projects, too.  So,
> if we want to choose totally randomly, it's going to be at least a 5:1
> ratio between github projects and all other public hosting providers.
> If anything, statistically, the current list is conservative in it's
> links to github projects.  For me to avoid using them is artificially
> punishing them for having paid plans, which is silly.

as long as there is a mechanism to add things to the list I don't see 
anything wrong with the frequency reflecting this reality. anyone who 
thinks the numbers are skewed is free to add other projects to the list.

part of this is reducing the room for people to accuse you of impropriaty, 
if you select the links people can accuse you of playing favorites, if 
it's random selection and includes competitors entire lists, it's much 
clearer that you aren't skewing things.

David Lang

^ permalink raw reply

* Re: git-scm.com
From: Patrick Aljord @ 2008-07-26  5:49 UTC (permalink / raw)
  To: git list
In-Reply-To: <d411cc4a0807252230v76670d3cp2205e40826acc6e2@mail.gmail.com>

How about linking to the project web page or the official blog where
the move was announced when available? I think that's how it's done on
the mercurial page. And it explains people why the switch was done
rather then linking to a source repository they might not care about
and the link to the project page might give a hint about the
importance of the given project for those that might not know it (such
as prototype, mootools or liftweb).

example:
http://weblog.rubyonrails.org/2008/4/2/rails-is-moving-from-svn-to-git

^ permalink raw reply

* Re: [PATCH] git-svn: teach dcommit about svn auto-props
From: Eric Wong @ 2008-07-26  5:45 UTC (permalink / raw)
  To: Brad King; +Cc: Junio C Hamano, git
In-Reply-To: <4889F215.9020804@kitware.com>

Brad King <brad.king@kitware.com> wrote:
> 
> Subversion repositories often require files to have properties such as
> svn:mime-type and svn:eol-style set when they are added.  Users
> typically set these properties automatically using the SVN auto-props
> feature with 'svn add'.  This commit teaches dcommit to look at the user
> SVN configuration and apply matching auto-props entries for files added
> by a diff as it is applied to the SVN remote.
> 
> Signed-off-by: Brad King <brad.king@kitware.com>

Thanks Brad,

Acked-by: Eric Wong <normalperson@yhbt.net>

> ---
> Eric Wong wrote:
> > I like this patch.
> 
> Thanks.
> 
> > Can we get an automated test of this functionality?
> 
> This patch adds a test.  I also fixed the property name/value parsing
> to remove leading and trailing whitespace.
> 
> > We can (and probably should) set $HOME for the test and ignore the
> > existing ~/.subversion/config of the user.
> 
> I used the --config-dir option.
> 
> > Also, some minor nitpicks on whitespace/formatting inline below.
> 
> Addressed.  I missed the wrong indentation before because my second patch
> removed it.
> 
> > I haven't had the chance to look at this.   Can anybody else shed more
> > light on that bug?  It's really strange that the tests won't run because
> > of it.  Are you unable to run some git-svn tests or all of them?
> 
> Just that one fails.  All others (including the one in the patch below) pass.

Exactly which test fails for you?  Perhaps it's some setting in your
~/.subversion/config that's causing it to fail.  Maybe we should set
$HOME and use a clean ~/.subversion/config for git-svn tests regardless
if that turns out to be the case...

> Thanks for reviewing,
> -Brad
> 
>  git-svn.perl                          |   52 ++++++++++++++++++++
>  t/t9124-git-svn-dcommit-auto-props.sh |   84 +++++++++++++++++++++++++++++++++
>  2 files changed, 136 insertions(+), 0 deletions(-)
>  create mode 100755 t/t9124-git-svn-dcommit-auto-props.sh
> 

> diff --git a/git-svn.perl b/git-svn.perl
> index 2e0e552..0a8e907 100755
> --- a/git-svn.perl
> +++ b/git-svn.perl
> @@ -3340,6 +3340,7 @@ sub new {
>  	$self->{rm} = { };
>  	$self->{path_prefix} = length $self->{svn_path} ?
>  	                       "$self->{svn_path}/" : '';
> +	$self->{config} = $opts->{config};
>  	return $self;
>  }
>  
> @@ -3528,6 +3529,56 @@ sub ensure_path {
>  	return $bat->{$c};
>  }
>  
> +# Subroutine to convert a globbing pattern to a regular expression.
> +# From perl cookbook.
> +sub glob2pat {
> +	my $globstr = shift;
> +	my %patmap = ('*' => '.*', '?' => '.', '[' => '[', ']' => ']');
> +	$globstr =~ s{(.)} { $patmap{$1} || "\Q$1" }ge;
> +	return '^' . $globstr . '$';
> +}
> +
> +sub check_autoprop {
> +	my ($self, $pattern, $properties, $file, $fbat) = @_;
> +	# Convert the globbing pattern to a regular expression.
> +	my $regex = glob2pat($pattern);
> +	# Check if the pattern matches the file name.
> +	if($file =~ m/($regex)/) {
> +		# Parse the list of properties to set.
> +		my @props = split(/;/, $properties);
> +		foreach my $prop (@props) {
> +			# Parse 'name=value' syntax and set the property.
> +			if ($prop =~ /([^=]+)=(.*)/) {
> +				my ($n,$v) = ($1,$2);
> +				$n =~ s/^\s+//; $n =~ s/\s+$//;
> +				$v =~ s/^\s+//; $v =~ s/\s+$//;
> +				$self->change_file_prop($fbat, $n, $v);
> +			}
> +		}
> +	}
> +}
> +
> +sub apply_autoprops {
> +	my ($self, $file, $fbat) = @_;
> +	my $conf_t = ${$self->{config}}{'config'};
> +	no warnings 'once';
> +	# Check [miscellany]/enable-auto-props in svn configuration.
> +	if (SVN::_Core::svn_config_get_bool(
> +		$conf_t,
> +		$SVN::_Core::SVN_CONFIG_SECTION_MISCELLANY,
> +		$SVN::_Core::SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS,
> +		0)) {
> +		# Auto-props are enabled.  Enumerate them to look for matches.
> +		my $callback = sub {
> +			$self->check_autoprop($_[0], $_[1], $file, $fbat);
> +		};
> +		SVN::_Core::svn_config_enumerate(
> +			$conf_t,
> +			$SVN::_Core::SVN_CONFIG_SECTION_AUTO_PROPS,
> +			$callback);
> +	}
> +}
> +
>  sub A {
>  	my ($self, $m) = @_;
>  	my ($dir, $file) = split_path($m->{file_b});
> @@ -3535,6 +3586,7 @@ sub A {
>  	my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
>  					undef, -1);
>  	print "\tA\t$m->{file_b}\n" unless $::_q;
> +	$self->apply_autoprops($file, $fbat);
>  	$self->chg_file($fbat, $m);
>  	$self->close_file($fbat,undef,$self->{pool});
>  }
> diff --git a/t/t9124-git-svn-dcommit-auto-props.sh b/t/t9124-git-svn-dcommit-auto-props.sh
> new file mode 100755
> index 0000000..beefbcc
> --- /dev/null
> +++ b/t/t9124-git-svn-dcommit-auto-props.sh
> @@ -0,0 +1,84 @@
> +#!/bin/sh
> +#
> +# Copyright (c) 2008 Brad King
> +
> +test_description='git-svn dcommit honors auto-props'
> +
> +. ./lib-git-svn.sh
> +
> +generate_auto_props() {
> +cat << EOF
> +[miscellany]
> +enable-auto-props=$1
> +[auto-props]
> +*.sh  = svn:mime-type=application/x-shellscript; svn:eol-style=LF
> +*.txt = svn:mime-type=text/plain; svn:eol-style = native
> +EOF
> +}
> +
> +test_expect_success 'initialize git-svn' '
> +	mkdir import &&
> +	cd import &&
> +	echo foo > foo &&
> +	svn import -m "import for git-svn" . "$svnrepo" >/dev/null &&
> +	cd .. &&
> +	rm -rf import &&
> +	git-svn init "$svnrepo"
> +	git-svn fetch'
> +
> +test_expect_success 'enable auto-props config' '
> +	cd "$gittestrepo" &&
> +	mkdir user &&
> +	generate_auto_props yes > user/config
> +	'
> +
> +test_expect_success 'add files matching auto-props' '
> +	cd "$gittestrepo" &&
> +	echo "#!/bin/sh" > exec1.sh &&
> +	chmod +x exec1.sh &&
> +	echo "hello" > hello.txt &&
> +	echo bar > bar &&
> +	git add exec1.sh hello.txt bar &&
> +	git commit -m "files for enabled auto-props" &&
> +	git svn dcommit --config-dir=user
> +	'
> +
> +test_expect_success 'disable auto-props config' '
> +	cd "$gittestrepo" &&
> +	generate_auto_props no > user/config
> +	'
> +
> +test_expect_success 'add files matching disabled auto-props' '
> +	cd "$gittestrepo" &&
> +	echo "#!/bin/sh" > exec2.sh &&
> +	chmod +x exec2.sh &&
> +	echo "world" > world.txt &&
> +	echo zot > zot &&
> +	git add exec2.sh world.txt zot &&
> +	git commit -m "files for disabled auto-props" &&
> +	git svn dcommit --config-dir=user
> +	'
> +
> +test_expect_success 'check resulting svn repository' '
> +	mkdir work &&
> +	cd work &&
> +	svn co "$svnrepo" &&
> +	cd svnrepo &&
> +
> +	# Check properties from first commit.
> +	test "x$(svn propget svn:executable exec1.sh)" = "x*" &&
> +	test "x$(svn propget svn:mime-type exec1.sh)" = \
> +	     "xapplication/x-shellscript" &&
> +	test "x$(svn propget svn:mime-type hello.txt)" = "xtext/plain" &&
> +	test "x$(svn propget svn:eol-style hello.txt)" = "xnative" &&
> +	test "x$(svn propget svn:mime-type bar)" = "x" &&
> +
> +	# Check properties from second commit.
> +	test "x$(svn propget svn:executable exec2.sh)" = "x*" &&
> +	test "x$(svn propget svn:mime-type exec2.sh)" = "x" &&
> +	test "x$(svn propget svn:mime-type world.txt)" = "x" &&
> +	test "x$(svn propget svn:eol-style world.txt)" = "x" &&
> +	test "x$(svn propget svn:mime-type zot)" = "x"
> +	'
> +
> +test_done

-- 
Eric Wong

^ permalink raw reply

* Re: git-scm.com
From: Scott Chacon @ 2008-07-26  5:30 UTC (permalink / raw)
  To: david; +Cc: Petr Baudis, Patrick Aljord, git list
In-Reply-To: <alpine.DEB.1.10.0807251943280.11335@asgard.lang.hm>

On Fri, Jul 25, 2008 at 7:47 PM,  <david@lang.hm> wrote:
> On Sat, 26 Jul 2008, Petr Baudis wrote:
>
>>  Hi,
>>
>> On Fri, Jul 25, 2008 at 07:28:32PM -0700, Scott Chacon wrote:
>>>
>>> I am more concerned about the logo at the bottom, and Petr and I are
>>> discussing this - I can remove the logo, but then I'd have to pay for
>>> this out of my pocket instead of having a small logo on the page.
>>
>>  I actually think that this is *one* reference to GitHub that is
>> perfectly and 100% okay; if it is sponsoring the hosting, it deserves
>> the logo, and it is fairly non-intrusive. I _am_ watching out warily
>> for excessive GitHub references within the rest of the site - if only
>> because I have kind of personal interest in a competitor of GitHub and
>> thus don't want GitHub to get unwarranted free advertising. :-)
>>
>>                                Petr "Pasky" Baudis
>
> since this is a Ruby on Rails site, could the 'five links' that have been
> bothering people be randomly selected? if every time you go to the site you
> get a different list of projects it show how broadly git is used. it's not
> as 'in your face' as managing to select five that cause people to say "wow,
> they're using this", but different people will react to different sites.
>
> if this table gets populated by GitHub, kernel.org, and a couple other
> sources it should be vendor independant enough (and we need a table like
> this anyway for the 'list of projects that use git', so it serves two
> purposes)
>
> David Lang
>

I would really like to have the big ones there all the time ('Linux',
'Ruby on Rails', 'WINE', 'X.org', etc)  Prototype and MooTools are
pretty big in the web dev world, which a lot of people are starting to
come from - at least Prototype should be there all the time.  For the
rest, if we want to pool a bunch of other projects from different
places, that would be cool, but they should be active - I don't want
people clicking on something above the fold and getting a dead
project.  If someone wants to help me vet a list, I'd be happy to do
that.

However, that being said, it's going to be difficult to have Github
projects not dominate the list a bit.  The fact is that it hosts far,
far more projects than any other single hosting service.  Just in
fully public projects, the current stats (from the website pages) are
something like this:

kernel.org : 475
repo.or.cz : 1,553
gitorious   : 780
github       : 10,560

It hosts far more than that if you include private projects, too.  So,
if we want to choose totally randomly, it's going to be at least a 5:1
ratio between github projects and all other public hosting providers.
If anything, statistically, the current list is conservative in it's
links to github projects.  For me to avoid using them is artificially
punishing them for having paid plans, which is silly.

Scott

^ permalink raw reply

* [PATCH 2/5] Add git-sequencer documentation
From: Stephan Beyer @ 2008-07-26  5:20 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Daniel Barkalow, Stephan Beyer
In-Reply-To: <1217049644-8874-2-git-send-email-s-beyer@gmx.net>

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 Documentation/git-sequencer.txt |  676 +++++++++++++++++++++++++++++++++++++++
 1 files changed, 676 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/git-sequencer.txt

diff --git a/Documentation/git-sequencer.txt b/Documentation/git-sequencer.txt
new file mode 100644
index 0000000..04fea2c
--- /dev/null
+++ b/Documentation/git-sequencer.txt
@@ -0,0 +1,676 @@
+git-sequencer(1)
+================
+
+NAME
+----
+git-sequencer - Execute a sequence of git instructions
+
+SYNOPSIS
+--------
+[verse]
+'git sequencer' [--batch] [--onto=<base>] [--allow-dirty]
+		[--verbose | --no-advice | --quiet]
+		[--] [<file>]
+'git sequencer' --continue | --skip | --abort | --edit | --status
+
+
+DESCRIPTION
+-----------
+Executes a sequence of git instructions to HEAD or `<base>`.
+The sequence is given by `<file>` or standard input.
+Also see 'TODO FILE FORMAT' below.
+
+Before doing anything, the TODO file is checked for correct syntax
+and sanity.
+
+In case of a conflict or request in the TODO file, 'git-sequencer' will
+pause. On conflict you can use 'git-diff' to locate the markers (`<<<<<<<`)
+and make edits to resolve the conflict.
+
+For each file you edit, you need to tell git the changes by doing
+
+    git add <file>
+
+After resolving the conflict manually and updating the index with the
+desired resolution, you can continue the sequencing process with
+
+    git sequencer --continue
+
+Alternatively, you can undo the 'git-sequencer' progress with
+
+    git sequencer --abort
+
+or skip the current instruction with
+
+    git sequencer --skip
+
+or correct the TODO file with
+
+    git sequencer --edit
+
+During pauses or when finished with the sequencing task, the current
+HEAD will always be the result of the last processed instruction.
+
+
+OPTIONS
+-------
+<file>::
+	Filename of the TODO file.  If omitted, standard input is used.
+	See 'TODO FILE FORMAT' below.
+
+--allow-dirty::
+	Run even if working tree is dirty.
+
+-B::
+--batch::
+	Run in batch mode. If unexpected user intervention is needed
+	(e.g. a conflict or the need to run an editor), 'git-sequencer' fails.
++
+Note that the sanity check fails, if you use this option
+and an instruction like `edit` or `pause` is in the TODO file.
+
+--onto=<base>::
+	Checkout given commit or branch before sequencing.
+	If you provide a branch, sequencer will make the provided
+	changes on the branch, i.e. the branch will be changed.
+
+--continue::
+	Restart the sequencing process after having resolved a merge conflict.
+
+--abort::
+	Restore the original branch and abort the sequence operation.
+
+--skip::
+	Restart the sequencing process by skipping the current instruction.
+
+--status::
+	Show the current status of 'git-sequencer' and what
+	operations can be done to change that status.
+
+--edit::
+	Invoke editor to edit the unprocessed part of the TODO file.
++
+The file is syntax- and sanity-checked afterwards, so that you can
+safely run `git sequencer --skip` or `--continue` after editing.
+If you nonetheless noticed that you made a mistake, you can
+overwrite `.git/sequencer/todo` with `.git/sequencer/todo.old` and
+rerun `git sequencer --edit`.
++
+If the check fails you are prompted if you want to correct your
+changes, edit again, cancel editing or really want to save.
+
+--no-advice::
+	Suppress advice on intentional and unintentional pauses.
+
+-q::
+--quiet::
+	Suppress output. Implies `--no-advice`.
+	(Not yet implemented.)
+
+-v::
+--verbose::
+	Be more verbose.
+
+
+NOTES
+-----
+
+When sequencing, it is possible, that you are changing the history of
+a branch in a way that can cause problems for anyone who already has
+a copy of the branch in their repository and tries to pull updates from
+you.  You should understand the implications of using 'git-sequencer' on
+a repository that you share.
+
+'git-sequencer' will usually be called by another git porcelain, like
+linkgit:git-am[1] or linkgit:git-rebase[1].
+
+
+TODO FILE FORMAT
+----------------
+
+The TODO file contains basically one instruction per line.
+
+Blank lines will be ignored.
+All characters after a `#` character will be ignored until the end of a line.
+
+The following instructions can be used:
+
+
+edit <commit>::
+	Pick a commit and pause the sequencer process to let you
+	make changes.
++
+This is a short form for `pick <commit> and `pause` on separate lines.
+
+
+mark <mark>::
+	Set a symbolic mark for the last commit.
+	`<mark>` is an unsigned integer starting at 1 and
+	prefixed with a colon, e.g. `:1`.
++
+The marks can help if you want to refer to commits that you
+created during the sequencer process, e.g. if you want to
+merge such a commit.
++
+The set marks are removed after the sequencer has completed.
+
+
+merge [options] <commit-ish1> <commit-ish2> ... <commit-ishN>::
+	Merge commits into HEAD.
++
+You can refer to a commit by a mark.
++
+If you do not provide a commit message (using `-F`, `-m`, `-C`, `-M`,
+or `--standard`), an editor will be invoked.
++
+See the following list and 'GENERAL OPTIONS' for values of `options`:
+
+	--standard;;
+		Generate a commit message like 'Merge ... into HEAD'.
+		See also linkgit:git-fmt-merge-msg[1].
+
+	-s <strategy>;;
+	--strategy=<strategy>;;
+		Use the given merge strategy.
+		See also linkgit:git-merge[1].
+
+
+pick [options] <commit>::
+	Pick (see linkgit:git-cherry-pick[1]) a commit.
+	Sequencer will pause on conflicts.
++
+See the following list and 'GENERAL OPTIONS' for values of `options`:
+
+	-R;;
+	--reverse;;
+		Revert the changes introduced by pick <commit>.
+
+	--mainline=<n>;;
+		Allow you to pick merge commits by specifying the
+		parent number (beginning from 1) to let sequencer
+		replay the changes relative to the specified parent.
+		+
+This option does not work together with `-R`.
+
+
+patch [options] <file>::
+	If file `<file>` is a pure (diff) patch, then apply the patch.
+	If no `--message` option is given, an editor will
+	be invoked to enter a commit message.
++
+If `<file>` is a linkgit:git-format-patch[1]-formatted patch,
+then the patch will be commited.
++
+See the following list and 'GENERAL OPTIONS' for values of `options`:
+
+	-3;;
+	--3way;;
+		When the patch does not apply cleanly, fall back on
+		3-way merge, if the patch records the identity of blobs
+		it is supposed to apply to, and we have those blobs
+		available locally.
+
+	-k;;
+		Pass `-k` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]).
+
+	-n;;
+		Pass `-n` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]).
+
+	--exclude=<path-pattern>;;
+		Do not apply changes to files matching the given path pattern.
+		This can be useful when importing patchsets, where you want to
+		exclude certain files or directories.
+
+	-R;;
+	--reverse;;
+		Apply the patch in reverse.
+
+	--no-add;;
+		When applying a patch, ignore additions made by the
+		patch.  This can be used to extract the common part between
+		two files by first running 'diff' on them and applying
+		the result with this option, which would apply the
+		deletion part but not addition part.
+
+	--whitespace=<action>;;
+		Specify behavior on whitespace errors.
+		See linkgit:git-apply[1] for a detailed description.
+
+	--context=<n>;;
+		Ensure at least <n> lines of surrounding context match before
+		and after each change.  When fewer lines of surrounding
+		context exist they all must match.  By default no context is
+		ever ignored.
+
+	--inaccurate-eof;;
+		Under certain circumstances, some versions of 'diff' do not
+		correctly detect a missing new-line at the end of the file.
+		As a result, patches created by such 'diff' programs do not
+		record incomplete lines correctly.
+		This option adds support for applying such patches by
+		working around this bug.
+
+	-p<n>;;
+		Remove <n> leading slashes from traditional diff paths.
+		The default is 1.
+
+	--unidiff-zero;;
+		By default, 'git-apply' expects that the patch being
+		applied is a unified diff with at least one line of context.
+		This provides good safety measures, but breaks down when
+		applying a diff generated with --unified=0. To bypass these
+		checks use this option.
+
+
+pause::
+	Pause the sequencer process to let you manually make changes.
+	For example, you can re-edit the done commit, split a commit,
+	fix bugs or typos, or make further commits on top of HEAD before
+	continuing.
++
+After you have finished your changes and added them to the index,
+invoke `git sequencer --continue`.
+If you only want to edit the last commit message with an editor,
+run `git commit --amend` (see linkgit:git-commit[1]) before saying
+`--continue`.
+
+
+ref <ref>::
+	Set ref `<ref>` to the current HEAD, see also
+	linkgit:git-update-ref[1].
+
+
+reset <commit-ish>::
+	Go back (see linkgit:git-reset[1] `--hard`) to commit `<commit-ish>`.
+	`<commit-ish>` can also be given by a mark, if prefixed with a colon.
+
+
+squash [options] <commit>::
+	Add the changes introduced by `<commit>` to the last commit.
++
+See 'GENERAL OPTIONS' for values of `options`.
+
+squash [options] --from <mark>::
+	Squash all commits from the given mark into one commit.
+	There must not be any `merge` instructions between the
+	`mark` instruction and this `squash --from` instruction.
++
+See the following list and 'GENERAL OPTIONS' for values of `options`:
+
+	--collect-signoffs;;
+		Collect the Signed-off-by: lines of each commit and
+		add them to the squashed commit message.
+		(Not yet implemented.)
+
+	--include-merges;;
+		Sanity check does not fail if you have merges
+		between HEAD and <mark>.
+
+
+run [--dir=<path>] [--] <cmd> <args>...::
+	Run command `<cmd>` with arguments `<args>`.
+	Pause (conflict-like) if exit status is non-zero.
++
+If `<path>` is set, sequencer will change directory to `<path>`
+before running the command and change back after exit.
+
+
+GENERAL OPTIONS
+---------------
+
+Besides some special options, the instructions
+`patch`, `merge`, `pick`, `squash` take the following general options:
+
+--author=<author>::
+	Override the author name and e-mail address used in the commit.
+	Use `A U Thor <author@example.com>` format.
+
+-C <commit-ish>::
+--reuse-commit=<commit-ish>::
+	Reuse message and authorship data from specified commit.
+
+-M <commit-ish>
+--reuse-message=<commit-ish>::
+	Reuse message from specified commit.
+	Note, that only the commit message is reused
+	and not the authorship information.
+
+-F <file>::
+--file=<file>::
+	Take the commit message from the given file.
+
+-m <msg>::
+--message=<msg>::
+	Use the given `<msg>` as the commit message.
+
+--signoff::
+	Add `Signed-off-by:` line to the commit message (if not yet there),
+	using the committer identity of yourself.
+
+-e::
+--edit::
+	Regardless what commit message options are given,
+	invoke the editor to allow editing of the commit message.
+
+
+RETURN VALUES
+-------------
+
+'git-sequencer' returns:
+
+* `0`, if 'git-sequencer' successfully completed all the instructions
+       in the TODO file or successfully aborted after
+       `git sequencer --abort`,
+* `2`, on user-requested pausing, e.g.
+       when using the `edit` instruction.
+* `3`, on pauses that are not requested, e.g.
+       when there are conflicts to resolve
+       or errors in the TODO file.
+* any other value on error, e.g.
+  running 'git-sequencer' on a bare repository.
+
+
+EXAMPLES
+--------
+
+Here are some examples that shall ease the start with the TODO
+file format.
+Make sure you have understood the `pick` and perhaps the `patch` command.
+Those will not be explained further.
+
+Manually editing and adding commits
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sequencer allows manual intervention in between.
+This can be useful to
+
+* check if everything has gone right so far,
+
+* split commits,
+
+* edit changes and/or commit messages, or
+
+* add further manual commits on top of the current one.
+
+If you want to do one of this, either change `pick` to `edit`, or
+add a `pause` line after the specific instruction.
+
+Note that if you only want to edit the commit message in an
+editor, just use the `--edit` option of your `pick` or `patch`
+instruction.
+
+`HEAD` refers to the last commit being done by sequencer.
+So if you want to split a commit, repeat something like
+
+------------
+$ git reset HEAD^   # Reset index to HEAD^, but keep working tree
+		    # HEAD is the last commit being done by sequencer
+$ git add -p        # Add changes interactively to the index, and/or
+$ git add file1     # Add changes from file1 to the index
+$ git commit        # Commit staged changes
+------------
+
+until you have no changes to commit, and then run
+
+------------
+$ git sequencer --continue   # Continue sequencer process
+------------
+
+Be aware that if there are still staged changes,
+'git-sequencer' will add those changes to the last commit being done.
+
+
+Squashing commits
+~~~~~~~~~~~~~~~~~
+
+Squashing commits means putting the changes of many commits into one.
+If you have two commits `abcdef1` and `fa1afe1` and you want to squash them,
+feed 'git-sequencer' with a TODO file like:
+
+------------
+pick abcdef1
+squash fa1afe1
+------------
+
+Squash will concatenate the commit messages of `abcdef1` and `fa1afe1` and
+invoke an editor so that you can edit them.
+Perhaps you just want to reuse the commit message of `abcdef1` and
+add a signoff.  Then use:
+
+------------
+pick abcdef1
+squash -C abcdef1 --signoff fa1afe1
+------------
+
+You can also squash more than two commits.
+Basically you can do:
+
+------------
+pick A
+squash B
+squash C
+squash D
+squash --message "Make indentation consistent" --signoff E
+------------
+
+If somebody sent you a patch that you have not yet applied and you want
+to apply it and squash it, or if you have a `pick <commit>` list generated
+with something like
+
+------------
+$ git rev-list --no-merges --reverse A^..E | sed -e 's/^/pick /'`
+------------
+
+you can use the `mark` and `squash --from` instructions to
+squash all commits between them into one:
+
+------------
+mark :0
+pick A
+pick B
+pick C
+pick D
+pick E
+squash --message "Make indentation consistent" --signoff --from :0
+------------
+
+
+Branching and Merging
+~~~~~~~~~~~~~~~~~~~~~
+
+Merging branches can easily be done using the `merge` instruction.
+For an example, it is more interesting to branch, pick some commits
+and merge.  Imagine you want to 'copy' this onto the current branch:
+
+------------
+ ...--A1--A2--A3--A4--A5---MA-A6  refs/heads/old
+       |       \          /
+       |        C1--MC--C2  refs/heads/topic
+	\          /
+	 B1--B2--B3
+------------
+
+You want the copy to look exactly like this, except that you
+are not on branch `old`, and you want to call the copy of `topic`
+simply `topic2`.
+Here is a way to achieve this:
+
+------------
+pick A1
+mark :0    # remember this to pick further commits on trunk A
+
+pick B1    # pick commits for trunk B
+pick B2
+pick B3
+mark :1    # remember this for merge
+# Why not just merge B3 later?
+# Then you would merge the original B3 and not the copy.
+# But in this example you want to merge the copy of B3.
+
+reset :0   # go back to the copy of A1
+pick A2    # go on picking the commits of trunk A
+pick A3
+mark :2    # remember this to pick further commits on trunk A
+
+pick C1    # pick commits for trunk C
+merge -C MC :1    # merge trunk B
+pick C2
+ref refs/heads/topic2    # create branch for the C trunk
+
+reset :2   # go back to last commit of trunk A (copy of A3)
+pick A4    # go on picking the commits of trunk A
+pick A5
+merge --standard topic2   # merge trunk C
+pick A6
+------------
+
+
+Proper handling of conflicts
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+First of all, you are encouraged to use linkgit:git-rerere[1]:
+
+------------
+$ git config rerere.enabled true    # enable rerere
+------------
+
+Sequencer invokes 'git-rerere' automatically on conflict.
+
+If you experience conflicts, try
+
+------------
+$ git diff     # Show conflicting code
+$ git status   # Show conflicting files
+------------
+
+Then fix these conflicts using your editor and run
+
+------------
+$ git add file1 file2 file3   # Add modified files to the index
+$ git status                  # Make sure working tree is clean
+$ git sequencer --continue    # Continue sequencer process
+------------
+
+Now assume a conflict happens because you have unproperly edited
+the TODO file.
+
+Imagine your initial TODO file was:
+
+------------
+pick A
+pick C
+pick D
+------------
+
+But you wanted to pick B before C, and now you have this conflict on
+picking C.  You may first have a look at:
+
+------------
+$ git sequencer --status
+------------
+
+This will show you, what has been done, in what step the conflict
+happened and what is still to do, like this:
+
+------------
+Already done (or tried):
+  pick A
+  pick C
+
+Interrupted by conflict at
+  pick C
+
+Still to do:
+  pick D
+
+To abort & restore, invoke:
+    git sequencer --abort
+To continue, invoke:
+    git sequencer --continue
+To skip the current instruction, invoke:
+    git sequencer --skip
+------------
+
+A good way to solve that situation is running
+
+------------
+$ git sequencer --edit
+------------
+
+and change the file to:
+
+------------
+pick B
+pick C
+pick D
+------------
+
+Save the file, and invoke:
+
+------------
+$ git sequencer --skip
+------------
+
+Then the conflict-ridden `pick C` will be skipped and B is picked,
+before C will again be picked.
+
+
+Running tests
+~~~~~~~~~~~~~
+
+Imagine you have test programs within a `tests/` directory in your working
+tree.  But before running your test programs, you have to invoke `make` in
+the root directory of the working tree to compile your project.
+
+If the commit policy of your project says that after every commit the
+software must be able to compile and the test suite must pass, you
+are required to check this after every pick.
+
+This example shows how 'git-sequencer' can assist you:
+
+------------
+pick A      # Fix foo
+run make
+run --dir=tests ./test-foo
+pick B      # Extend bar
+run make
+run --dir tests -- ./test-bar --expensive-tests
+pick C
+run make
+run --dir tests make tests
+------------
+
+Sequencer will be paused, when a run fails (i.e. on non-zero exit status).
+Then it is your turn to fix the problem and make the tests pass.
+
+Note, that on `git sequencer --continue`, 'git-sequencer' will not
+repeat the failed `run` instruction.
+
+
+SEE ALSO
+--------
+
+linkgit:git-add[1],
+linkgit:git-am[1],
+linkgit:git-cherry-pick[1],
+linkgit:git-commit[1],
+linkgit:git-fmt-merge-msg[1],
+linkgit:git-format-patch[1],
+linkgit:git-rebase[1],
+linkgit:git-rerere[1],
+linkgit:git-reset[1],
+linkgit:git-update-ref[1]
+
+
+Authors
+-------
+Written by Stephan Beyer <s-beyer@gmx.net>.
+
+
+Documentation
+-------------
+Documentation by Stephan Beyer and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the linkgit:git[1] suite
-- 
1.6.0.rc0.49.gd39f

^ permalink raw reply related

* [PATCH 4/5] Migrate git-am to use git-sequencer
From: Stephan Beyer @ 2008-07-26  5:20 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Daniel Barkalow, Stephan Beyer
In-Reply-To: <1217049644-8874-4-git-send-email-s-beyer@gmx.net>

In principle a migration to git-sequencer is straightforward:
Put all the mail from the mbox or Maildir into .git/rebase-apply
and let the "patch" instruction of sequencer do the rest of the
work.

The git am --interactive part is a little more tricky.
To get this working, "pause" instructions are put after every
"patch" instruction and then be_interactive() swoops in,
that allows the user to input his choice.

Also a slight behavior change, that can be seen in the diff of
the test cases, should be mentioned: If git-am has nothing to
do, the user does not have to remove .git/rebase-apply or run
git-am --skip manually. It automatically aborts instead, which
seems to be an improvement.

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 git-am.sh     |  632 ++++++++++++++++++++-------------------------------------
 git-rebase.sh |    7 +-
 t/t4150-am.sh |    4 +-
 3 files changed, 220 insertions(+), 423 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index f4abd9d..ba93f02 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -34,103 +34,148 @@ cd_to_toplevel
 git var GIT_COMMITTER_IDENT >/dev/null ||
 	die "You need to set your committer info first"
 
-stop_here () {
-    echo "$1" >"$dotest/next"
-    exit 1
+cleanup () {
+	git gc --auto
+	rm -fr "$dotest"
 }
 
-stop_here_user_resolve () {
-    if [ -n "$resolvemsg" ]; then
-	    printf '%s\n' "$resolvemsg"
-	    stop_here $1
-    fi
-    cmdline="git am"
-    if test '' != "$interactive"
-    then
-        cmdline="$cmdline -i"
-    fi
-    if test '' != "$threeway"
-    then
-        cmdline="$cmdline -3"
-    fi
-    echo "When you have resolved this problem run \"$cmdline --resolved\"."
-    echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
-    echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
-
-    stop_here $1
+die_abort () {
+	cleanup
+	die "$1"
 }
 
-go_next () {
-	rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
-		"$dotest/patch" "$dotest/info"
-	echo "$next" >"$dotest/next"
-	this=$next
-}
+be_interactive () {
+	msg="$GIT_DIR/sequencer/message"
+	patch="$GIT_DIR/sequencer/patch"
+	# we rely on sequencer here
+
+	test -t 0 ||
+		die "cannot be interactive without stdin connected to a terminal."
+	action=$(cat "$dotest/interactive")
+	while test "$action" = again
+	do
+		echo
+		echo "Commit Body is:"
+		echo "--------------------------"
+		cat "$msg"
+		echo "--------------------------"
+		if test -z "$1"
+		then
+			printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+		else
+			echo 'Patch does not apply cleanly!'
+			printf "Apply+fix? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+		fi
 
-cannot_fallback () {
-	echo "$1"
-	echo "Cannot fall back to three-way merge."
-	exit 1
+		read reply
+		case "$reply" in
+		[yY]*)
+			return 0
+			;;
+		[nN]*)
+			# pretend we never tried to apply
+			to=HEAD
+			test conflict = "$1" ||
+				to=HEAD^
+			git read-tree -m -u HEAD $to
+			git reset -q $to >/dev/null
+			return 1
+			;;
+		[eE]*)
+			git_editor "$msg"
+			git commit --amend --file="$msg" --no-verify >/dev/null
+			;;
+		[vV]*)
+			LESS=-S ${PAGER:-less} "$patch"
+			;;
+		[aA]*)
+			echo 'accept' >"$dotest/interactive"
+			return 0
+			;;
+		*)
+			:
+			;;
+		esac
+	done
+	test "$action" = accept &&
+		sed -n -e '1s/^/Applying &/p' <"$msg"
+	return 0
 }
 
-fall_back_3way () {
-    O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
-
-    rm -fr "$dotest"/patch-merge-*
-    mkdir "$dotest/patch-merge-tmp-dir"
-
-    # First see if the patch records the index info that we can use.
-    git apply --build-fake-ancestor "$dotest/patch-merge-tmp-index" \
-	"$dotest/patch" &&
-    GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
-    git write-tree >"$dotest/patch-merge-base+" ||
-    cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge."
-
-    echo Using index info to reconstruct a base tree...
-    if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
-	git apply $binary --cached <"$dotest/patch"
-    then
-	mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
-	mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
-    else
-        cannot_fallback "Did you hand edit your patch?
-It does not apply to blobs recorded in its index."
-    fi
-
-    test -f "$dotest/patch-merge-index" &&
-    his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git write-tree) &&
-    orig_tree=$(cat "$dotest/patch-merge-base") &&
-    rm -fr "$dotest"/patch-merge-* || exit 1
-
-    echo Falling back to patching base and 3-way merge...
+print_continue_info () {
+	echo 'When you have resolved this problem run "git am --resolved".'
+	echo 'If you would prefer to skip this patch, instead run "git am --skip".'
+	echo 'To restore the original branch and stop patching run "git am --abort".'
+}
 
-    # This is not so wrong.  Depending on which base we picked,
-    # orig_tree may be wildly different from ours, but his_tree
-    # has the same set of wildly different changes in parts the
-    # patch did not touch, so recursive ends up canceling them,
-    # saying that we reverted all those changes.
+run_sequencer () {
+	git sequencer $seqopts --caller='git am|--abort|--resolved|--skip' "$@"
+	case "$?" in
+	0)
+		cleanup
+		exit 0
+		;;
+	2|3)
+		ret=$?
+		print_continue_info
+		exit $(($ret-2))
+		;;
+	*)
+		die_abort 'git-sequencer died unexpected. Aborting.'
+		;;
+	esac
+}
 
-    eval GITHEAD_$his_tree='"$FIRSTLINE"'
-    export GITHEAD_$his_tree
-    git-merge-recursive $orig_tree -- HEAD $his_tree || {
-	    git rerere
-	    echo Failed to merge in the changes.
-	    exit 1
-    }
-    unset GITHEAD_$his_tree
+run_sequencer_i () {
+	command="$1"
+	while true
+	do
+		output=$(git sequencer $seqopts \
+			--caller='git am -i|--abort|--resolved|--skip' \
+			$command 2>&1)
+		case "$?" in
+		0)
+			cleanup
+			exit 0
+			;;
+		2)
+			if test -f "$dotest/conflict"
+			then
+				rm "$dotest/conflict"
+			else
+				be_interactive
+			fi
+			;;
+		3)
+			: >"$dotest/conflict"
+			be_interactive conflict
+			if test $? -eq 0
+			then
+				printf '%s\n' "$output" 1>&2
+				print_continue_info
+				exit 1
+			fi
+			;;
+		*)
+			die_abort "$output"
+			;;
+		esac
+		seqopts=
+		command=--continue
+	done
 }
 
 prec=4
 dotest="$GIT_DIR/rebase-apply"
+todofile="$dotest/todo"
 sign= utf8=t keep= skip= interactive= resolved= binary= rebasing= abort=
-resolvemsg= resume=
-git_apply_opt=
+opts=
 
 while test $# != 0
 do
 	case "$1" in
 	-i|--interactive)
-		interactive=t ;;
+		interactive=_i ;;
 	-b|--binary)
 		binary=t ;;
 	-3|--3way)
@@ -157,9 +202,9 @@ do
 	--resolvemsg)
 		shift; resolvemsg=$1 ;;
 	--whitespace)
-		git_apply_opt="$git_apply_opt $1=$2"; shift ;;
+		opts="$opts $1=$2"; shift ;;
 	-C|-p)
-		git_apply_opt="$git_apply_opt $1$2"; shift ;;
+		opts="$opts $1$2"; shift ;;
 	--)
 		shift; break ;;
 	*)
@@ -168,363 +213,118 @@ do
 	shift
 done
 
-# If the dotest directory exists, but we have finished applying all the
-# patches in them, clear it out.
-if test -d "$dotest" &&
-   last=$(cat "$dotest/last") &&
-   next=$(cat "$dotest/next") &&
-   test $# != 0 &&
-   test "$next" -gt "$last"
-then
-   rm -fr "$dotest"
-fi
-
 if test -d "$dotest"
 then
-	case "$#,$skip$resolved$abort" in
-	0,*t*)
-		# Explicit resume command and we do not have file, so
-		# we are happy.
-		: ;;
-	0,)
-		# No file input but without resume parameters; catch
-		# user error to feed us a patch from standard input
-		# when there is already $dotest.  This is somewhat
-		# unreliable -- stdin could be /dev/null for example
-		# and the caller did not intend to feed us a patch but
-		# wanted to continue unattended.
-		tty -s
-		;;
-	*)
-		false
-		;;
-	esac ||
-	die "previous rebase directory $dotest still exists but mbox given."
-	resume=yes
+	test "$#" != 0 &&
+		die "previous rebase directory $dotest still exists but mbox given."
 
-	case "$skip,$abort" in
-	t,)
-		git rerere clear
-		git read-tree --reset -u HEAD HEAD
-		orig_head=$(cat "$GIT_DIR/ORIG_HEAD")
-		git reset HEAD
-		git update-ref ORIG_HEAD $orig_head
-		;;
-	,t)
-		git rerere clear
-		git read-tree --reset -u HEAD ORIG_HEAD
-		git reset ORIG_HEAD
-		rm -fr "$dotest"
-		exit ;;
-	esac
-else
-	# Make sure we are not given --skip, --resolved, nor --abort
-	test "$skip$resolved$abort" = "" ||
-		die "Resolve operation not in progress, we are not resuming."
+	test -f "$dotest/interactive" &&
+		interactive=_i action=$(cat "$dotest/interactive")
 
-	# Start afresh.
-	mkdir -p "$dotest" || exit
+	# No file input but without resume parameters; catch
+	# user error to feed us a patch from standard input
+	# when there is already $dotest.  This is somewhat
+	# unreliable -- stdin could be /dev/null for example
+	# and the caller did not intend to feed us a patch but
+	# wanted to continue unattended.
+	test -z "$abort$resolved$skip" && tty -s
 
-	if test -n "$prefix" && test $# != 0
-	then
-		first=t
-		for arg
-		do
-			test -n "$first" && {
-				set x
-				first=
-			}
-			case "$arg" in
-			/*)
-				set "$@" "$arg" ;;
-			*)
-				set "$@" "$prefix$arg" ;;
-			esac
-		done
-		shift
-	fi
-	git mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" ||  {
-		rm -fr "$dotest"
-		exit 1
-	}
+	test -n "$abort" && run_sequencer$interactive --abort
+	test -n "$resolved" && run_sequencer$interactive --continue
+	test -n "$skip" && run_sequencer$interactive --skip
 
-	# -b, -s, -u, -k and --whitespace flags are kept for the
-	# resuming session after a patch failure.
-	# -3 and -i can and must be given when resuming.
-	echo "$binary" >"$dotest/binary"
-	echo " $ws" >"$dotest/whitespace"
-	echo "$sign" >"$dotest/sign"
-	echo "$utf8" >"$dotest/utf8"
-	echo "$keep" >"$dotest/keep"
-	echo 1 >"$dotest/next"
-	if test -n "$rebasing"
-	then
-		: >"$dotest/rebasing"
-	else
-		: >"$dotest/applying"
-		git update-ref ORIG_HEAD HEAD
-	fi
+	die "$dotest still exists. Use git am --abort/--skip/--resolved."
 fi
 
-case "$resolved" in
-'')
-	files=$(git diff-index --cached --name-only HEAD --) || exit
-	if [ "$files" ]; then
-	   echo "Dirty index: cannot apply patches (dirty: $files)" >&2
-	   exit 1
-	fi
-esac
+# Make sure we are not given --skip, --resolved, nor --abort
+test -z "$abort$resolved$skip" ||
+	die 'git-am is not in progress. You cannot use --abort/--skip/--resolved then.'
 
-if test "$(cat "$dotest/binary")" = t
-then
-	binary=--allow-binary-replacement
-fi
-if test "$(cat "$dotest/utf8")" = t
-then
-	utf8=-u
-else
-	utf8=-n
-fi
-if test "$(cat "$dotest/keep")" = t
-then
-	keep=-k
-fi
-ws=`cat "$dotest/whitespace"`
-if test "$(cat "$dotest/sign")" = t
+# sequencer running?
+git sequencer --status >/dev/null 2>&1 &&
+	die "Sequencer already started. Cannot run git-am."
+
+# Start afresh.
+mkdir -p "$dotest" ||
+	die "Could not create $dotest directory."
+
+if test -n "$prefix" && test $# != 0
 then
-	SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
-			s/>.*/>/
-			s/^/Signed-off-by: /'
-		`
-else
-	SIGNOFF=
+	first=t
+	for arg
+	do
+		test -n "$first" && {
+			set x
+			first=
+		}
+		case "$arg" in
+		/*)
+			set "$@" "$arg" ;;
+		*)
+			set "$@" "$prefix$arg" ;;
+		esac
+	done
+	shift
 fi
+last=$(git mailsplit -d"$prec" -o"$dotest" -b -- "$@") ||  {
+	cleanup
+	exit 1
+}
+this=1
 
-last=`cat "$dotest/last"`
-this=`cat "$dotest/next"`
-if test "$skip" = t
-then
-	this=`expr "$this" + 1`
-	resume=
+files=$(git diff-index --cached --name-only HEAD --) || exit
+if [ "$files" ]; then
+	echo "Dirty index: cannot apply patches (dirty: $files)" >&2
+	exit 1
 fi
 
-if test "$this" -gt "$last"
+test -n "$interactive" && echo 'again' >"$dotest/interactive"
+
+# converting our options to git-sequencer file insn options
+test -n "$binary" && opts="$opts --binary"
+test -n "$utf8" || opts="$opts -n"
+test -n "$keep" && opts="$opts -k"
+test -n "$sign" && opts="$opts --signoff"
+test -n "$threeway" && opts="$opts -3"
+
+# these files are created for tab completion scripts
+if test -n "$rebasing"
 then
-	echo Nothing to do.
-	rm -fr "$dotest"
-	exit
+	: >"$dotest/rebasing"
+else
+	: >"$dotest/applying"
+	git update-ref ORIG_HEAD HEAD
 fi
 
+# create todofile
+: > "$todofile" ||
+	die_abort "Cannot create $todofile"
 while test "$this" -le "$last"
 do
-	msgnum=`printf "%0${prec}d" $this`
-	next=`expr "$this" + 1`
-	test -f "$dotest/$msgnum" || {
-		resume=
-		go_next
-		continue
-	}
-
-	# If we are not resuming, parse and extract the patch information
-	# into separate files:
-	#  - info records the authorship and title
-	#  - msg is the rest of commit log message
-	#  - patch is the patch body.
-	#
-	# When we are resuming, these files are either already prepared
-	# by the user, or the user can tell us to do so by --resolved flag.
-	case "$resume" in
-	'')
-		git mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
-			<"$dotest/$msgnum" >"$dotest/info" ||
-			stop_here $this
-
-		# skip pine's internal folder data
-		grep '^Author: Mail System Internal Data$' \
-			<"$dotest"/info >/dev/null &&
-			go_next && continue
-
-		test -s "$dotest/patch" || {
-			echo "Patch is empty.  Was it split wrong?"
-			stop_here $this
-		}
-		if test -f "$dotest/rebasing" &&
-			commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
-				-e q "$dotest/$msgnum") &&
-			test "$(git cat-file -t "$commit")" = commit
-		then
-			git cat-file commit "$commit" |
-			sed -e '1,/^$/d' >"$dotest/msg-clean"
-		else
-			SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
-			case "$keep_subject" in -k)  SUBJECT="[PATCH] $SUBJECT" ;; esac
-
-			(printf '%s\n\n' "$SUBJECT"; cat "$dotest/msg") |
-				git stripspace > "$dotest/msg-clean"
-		fi
-		;;
-	esac
-
-	GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")"
-	GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
-	GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
-
-	if test -z "$GIT_AUTHOR_EMAIL"
-	then
-		echo "Patch does not have a valid e-mail address."
-		stop_here $this
-	fi
-
-	export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
-
-	case "$resume" in
-	'')
-	    if test '' != "$SIGNOFF"
-	    then
-		LAST_SIGNED_OFF_BY=`
-		    sed -ne '/^Signed-off-by: /p' \
-		    "$dotest/msg-clean" |
-		    sed -ne '$p'
-		`
-		ADD_SIGNOFF=`
-		    test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
-		    test '' = "$LAST_SIGNED_OFF_BY" && echo
-		    echo "$SIGNOFF"
-		}`
-	    else
-		ADD_SIGNOFF=
-	    fi
-	    {
-		if test -s "$dotest/msg-clean"
-		then
-			cat "$dotest/msg-clean"
-		fi
-		if test '' != "$ADD_SIGNOFF"
-		then
-			echo "$ADD_SIGNOFF"
-		fi
-	    } >"$dotest/final-commit"
-	    ;;
-	*)
-		case "$resolved$interactive" in
-		tt)
-			# This is used only for interactive view option.
-			git diff-index -p --cached HEAD -- >"$dotest/patch"
-			;;
-		esac
-	esac
-
-	resume=
-	if test "$interactive" = t
-	then
-	    test -t 0 ||
-	    die "cannot be interactive without stdin connected to a terminal."
-	    action=again
-	    while test "$action" = again
-	    do
-		echo "Commit Body is:"
-		echo "--------------------------"
-		cat "$dotest/final-commit"
-		echo "--------------------------"
-		printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
-		read reply
-		case "$reply" in
-		[yY]*) action=yes ;;
-		[aA]*) action=yes interactive= ;;
-		[nN]*) action=skip ;;
-		[eE]*) git_editor "$dotest/final-commit"
-		       action=again ;;
-		[vV]*) action=again
-		       LESS=-S ${PAGER:-less} "$dotest/patch" ;;
-		*)     action=again ;;
-		esac
-	    done
-	else
-	    action=yes
-	fi
-	FIRSTLINE=$(sed 1q "$dotest/final-commit")
+	msgnum=$(printf "%0${prec}d" $this)
+	this=$(($this+1))
 
-	if test $action = skip
-	then
-		go_next
+	# This ignores every mail that does not contain a patch.
+	grep '^diff' "$dotest/$msgnum" >/dev/null ||
 		continue
-	fi
-
-	if test -x "$GIT_DIR"/hooks/applypatch-msg
-	then
-		"$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
-		stop_here $this
-	fi
-
-	printf 'Applying: %s\n' "$FIRSTLINE"
 
-	case "$resolved" in
-	'')
-		git apply $git_apply_opt $binary --index "$dotest/patch"
-		apply_status=$?
-		;;
-	t)
-		# Resolved means the user did all the hard work, and
-		# we do not have to do any patch application.  Just
-		# trust what the user has in the index file and the
-		# working tree.
-		resolved=
-		git diff-index --quiet --cached HEAD -- && {
-			echo "No changes - did you forget to use 'git add'?"
-			stop_here_user_resolve $this
-		}
-		unmerged=$(git ls-files -u)
-		if test -n "$unmerged"
-		then
-			echo "You still have unmerged paths in your index"
-			echo "did you forget to use 'git add'?"
-			stop_here_user_resolve $this
-		fi
-		apply_status=0
-		git rerere
-		;;
-	esac
-
-	if test $apply_status = 1 && test "$threeway" = t
-	then
-		if (fall_back_3way)
-		then
-		    # Applying the patch to an earlier tree and merging the
-		    # result may have produced the same tree as ours.
-		    git diff-index --quiet --cached HEAD -- && {
-			echo No changes -- Patch already applied.
-			go_next
-			continue
-		    }
-		    # clear apply_status -- we have successfully merged.
-		    apply_status=0
-		fi
-	fi
-	if test $apply_status != 0
-	then
-		echo Patch failed at $msgnum.
-		stop_here_user_resolve $this
-	fi
-
-	if test -x "$GIT_DIR"/hooks/pre-applypatch
-	then
-		"$GIT_DIR"/hooks/pre-applypatch || stop_here $this
-	fi
-
-	tree=$(git write-tree) &&
-	parent=$(git rev-parse --verify HEAD) &&
-	commit=$(git commit-tree $tree -p $parent <"$dotest/final-commit") &&
-	git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
-	stop_here $this
-
-	if test -x "$GIT_DIR"/hooks/post-applypatch
-	then
-		"$GIT_DIR"/hooks/post-applypatch
-	fi
-
-	go_next
+	extra=
+	test -n "$rebasing" &&
+		commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
+			-e q "$dotest/$msgnum") &&
+		test "$(git cat-file -t "$commit")" = commit &&
+		extra=" -C $commit"
+
+	subject=$(sed -n '1,/^Subject:/s/Subject: *\(\[.*\]\)\{0,1\} *//p' \
+		<"$dotest/$msgnum")
+	test -n "$interactive" ||
+		printf 'run -- printf '\''Applying: %%s\\n'\'' '\''%s'\''\n' \
+			"$(printf '%s\n' "$subject" |
+				sed "s/'/'\\\\''/g")" >>"$todofile"
+	printf 'patch%s%s "%s" # %s\n' "$opts" "$extra" "$dotest/$msgnum" \
+		"$subject" >>"$todofile"
+	test -z "$interactive" || echo 'pause' >>"$todofile"
 done
 
-git gc --auto
-
-rm -fr "$dotest"
+seqopts="--no-advice --allow-dirty"
+run_sequencer$interactive "$todofile"
diff --git a/git-rebase.sh b/git-rebase.sh
index 412e135..67c1868 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -216,12 +216,11 @@ do
 		if test -d "$dotest"
 		then
 			move_to_original_branch
+			git reset --hard $(cat "$dotest/orig-head")
+			rm -r "$dotest"
 		else
-			dotest="$GIT_DIR"/rebase-apply
-			move_to_original_branch
+			git am --abort
 		fi
-		git reset --hard $(cat "$dotest/orig-head")
-		rm -r "$dotest"
 		exit
 		;;
 	--onto)
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 6e6aaf5..d49c69d 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -212,14 +212,12 @@ test_expect_success 'am takes patches from a Pine mailbox' '
 '
 
 test_expect_success 'am fails on mail without patch' '
-	test_must_fail git am <failmail &&
-	rm -r .git/rebase-apply/
+	test_must_fail git am <failmail
 '
 
 test_expect_success 'am fails on empty patch' '
 	echo "---" >>failmail &&
 	test_must_fail git am <failmail &&
-	git am --skip &&
 	! test -d .git/rebase-apply
 '
 
-- 
1.6.0.rc0.49.gd39f

^ permalink raw reply related

* [PATCH 3/5] Add git-sequencer test suite (t3350)
From: Stephan Beyer @ 2008-07-26  5:20 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Daniel Barkalow, Stephan Beyer
In-Reply-To: <1217049644-8874-3-git-send-email-s-beyer@gmx.net>

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 t/t3350-sequencer.sh |  838 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 838 insertions(+), 0 deletions(-)
 create mode 100755 t/t3350-sequencer.sh

diff --git a/t/t3350-sequencer.sh b/t/t3350-sequencer.sh
new file mode 100755
index 0000000..3cc7da8
--- /dev/null
+++ b/t/t3350-sequencer.sh
@@ -0,0 +1,838 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Stephan Beyer
+#
+# `setup' is based on t3404* by Johannes Schindelin.
+
+test_description='git sequencer
+
+These are basic usage tests for git sequencer.
+'
+. ./test-lib.sh
+
+# set up two branches like this:
+#
+# A - B - C - D - E
+#   \
+#     F - G - H
+#       \
+#         I
+#
+# where B, D and G touch increment value in file1.
+# The others generate empty file[23456].
+
+SEQDIR=".git/sequencer"
+SEQMARK="refs/sequencer-marks"
+MARKDIR=".git/$SEQMARK"
+
+test_expect_success 'setup' '
+	: >file1 &&
+	git add file1 &&
+	test_tick &&
+	git commit -m "generate empty file1" &&
+	git tag A &&
+	echo 1 >file1 &&
+	test_tick &&
+	git commit -m "write 1 into file1" file1 &&
+	git tag B &&
+	: >file2 &&
+	git add file2 &&
+	test_tick &&
+	git commit -m "generate empty file2" &&
+	git tag C &&
+	echo 2 >file1 &&
+	test_tick &&
+	git commit -m "write 2 into file1" file1 &&
+	git tag D &&
+	: >file3 &&
+	git add file3 &&
+	test_tick &&
+	git commit -m "generate empty file3" &&
+	git tag E &&
+	git checkout -b branch1 A &&
+	: >file4 &&
+	git add file4 &&
+	test_tick &&
+	git commit -m "generate empty file4" &&
+	git tag F &&
+	echo 3 >file1 &&
+	test_tick &&
+	git commit -m "write 3 into file1" file1 &&
+	git tag G &&
+	: >file5 &&
+	git add file5 &&
+	test_tick &&
+	git commit -m "generate empty file5" &&
+	git tag H &&
+	git checkout -b branch2 F &&
+	: >file6 &&
+	git add file6 &&
+	test_tick &&
+	git commit -m "generate empty file6" &&
+	git tag I &&
+	git diff -p --raw C..D >patchD.raw &&
+	git diff -p --raw A..F >patchF.raw &&
+	git format-patch --stdout A..B >patchB &&
+	git format-patch --stdout B..C >patchC &&
+	git format-patch --stdout C..D >patchD &&
+	git format-patch --stdout A..F >patchF &&
+	git format-patch --stdout F..G >patchG
+'
+
+orig_author="$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>"
+
+# Functions to verify exit status of sequencer.
+# Do not just use "test_must_fail git sequencer ..."!
+expect_fail () {
+	"$@"
+	test $? -eq 1
+}
+expect_continue () {
+	"$@"
+	test $? -eq 2
+}
+expect_conflict () {
+	"$@"
+	test $? -eq 3
+}
+
+
+# Other test helpers:
+
+# Test if commit $1 has author $2
+expect_author () {
+	test "$2" = "$(git cat-file commit "$1" |
+		sed -n -e "s/^author \(.*\)> .*$/\1>/p")"
+}
+
+# Test if commit $1 has commit message in file $2
+# Side effect: overwrites actual
+expect_msg () {
+	git cat-file commit "$1" | sed -e "1,/^$/d" >actual &&
+	test_cmp "$2" actual
+}
+
+# Test that no marks are set.
+no_marks_set () {
+	if test -e "$MARKDIR"
+	then
+		rmdir "$MARKDIR"
+	fi
+}
+
+test_expect_success 'fail on empty TODO from stdin' '
+	expect_fail git sequencer <file6 &&
+	! test -d "$SEQDIR"
+'
+
+# Generate fake editor
+#
+# Simple and practical concept:
+#  We use only a small string identifier for "editor sessions".
+#  Each sessions knows what to do and perhaps defines
+#  which session to choose next.
+echo "#!$SHELL_PATH" >fake-editor.sh
+cat >>fake-editor.sh <<\EOF
+test -f fake-editor-session || exit 1
+#test -t 1 || exit 1
+# This test could be useful, but as the test-lib is not always
+# verbose, this will fail.
+next=ok
+read this <fake-editor-session
+case "$this" in
+commitmsg)
+	echo 'echo 2 >file1'
+	;;
+squashCE)
+	echo 'generate file2 and file3'
+	;;
+squashCI)
+	echo 'generate file2 and file6'
+	next=squashDCE
+	;;
+squashDCE)
+	echo 'generate file2 and file3 and write 2 into file1'
+	next=merge1
+	;;
+merge1)
+	echo 'A typed merge message.'
+	;;
+merge2)
+	test "$(sed -n -e 1p "$1")" = 'test merge' &&
+		echo 'cleanup merge' ||
+		echo error
+	sed -e 1d "$1"
+	;;
+editXXXXXXXXX)
+	printf 'last edited'
+	;;
+edit*)
+	printf 'edited: '
+	cat "$1"
+	next="${this}X"
+	;;
+nochange)
+	cat "$1"
+	;;
+ok|fail)
+	echo '-- THIS IS UNEXPECTED --'
+	next=fail
+	;;
+*)
+	echo 'I do not know.'
+	;;
+esac >"$1".tmp
+mv "$1".tmp "$1"
+echo $next >fake-editor-session
+exit 0
+EOF
+chmod a+x fake-editor.sh
+test_set_editor "$(pwd)/fake-editor.sh"
+
+next_session () {
+	echo "$1" >fake-editor-session
+}
+
+# check if fake-editor-session is ok.
+# If "$1" is set to anything, it will set the
+# next session to "ok", which is nice for
+# test_expect_failure.
+session_ok () {
+	test "ok" = $(cat fake-editor-session)
+	ret=$?
+	test -n "$1" && next_session ok
+	return $ret
+}
+
+
+cat >todotest1 <<EOF
+pick C
+squash E
+ref refs/tags/CE
+EOF
+
+test_expect_success '"pick", "squash", "ref" from stdin' '
+	next_session squashCE &&
+	git sequencer <todotest1 &&
+	! test -d "$SEQDIR" &&
+	session_ok &&
+	test -f file2 &&
+	test -f file3 &&
+	test $(git rev-parse CE) = $(git rev-parse HEAD) &&
+	test $(git rev-parse I) = $(git rev-parse HEAD^)
+'
+
+cat >todotest2 <<EOF
+# This is a test
+
+reset I  # go back to I
+
+EOF
+
+test_expect_success '"reset" from file with comments and blank lines' '
+	git sequencer todotest2 &&
+	session_ok &&
+	test $(git rev-parse I) = $(git rev-parse HEAD)
+'
+
+cat >todotest1 <<EOF
+pick C
+EOF
+
+test_expect_success '--onto <branch> keeps branch' '
+	git checkout -b test-branch A &&
+	git checkout master &&
+	git sequencer --onto test-branch <todotest1 &&
+	session_ok &&
+	test "$(git symbolic-ref -q HEAD)" = "refs/heads/test-branch" &&
+	test "$(git rev-parse test-branch^)" = "$(git rev-parse A)"
+'
+
+test_expect_success '--onto commit (detached HEAD) works' '
+	git sequencer --onto A <todotest1 &&
+	session_ok &&
+	test_must_fail git symbolic-ref -q HEAD &&
+	test "$(git rev-parse HEAD)" = "$(git rev-parse test-branch)"
+'
+
+echo 'pick -R C' >>todotest1
+
+test_expect_success 'pick -R works' '
+	git checkout A &&
+	git sequencer todotest1 &&
+	session_ok &&
+	! test -f file2
+'
+
+mkdir testdir
+cat >testdir/script <<EOF
+#!/bin/sh
+test -s ../file1
+EOF
+chmod 755 testdir/script
+cat >todotest1 <<EOF
+run -- test -s file1  # this will fail
+pick B
+run --dir testdir -- test -s ../file1
+pick D
+run --dir=testdir ./script
+EOF
+
+test_expect_success '"run" insn works' '
+	git checkout A &&
+	expect_conflict git sequencer todotest1 &&
+	: >newfile &&
+	git add newfile &&
+	next_session nochange &&
+	git sequencer --continue &&
+	session_ok &&
+	test -f newfile
+'
+
+echo thisdoesnotexist >>todotest1
+
+test_expect_success 'junk is conflict' '
+	git checkout A &&
+	expect_conflict git sequencer todotest1 &&
+	test -d "$SEQDIR" &&
+	git sequencer --abort &&
+	! test -d "$SEQDIR" &&
+	session_ok &&
+	test $(git rev-parse A) = $(git rev-parse HEAD)
+'
+
+GIT_AUTHOR_NAME="Another 'ant' Thor"
+GIT_AUTHOR_EMAIL="a.thor@example.com"
+GIT_COMMITTER_NAME="Co M Miter"
+GIT_COMMITTER_EMAIL="c.miter@example.com"
+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL
+yet_another="Max Min <mm@example.com>"
+
+cat >todotest1 <<EOF
+patch patchB      # write 1 into file1
+patch -k patchC   # generate file2
+patch patchD.raw  # write 2 into file1
+EOF
+
+echo 'write 1 into file1' >expected1
+echo '[PATCH] generate empty file2' >expected2
+echo 'echo 2 >file1' >expected3
+
+test_expect_success '"patch" insn works' '
+	git checkout A &&
+	next_session commitmsg &&
+	git sequencer todotest1 &&
+	! test -d "$SEQDIR" &&
+	session_ok &&
+	test "$(git rev-parse HEAD~3)" = "$(git rev-parse A)" &&
+	test "$(git show HEAD~2:file1)" = "1" &&
+	test -z "$(git show HEAD^:file2)" &&
+	test "$(git show HEAD:file1)" = "2" &&
+	expect_author HEAD~2 "$orig_author" &&
+	expect_author HEAD~1 "$orig_author" &&
+	expect_author HEAD~0 "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" &&
+	expect_msg HEAD~2 expected1 &&
+	expect_msg HEAD~1 expected2 &&
+	expect_msg HEAD~0 expected3
+'
+
+cat >todotest1 <<EOF
+pick B	# write 1 into file1
+pause
+pick C	# generate file2
+EOF
+
+echo 'generate empty file2' >expected1
+echo 'write 1 into file1' >expected2
+
+test_expect_success "pick ; pause insns and --continue works" '
+	git checkout A &&
+	expect_continue git sequencer todotest1 &&
+	session_ok &&
+	echo 5 >file1 &&
+	git add file1 &&
+	next_session nochange &&
+	git sequencer --continue &&
+	test "$(git show HEAD:file1)" = 5 &&
+	test -z "$(git show HEAD:file2)" &&
+	expect_msg HEAD expected1 &&
+	expect_msg HEAD^ expected2 &&
+	session_ok
+'
+
+cat >todotest1 <<EOF
+edit B	# write 1 into file1
+pick C	# generate file2
+EOF
+
+test_expect_success "edit insn and --continue works" '
+	git checkout A &&
+	expect_continue git sequencer todotest1 &&
+	session_ok &&
+	echo 5 >file1 &&
+	git add file1 &&
+	next_session nochange &&
+	git sequencer --continue &&
+	test "$(git show HEAD:file1)" = 5 &&
+	test -z "$(git show HEAD:file2)" &&
+	expect_msg HEAD expected1 &&
+	expect_msg HEAD^ expected2 &&
+	session_ok
+'
+
+cat >todotest1 <<EOF
+patch patchB	# write 1 into file1
+pick H		# generate file5
+mark :1
+patch patchC	# generate file2
+squash I	# generate file6
+patch patchD	# write 2 into file1
+ref refs/tags/CID
+mark :2
+reset :1	# reset to new H
+patch patchD	# write 2 into file1
+squash CE	# generate file2 and file3
+ref refs/tags/DCE
+merge :2	# merge :2 into HEAD
+patch patchF	# generate file4
+EOF
+
+test_expect_success 'all insns work without options' '
+	git checkout A &&
+	next_session squashCI &&
+	no_marks_set &&
+	git sequencer todotest1 &&
+	no_marks_set &&
+	test "$(git show HEAD:file1)" = "2" &&
+	test -z "$(git show HEAD:file2)" &&
+	test -z "$(git show HEAD:file3)" &&
+	test -z "$(git show HEAD:file4)" &&
+	test -z "$(git show HEAD:file5)" &&
+	test -z "$(git show HEAD:file6)" &&
+	echo "$(git rev-parse DCE)" >expected &&
+	echo "$(git rev-parse CID)" >>expected &&
+	git cat-file commit HEAD^ | sed -n -e "s/^parent //p" >actual &&
+	test_cmp expected actual &&
+	session_ok
+'
+
+cat >todotest1 <<EOF
+merge --standard DCE
+EOF
+
+echo "Merge DCE into HEAD" >expected1
+
+test_expect_success 'merge --standard works' '
+	git checkout CID &&
+	git sequencer todotest1 &&
+	expect_msg HEAD expected1 &&
+	session_ok
+'
+
+cat >todotest1 <<EOF
+merge --standard --message="foo" DCE
+EOF
+
+
+test_expect_success 'merge --standard --message="foo" is conflict' '
+	git checkout CID &&
+	expect_conflict git sequencer todotest1 &&
+	git sequencer --abort &&
+	session_ok
+'
+
+for command in 'pick ' 'patch patch' 'squash ' 'merge --standard '
+do
+	cat >todotest1 <<EOF
+patch patchB	# 1 into file1
+${command}G	# 3 into file1
+patch -3 patchF	# empty file4
+EOF
+
+	test_expect_success "conflict test: ${command%% *} and --abort" '
+		git checkout A &&
+		expect_conflict git sequencer todotest1 &&
+		session_ok &&
+		test -d "$SEQDIR" &&
+		git sequencer --abort &&
+		session_ok &&
+		test $(git rev-parse HEAD) = $(git rev-parse A)
+	'
+
+	test_expect_success "conflict test: ${command%% *} and --continue" '
+		git checkout A &&
+		expect_conflict git sequencer todotest1 &&
+		session_ok &&
+		test -d "$SEQDIR" &&
+		## XXX: It would be perfect if we could remove the if
+		{ if test "${command%% *}" != "patch"
+		then grep "^<<<<<<<" file1 ; fi } &&
+		echo 3 >file1 &&
+		git add file1 &&
+		next_session nochange &&
+		git sequencer --continue &&
+		session_ok &&
+		! test -d "$SEQDIR" &&
+		test "$(git show HEAD:file1)" = "3" &&
+		test -f file4
+	'
+
+	test_expect_success "conflict test: ${command%% *} and --skip" '
+		git checkout A &&
+		expect_conflict git sequencer todotest1 &&
+		session_ok &&
+		test -d "$SEQDIR" &&
+		git sequencer --skip &&
+		session_ok &&
+		! test -d "$SEQDIR" &&
+		test "$(git show HEAD:file1)" = "1" &&
+		test -f file4
+	'
+done
+
+echo 'file5-gen' >commitmsg
+
+cat >todotest1 <<EOF
+patch --signoff patchB
+pause
+pick --author="$yet_another" --file="commitmsg" --signoff H
+mark :1
+patch --message="file2-gen" patchC
+squash --signoff --author="$yet_another" I
+pause
+patch --message="echo 2 >file1" patchD
+mark :2
+reset :1
+patch --author="$yet_another" patchD
+squash --signoff --message="generate file[23]" CE
+merge --signoff --message="test merge" --author="$yet_another" :2
+pause
+ref refs/tags/a_merge
+patch --message="Generate file4 and write 23 into it" patchF.raw
+pause
+pick I
+EOF
+
+cat >expected1 <<EOF
+write 1 into file
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+test_expect_success 'insns work with options and another author 1' '
+	git checkout A &&
+	no_marks_set &&
+
+	# patch --signoff patchB  # write 1 into file1
+	# pause
+	expect_continue git sequencer todotest1 &&
+	test "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" = \
+		"$(git cat-file commit HEAD | grep "^Signed-off-by")" &&
+	expect_author HEAD "$orig_author" &&
+	test -d "$SEQDIR" &&
+	session_ok
+'
+
+cat >expected1 <<EOF
+file5-gen
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+cat >expected2 <<EOF
+file2-gen
+
+generate empty file6
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+test_expect_success 'insns work with options and another author 2' '
+	: >file7 &&
+	git add file7 &&
+	next_session nochange &&
+	git commit --amend &&
+	session_ok &&
+
+	next_session nochange &&
+	expect_continue git sequencer --continue &&
+	session_ok &&
+
+	# amended commit
+	test "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" = \
+		"$(git cat-file commit HEAD^^ | grep "^Signed-off-by")" &&
+	expect_author HEAD^^ "$orig_author" &&
+	test -z "$(git show HEAD:file7)" &&
+
+	# pick --author="$yet_another" --file="commitmsg" --signoff H
+	expect_author HEAD^ "$yet_another" &&
+	expect_msg HEAD^ expected1 &&
+	test -z "$(git show HEAD:file5)" &&
+
+	# mark :1
+	test "$(git rev-parse "$SEQMARK/1")" = "$(git rev-parse HEAD^)" &&
+
+	# patch --message="file2-gen" patchC
+	# squash --signoff --author="$yet_another" I # generate file6
+	# pause
+	test -z "$(git show HEAD:file2)" &&
+	test -z "$(git show HEAD:file6)" &&
+	git ls-files | grep "^file2" &&
+	git ls-files | grep "^file6" &&
+	expect_msg HEAD expected2 &&
+	expect_author HEAD "$yet_another"
+'
+
+echo 'echo 2 >file1' >expected1
+
+cat >expected2 <<EOF
+generate file[23]
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+cat >expected3 <<EOF
+test merge
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+cat >expected4 <<EOF
+file1
+file2
+file3
+file5
+file6
+file7
+EOF
+
+test_expect_success 'insns work with options and another author 3' '
+	# do not change anything
+	expect_continue git sequencer --continue &&
+	session_ok &&
+
+	# patch --message="echo 2 >file1" patchD
+	# mark :2
+	commit="$(git rev-parse --verify "$SEQMARK/2")" &&
+	expect_author "$commit" "$orig_author" &&
+	expect_msg "$commit" expected1 &&
+
+	# reset :1
+	# patch --author="$yet_another" patchD # write 2 into file1
+	# squash --signoff --message="generate file[23]" CE
+	expect_author HEAD^ "$yet_another" &&
+	expect_msg HEAD^ expected2 &&
+
+	# merge --signoff --message="test merge" --author="$yet_another" :2
+	# pause
+	expect_author HEAD "$yet_another" &&
+	expect_msg HEAD expected3 &&
+	git ls-files >actual &&
+	test_cmp expected4 actual
+'
+
+cat >expected_merge <<EOF
+cleanup merge
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+echo 'Generate file4 and write 23 into it' >expected2
+
+test_expect_success 'insns work with options and another author 4' '
+	git rm file5 file6 file7 &&
+	next_session merge2 &&
+	expect_continue git sequencer --continue &&
+	session_ok &&
+
+	# ref refs/tags/a_merge
+	expect_author a_merge "$yet_another" &&
+	expect_msg a_merge expected_merge &&
+
+	# patch --message="Generate file4 and write 23 into it" patchF.raw
+	# pause
+	git ls-files | grep "^file4" &&
+	echo 23 >file4 &&
+	git add file4 &&
+	next_session nochange &&
+	git sequencer --continue &&
+	session_ok &&
+	no_marks_set &&
+	test "$(git show HEAD:file1)" = "2" &&
+	test "$(git show HEAD:file4)" = "23" &&
+	expect_msg HEAD^ expected2 &&
+	expect_author HEAD^ "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" &&
+
+	# pick I
+	test -z "$(git show HEAD:file6)" &&
+	git ls-files | grep "^file6" &&
+	session_ok
+'
+
+# almost the same to test --quiet
+cat >todotest1 <<EOF
+patch patchB
+pick H
+mark :1
+patch patchC
+squash --message="a squash" I
+patch patchD
+mark :2
+reset :1
+patch patchD
+squash --message="another squash" CE
+merge --message="test merge" :2
+pause
+patch patchF
+EOF
+
+test_expect_failure '--quiet works' '
+	git checkout A &&
+	expect_continue git sequencer --quiet todotest1 >actual &&
+	session_ok &&
+	! test -s actual
+'
+
+test_expect_failure '--quiet works on continue' '
+	git sequencer --continue >>actual &&
+	session_ok &&
+	! test -s actual
+'
+
+echo 'merge --strategy=ours --reuse-commit=a_merge branch1 branch2 CE CID' >todotest1
+
+test_expect_success 'merge multiple branches and --reuse-commit works' '
+	git checkout -b merge-multiple master &&
+	git sequencer todotest1 &&
+	session_ok &&
+	expect_msg HEAD expected_merge &&
+	git rev-parse HEAD^ >expected &&
+	git rev-parse branch1 >>expected &&
+	git rev-parse branch2 >>expected &&
+	git rev-parse CE >>expected &&
+	git rev-parse CID >>expected &&
+	git cat-file commit HEAD | sed -n -e "s/^parent //p" >actual &&
+	test_cmp expected actual &&
+	! test -f file6
+'
+
+echo 'pick --mainline=5 merge-multiple' >todotest1
+
+test_expect_success 'pick --mainline works' '
+	git checkout -b mainline CID &&
+	git sequencer todotest1 &&
+	session_ok &&
+	expect_msg HEAD expected_merge &&
+	! test -f file6 &&
+	test -f file3 &&
+	test -f file2 &&
+	test "$(git show HEAD:file1)" = 2
+'
+
+cat >todotest1 <<EOF
+pick C		# file2
+mark :1
+patch patchB	# write 1 into file1
+patch patchD	# write 2 into file1
+pick I		# file6
+squash --message="2 in file1 and file6 exists" --signoff --from :1
+EOF
+
+cat >expected1 <<EOF
+2 in file1 and file6 exists
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+test_expect_success 'squash --from works' '
+	git checkout A &&
+	git sequencer <todotest1 &&
+	session_ok &&
+	test "$(git rev-parse A)" = "$(git rev-parse HEAD~2)" &&
+	test "$(git show HEAD:file1)" = "2" &&
+	test -z "$(git show HEAD:file6)" &&
+	expect_msg HEAD expected1
+'
+
+cat >todotest1 <<EOF
+patch patchB	# write 1 into file1
+pick H		# generate file5
+mark :1
+patch patchC	# generate file2
+squash --message="file5" I	# generate file6
+patch patchD	# write 2 into file1
+mark :2
+reset :1	# reset to new H
+patch patchD	# write 2 into file1
+squash --message="CE" CE	# generate file2 and file3
+merge --standard :2	# merge :2 into HEAD
+patch patchF	# generate file4
+EOF
+cp todotest1 todotest2
+cat todotest1 | sed -e 's/^\(patch\|pick\|squash\|merge\) /&--edit /' >todotest3
+echo 'squash --message="doesnt work either" --from :1' >>todotest1
+echo 'squash --include-merges --message="stupid" --from :1' >>todotest2
+
+test_expect_success 'squash --from conflicts with merge in between' '
+	git checkout A &&
+	expect_conflict git sequencer todotest1 &&
+	git sequencer --abort &&
+	session_ok &&
+	! test -d "$SEQDIR"
+'
+
+test_expect_success 'squash --include-merges --from succeeds with merge in between' '
+	git checkout A &&
+	git sequencer todotest2 &&
+	session_ok &&
+	test "$(git rev-parse HEAD~3)" = "$(git rev-parse A)"
+'
+
+test_expect_success 'patch|pick|squash|merge --edit works' '
+	git checkout A &&
+	next_session editX &&
+	git sequencer todotest3 &&
+	session_ok
+'
+
+cat >todotest1 <<EOF
+patch patchB
+pause
+EOF
+
+test_expect_success 'batch mode fails on pause insn' '
+	git checkout A &&
+	expect_fail git sequencer --batch todotest1 &&
+	session_ok &&
+	! test -d "$SEQDIR"
+'
+
+cat >todotest1 <<EOF
+patch patchB
+pick G
+EOF
+
+test_expect_success 'batch mode fails on conflict' '
+	git checkout A &&
+	expect_fail git sequencer --batch <todotest1 &&
+	session_ok &&
+	! test -d "$SEQDIR" &&
+	test -z "$(git show HEAD:file1)"
+'
+
+cat >todotest1 <<EOF
+patch patchB
+pause
+EOF
+
+test_expect_success '--caller works' '
+	git checkout A &&
+	expect_continue git sequencer \
+		--caller="this works|abrt||skip" todotest1 &&
+	expect_fail git sequencer --abort &&
+	expect_fail git sequencer --skip &&
+	git sequencer --continue &&
+	session_ok
+'
+
+test_done
-- 
1.6.0.rc0.49.gd39f

^ permalink raw reply related

* [PATCH 1/5] Add git-sequencer shell prototype
From: Stephan Beyer @ 2008-07-26  5:20 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Daniel Barkalow, Stephan Beyer
In-Reply-To: <1217049644-8874-1-git-send-email-s-beyer@gmx.net>

git-sequencer is planned as a backend for user scripts
that execute a sequence of git instructions and perhaps
need manual intervention, for example git-rebase or git-am.

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 .gitignore       |    1 +
 Makefile         |    1 +
 command-list.txt |    1 +
 git-sequencer.sh | 2042 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 2045 insertions(+), 0 deletions(-)
 create mode 100755 git-sequencer.sh

diff --git a/.gitignore b/.gitignore
index a213e8e..a617039 100644
--- a/.gitignore
+++ b/.gitignore
@@ -110,6 +110,7 @@ git-revert
 git-rm
 git-send-email
 git-send-pack
+git-sequencer
 git-sh-setup
 git-shell
 git-shortlog
diff --git a/Makefile b/Makefile
index b01cf1c..08facb1 100644
--- a/Makefile
+++ b/Makefile
@@ -248,6 +248,7 @@ SCRIPT_SH += git-rebase--interactive.sh
 SCRIPT_SH += git-rebase.sh
 SCRIPT_SH += git-repack.sh
 SCRIPT_SH += git-request-pull.sh
+SCRIPT_SH += git-sequencer.sh
 SCRIPT_SH += git-sh-setup.sh
 SCRIPT_SH += git-stash.sh
 SCRIPT_SH += git-submodule.sh
diff --git a/command-list.txt b/command-list.txt
index 3583a33..44bb5b0 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -101,6 +101,7 @@ git-rev-parse                           ancillaryinterrogators
 git-rm                                  mainporcelain common
 git-send-email                          foreignscminterface
 git-send-pack                           synchingrepositories
+git-sequencer                           plumbingmanipulators
 git-shell                               synchelpers
 git-shortlog                            mainporcelain
 git-show                                mainporcelain common
diff --git a/git-sequencer.sh b/git-sequencer.sh
new file mode 100755
index 0000000..2c14af9
--- /dev/null
+++ b/git-sequencer.sh
@@ -0,0 +1,2042 @@
+#!/bin/sh
+# A git sequencer prototype.
+
+SUBDIRECTORY_OK=Yes
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git sequencer [options] [--] [<file>]
+git sequencer (--continue | --abort | --skip | --edit | --status)
+--
+ Options to start a sequencing process
+allow-dirty    run even if working tree is dirty
+B,batch        run in batch-mode
+onto=          checkout the given commit or branch first
+no-advice      suppress advice when pausing (conflicts, etc)
+q,quiet        suppress output
+v,verbose      be more verbose
+ Options to restart/change a sequencing process or show information
+continue       continue paused sequencer process
+abort          restore original branch and abort
+skip           skip current patch and continue
+status         show the status of the sequencing process
+edit           invoke editor to let user edit the remaining insns
+ Options to be used by user scripts
+caller=        provide information string: name|abort|cont|skip
+"
+
+. git-sh-setup
+require_work_tree
+
+git var GIT_COMMITTER_IDENT >/dev/null ||
+	die 'You need to set your committer info first'
+
+SEQ_DIR="$GIT_DIR/sequencer"
+TODO="$SEQ_DIR/todo"
+DONE="$SEQ_DIR/done"
+MSG="$SEQ_DIR/message"
+PATCH="$SEQ_DIR/patch"
+AUTHOR_SCRIPT="$SEQ_DIR/author-script"
+ORIG_AUTHOR_SCRIPT="$SEQ_DIR/author-script.orig"
+CALLER_SCRIPT="$SEQ_DIR/caller-script"
+WHY_FILE="$SEQ_DIR/why"
+MARK_PREFIX='refs/sequencer-marks'
+
+warn () {
+	printf "%s\n" "$*" >&2
+}
+
+cleanup () {
+	for ref in $(git for-each-ref --format='%(refname)' "$MARK_PREFIX")
+	do
+		git update-ref -d "$ref" "$ref" || return
+	done
+	test -z "$ORIG_HEAD" || git update-ref ORIG_HEAD "$ORIG_HEAD"
+	rm -rf "$SEQ_DIR"
+}
+
+print_advice () {
+	case "$ADVICE,$WHY" in
+	t,conflict)
+		echo "
+After resolving the conflicts, mark the corrected paths with
+
+	git add <paths>
+
+and run
+
+	$(print_caller --continue)
+
+Note, that your working tree must match the index."
+		;;
+	t,pause)
+		echo "
+You can now edit files and add them to the index.
+Once you are satisfied with your changes, run
+
+	$(print_caller --continue)
+
+If you only want to change the commit message, run
+git commit --amend before."
+		;;
+	t,run)
+		echo "
+Running failed:
+	$@
+
+You can now fix the problem.  When manual runs pass,
+add the changes to the index and invoke
+
+	$(print_caller --continue)"
+		;;
+	t,todo)
+		echo "
+Fix with git sequencer --edit or abort with $(print_caller --abort)."
+		;;
+	esac
+}
+
+# Die if there has been a conflict
+die_to_continue () {
+	warn "$*"
+	test -z "$BATCHMODE" ||
+		die_abort 'Aborting, because of batch mode.'
+	test -n "$WHY" || WHY=conflict
+	echo "$WHY" >"$WHY_FILE"
+	print_advice
+	test -z "$ORIG_HEAD" || git update-ref ORIG_HEAD "$ORIG_HEAD"
+	exit 3
+}
+
+die_abort () {
+	restore
+	cleanup
+	die "$1"
+}
+
+perform () {
+	case "$VERBOSE" in
+	0)
+		"$@" >/dev/null
+		;;
+	1)
+		output=$("$@" 2>&1 )
+		status=$?
+		test $status -ne 0 && printf '%s\n' "$output"
+		return $status
+		;;
+	2)
+		"$@"
+		;;
+	esac
+}
+
+# test if working tree is dirty
+require_clean_work_tree () {
+	if test -z "$ALLOW_DIRTY"
+	then
+		git rev-parse --verify HEAD >/dev/null &&
+		git update-index --ignore-submodules --refresh &&
+		git diff-files --quiet --ignore-submodules ||
+			die 'Working tree is dirty.'
+	fi
+	git diff-index --cached --quiet HEAD --ignore-submodules -- ||
+		die 'Index is dirty'
+}
+
+ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
+
+comment_for_reflog () {
+	if test -z "$ORIG_REFLOG_ACTION"
+	then
+		GIT_REFLOG_ACTION='sequencer'
+		test -z "$CALLER" ||
+			GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION ($CALLER)"
+		export GIT_REFLOG_ACTION
+	fi
+}
+
+# Get commit message from commit $1
+commit_message () {
+	git cat-file commit "$1" | sed -e '1,/^$/d'
+}
+
+LAST_COUNT=
+remove_from_todo () {
+	sed -e 1q <"$TODO" >>"$DONE"
+	sed -e 1d <"$TODO" >"$TODO.new"
+	mv -f "$TODO.new" "$TODO"
+	todo_ack "$TODO"
+	if test "$VERBOSE" -gt 0
+	then
+		count=$(grep -c '^[^#]' <"$DONE")
+		total=$(expr "$count" + "$(grep -c '^[^#]' <"$TODO")")
+		if test "$LAST_COUNT" != "$count"
+		then
+			LAST_COUNT="$count"
+			test "$VERBOSE" -eq 1 -a -t 1 -o "$VERBOSE" -gt 1 &&
+				printf 'Sequencing (%d/%d)\r' "$count" "$total"
+			test "$VERBOSE" -lt 2 || echo
+		fi
+	fi
+}
+
+# Generate message and author script files
+save_commit_data () {
+	test -f "$MSG" ||
+		commit_message "$1" >"$MSG"
+	test -f "$AUTHOR_SCRIPT" ||
+		get_author_ident_from_commit "$1" >"$AUTHOR_SCRIPT"
+}
+
+# Generate a patch and die with "conflict" status code
+die_with_patch () {
+	save_commit_data "$1"
+	perform git rerere
+	die_to_continue "$2"
+}
+
+reset_almost_hard () {
+	perform git read-tree --reset -u $ALLOW_DIRTY "$1" &&
+	perform git reset "$1"
+}
+
+restore () {
+	# XXX: We should undo all "ref" invocations, but that's left to be
+	# done in the builtin-sequencer.
+	read HEADNAME <"$SEQ_DIR/head-name"
+	case $HEADNAME in
+	refs/*)
+		git symbolic-ref HEAD "$HEADNAME"
+		;;
+	esac &&
+	reset_almost_hard "$ORIG_HEAD"
+}
+
+has_action () {
+	grep '^[^#]' "$1" >/dev/null
+}
+
+# Check if text file $1 contains a commit message
+has_message () {
+	test -n "$(sed -n -e '/^Signed-off-by:/d;/^[^#]/p' <"$1")"
+}
+
+# Count parents of commit $1
+count_parents() {
+	git cat-file commit "$1" | sed -n -e '1,/^$/p' | grep -c '^parent'
+}
+
+# Evaluate the author script to get author information to
+# apply it with "with_author git foo" then.
+get_current_author () {
+	if test -f "$AUTHOR_SCRIPT"
+	then
+		. "$AUTHOR_SCRIPT"
+	else
+		. "$ORIG_AUTHOR_SCRIPT"
+	fi || die_abort 'Author script is damaged. This must not happen!'
+}
+
+# Run command with author information
+with_author () {
+	GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
+	GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
+	GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
+		"$@"
+}
+
+# clean WHY_FILE and reset WHY
+clean_why () {
+	rm -f "$WHY_FILE"
+	WHY=
+}
+
+# Usage: pick_one (cherry-pick|revert) [-*|--edit] sha1
+pick_one () {
+	what="$1"
+	shift
+
+	case "$what,$1" in
+	revert,*)
+		test "$1" != '--edit' &&
+			what='revert --no-edit'
+		;;
+	cherry-pick,-*)
+		;;
+	cherry-pick,*)
+		# fast forward
+		if test "$(git rev-parse --verify "$1^" 2>/dev/null)" = \
+			"$(git rev-parse --verify HEAD)"
+		then
+			reset_almost_hard "$1"
+			return
+		fi
+		;;
+	esac
+	$use_perform git $what "$@"
+}
+
+nth_string () {
+	case "$1" in
+	*1[0-9]|*[04-9])
+		echo "$1th"
+		;;
+	*1)
+		echo "$1st"
+		;;
+	*2)
+		echo "$1nd"
+		;;
+	*3)
+		echo "$1rd"
+		;;
+	esac
+}
+
+make_squash_message () {
+	if test -f "$squash_msg"
+	then
+		count=$(($(sed -n -e 's/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p' \
+			<"$squash_msg" | sed -n -e '$p')+1))
+		echo "# This is a combination of $count commits."
+		sed -e '1d' -e '2,/^./{
+			/^$/d
+		}' <"$squash_msg"
+	else
+		count=2
+		echo '# This is a combination of 2 commits.'
+		echo '# The first commit message is:'
+		echo
+		commit_message HEAD
+	fi
+	echo
+	echo "# This is the $(nth_string "$count") commit message:"
+	echo
+	commit_message "$1"
+}
+
+make_squash_message_multiple () {
+	revlist=$(git rev-list --reverse "$sha1..HEAD")
+	count=$(echo "$revlist" | wc -l)
+	squash_i=0
+	echo "# This is a combination of $count commits."
+	for cur_sha1 in $revlist
+	do
+		squash_i=$(($squash_i+1))
+		if test -f "$squash_msg"
+		then
+			count=$(($count + $(sed -n -e \
+				's/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p' \
+				<"$squash_msg" | sed -n -e '$p')+1))
+			sed -e '1d' -e '2,/^./{
+				/^$/d
+			}' <"$squash_msg"
+		fi
+		echo
+		echo "# This is the $(nth_string "$squash_i") commit message:"
+		echo
+		commit_message "$cur_sha1"
+	done
+}
+
+peek_next_command () {
+	sed -n -e '/^#/d' -e '1s/ .*$//p' <"$TODO"
+}
+
+# If $1 is a mark, make a ref from it; otherwise keep it.
+# Note on marks:
+#  * :0 is allowed
+#  * :01 is the same as :1
+mark_to_ref () {
+	arg="$1"
+	ref=$(expr "x$arg" : 'x:0*\([0-9][0-9]*\)$')
+	test -n "$ref" &&
+		arg="$MARK_PREFIX/$ref"
+	printf '%s\n' "$arg"
+}
+
+mark_to_commit () {
+	git rev-parse --verify "$(mark_to_ref "$1")"
+}
+
+
+fallback_3way () {
+	# Cleanup if not done before.
+	rm -fr "$PATCH-merge-"*
+
+	# First see if the patch records the index info that we can use.
+	perform git apply --build-fake-ancestor "$PATCH-merge-index" "$PATCH" &&
+	GIT_INDEX_FILE="$PATCH-merge-index" git write-tree >"$PATCH-merge-base" ||
+		die_to_continue 'Repository lacks necessary blobs to fall back on 3-way merge. Please hand-edit.'
+
+	test "$VERBOSE" -eq 0 ||
+		echo 'Using index info to reconstruct a base tree...'
+	GIT_INDEX_FILE="$PATCH-merge-index" git apply --cached "$PATCH" ||
+		die_to_continue 'Patch does not apply to blobs recorded in its index.'
+
+	his_tree=$(GIT_INDEX_FILE="$PATCH-merge-index" git write-tree) &&
+	orig_tree=$(cat "$PATCH-merge-base") &&
+	rm -fr "$PATCH-merge-"* ||
+		die_to_continue 'Writing new tree failed.'
+
+	test "$VERBOSE" -eq 0 ||
+		echo 'Falling back to patching base and 3-way merge...'
+
+	# This is not so wrong.  Depending on which base we picked,
+	# orig_tree may be wildly different from ours, but his_tree
+	# has the same set of wildly different changes in parts the
+	# patch did not touch, so recursive ends up canceling them,
+	# saying that we reverted all those changes.
+
+	eval GITHEAD_$his_tree='"$firstline"'
+	export GITHEAD_$his_tree
+	perform git merge-recursive "$orig_tree" -- HEAD "$his_tree" || {
+		perform git rerere
+		die_to_continue 'Failed to merge in the changes.'
+	}
+}
+
+# Run hook "$@" (with arguments) if executable
+run_hook () {
+	test -z "$1" || return
+	hookname="$1"
+	hook="$GIT_DIR/hooks/$hookname"
+	shift
+	if test -x "$hook"
+	then
+		"$hook" "$@" ||
+			die_to_continue "Hook $hookname failed."
+	fi
+}
+
+# Add Signed-off-by: line if general option --signoff is given
+dashdash_signoff () {
+	add_signoff=
+	if test -n "$SIGNOFF"
+	then
+		last_signed_off_by=$(
+			sed -n -e '/^Signed-off-by: /p' <"$MSG" | sed -n -e '$p'
+		)
+		test "$last_signed_off_by" = "$SIGNOFF" ||
+			add_signoff=$(
+				test '' = "$last_signed_off_by" && echo
+				echo "$SIGNOFF"
+			)
+	fi
+	{
+		test -s "$MSG" && cat "$MSG"
+		test -n "$add_signoff" && echo "$add_signoff"
+	} >"$MSG.new"
+	mv "$MSG.new" "$MSG"
+}
+
+
+### --caller-related functions
+
+# Show string for caller invocation for --abort/--continue/--skip
+print_caller_info () {
+	case "$1" in
+	--abort)
+		echo "$CALLER_ABRT"
+		;;
+	--continue)
+		echo "$CALLER_CONT"
+		;;
+	--skip)
+		echo "$CALLER_SKIP"
+		;;
+	*)
+		warn 'Internal error: Unknown print_caller argument!'
+		;;
+	esac
+}
+
+# Print the program to invoke to (--)abort/continue/skip
+print_caller() {
+	caller_info=$(print_caller_info "$1")
+	if test -n "$CALLERCOMPARE" -a -n "$caller_info"
+	then
+		test -n "$CALLER" && printf "$CALLER "
+		echo "$caller_info"
+	else
+		echo "git sequencer $1"
+	fi
+}
+
+# Test if --caller was set correctly
+# $1 must be abort/continue/skip
+test_caller () {
+	caller_info=$(print_caller_info "--$1")
+	test -n "$CALLERCOMPARE" -a \
+		"$CALLERCOMPARE" != "$CALLERSTRING" -a \
+		-n "$caller_info" &&
+		die "You must use '$CALLER $caller_info' to $1!"
+}
+
+# Generate $CALLER_SCRIPT file from "git foo|--abort|--continue|--skip"
+# string $1
+generate_caller_script () {
+	echo "$1" | sed -e 's/^\(.*\)|\(.*\)|\(.*\)|\(.*\)$/\
+CALLERCOMPARE="\0"\
+CALLER="\1"\
+CALLER_ABRT="\2"\
+CALLER_CONT="\3"\
+CALLER_SKIP="\4"/' >"$CALLER_SCRIPT"
+}
+
+# Run caller script and set GIT_CHERRY_PICK_HELP
+assure_caller () {
+	test -r "$CALLER_SCRIPT" &&
+		. "$CALLER_SCRIPT"
+
+	# we do not want cherry-pick to print *any* help
+	GIT_CHERRY_PICK_HELP=""
+	export GIT_CHERRY_PICK_HELP
+}
+
+
+### Helpers for check_* functions:
+
+# Print a warning on todo checking
+todo_warn () {
+	printf 'Warning at line %d, %s: %s\n' "$line" "$command" "$*"
+}
+
+# Raise an error on todo checking
+todo_error () {
+	printf 'Error at line %d, %s: %s\n' "$line" "$command" "$*"
+	retval=1
+}
+
+# Test if $1 is a commit on todo checking
+commit_check () {
+	test "$(git cat-file -t "$1" 2>/dev/null)" = commit ||
+		todo_error "'$1' is not a commit."
+}
+
+# A helper function to check if $1 is a an available mark during check_*
+arg_is_mark_check () {
+	for cur_mark in $available_marks
+	do
+		test "$cur_mark" -eq "${1#:}" && return
+	done
+	todo_error "Mark $1 is not yet defined."
+}
+
+# A helper function for check_* and mark usage:
+# check if "$1" is a commit or a defined mark
+arg_is_mark_or_commit_check () {
+	if expr "x$1" : 'x:[0-9][0-9]*$' >/dev/null
+	then
+		arg_is_mark_check "$1"
+	else
+		commit_check "$1"
+	fi
+}
+
+
+### Author script functions
+
+# Take "Name <e-mail>" in stdin and outputs author script
+make_author_script_from_string () {
+	sed -e "s/'/'"'\\'"''/g" \
+	    -e 's/^\(.*\) <\(.*\)>.*$/GIT_AUTHOR_NAME='\''\1'\''\
+GIT_AUTHOR_EMAIL='\''\2'\''\
+GIT_AUTHOR_DATE=/'
+}
+
+
+### General option functions (and options spec)
+
+OPTIONS_GENERAL=' General options
+author= override author
+C,reuse-commit= reuse message and authorship data from commit
+F,file= take commit message from given file
+m,message= specify commit message
+M,reuse-message= reuse message from commit
+signoff add signoff
+e,edit invoke editor to edit commit message'
+
+# Check if option is a general option
+check_general_option () {
+	general_shift=1
+	case "$1" in
+	--signoff)
+		return 0
+		;;
+	--author)
+		general_shift=2
+		author_opt="t$author_opt"
+		expr "x$2" : 'x.* <.*>' >/dev/null ||
+			todo_error "Author \"$2\" not in the correct format \"Name <e-mail>\"."
+		;;
+	-m)
+		general_shift=2
+		msg_opt="t$msg_opt"
+		;;
+	-C)
+		general_shift=2
+		msg_opt="t$msg_opt"
+		author_opt="t$author_opt"
+		commit_check "$2"
+		;;
+	-M)
+		general_shift=2
+		msg_opt="t$msg_opt"
+		commit_check "$2"
+		;;
+	-F)
+		general_shift=2
+		msg_opt="t$msg_opt"
+		test -r "$2" ||
+			todo_error "Cannot read file '$2'."
+		;;
+	-e)
+		test -z "$BATCHMODE" ||
+			todo_error '--batch and --edit options do not make sense together.'
+		;;
+	*)
+		return 1
+		;;
+	esac
+}
+
+# Set a general option variable or return 1
+handle_general_option () {
+	general_shift=1
+	case "$1" in
+	--signoff)
+		SIGNOFF=$(git var GIT_COMMITTER_IDENT | sed -e '
+				s/>.*/>/
+				s/^/Signed-off-by: /')
+		;;
+	--author)
+		general_shift=2
+		AUTHOR=t
+		echo "$2" |
+			make_author_script_from_string >"$AUTHOR_SCRIPT"
+		;;
+	-m)
+		general_shift=2
+		MESSAGE="$2"
+		;;
+	-C)
+		general_shift=2
+		AUTHOR=t
+		get_author_ident_from_commit "$2" >"$AUTHOR_SCRIPT"
+		MESSAGE=$(commit_message "$2")
+		;;
+	-M)
+		general_shift=2
+		MESSAGE=$(commit_message "$2")
+		;;
+	-F)
+		general_shift=2
+		MESSAGE=$(cat "$2")
+		;;
+	-e)
+		EDIT=--edit
+		;;
+	*)
+		return 1
+		;;
+	esac
+}
+
+
+### Functions for checking and realizing TODO instructions
+# Note that options_*, check_* and insn_* function names are reserved.
+
+options_pause="\
+pause
+--
+"
+
+# Check the "pause" instruction
+check_pause () {
+	shift
+	test -z "$BATCHMODE" ||
+		todo_error '"pause" instruction and --batch do not make sense together.'
+	test $# -eq 0 ||
+		todo_error 'The pause instruction takes no arguments.'
+	return 0
+}
+
+# Realize the "pause" instruction
+insn_pause () {
+	save_commit_data HEAD
+	echo 'pause' >"$WHY_FILE"
+	WHY=pause print_advice
+	test -z "$ORIG_HEAD" || git update-ref ORIG_HEAD "$ORIG_HEAD"
+	exit 2
+}
+
+
+options_run="\
+run [--dir=<path>] [--] <cmd> <args>...
+--
+dir= change directory before running command
+"
+
+# Check the "run" instruction
+check_run () {
+	while test $# -gt 0
+	do
+		case "$1" in
+		--dir)
+			test -e "$2" ||
+				todo_error "Path $2 does not exist."
+			test -d "$2" ||
+				todo_error "Path $2 is not a directory."
+			shift 2
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			todo_error "Unknown option $1"
+			;;
+		esac
+	done
+	# we don't check if cmd exists
+	return 0
+}
+
+# Realize the "run" instruction
+insn_run () {
+	runpath=./
+	while test $# -gt 0
+	do
+		case "$1" in
+		--dir)
+			runpath="$2"
+			shift 2
+			;;
+		--)
+			shift
+			break
+			;;
+		esac
+	done
+
+	savepath="$PWD"
+	cd "$runpath"
+	"$@"
+	success="$?"
+	cd "$savepath"
+
+	if test "$success" -ne 0
+	then
+		test -z "$BATCHMODE" ||
+			die_abort "Running $1 failed. Aborting because of batch mode."
+		save_commit_data HEAD
+		echo 'run' >"$WHY_FILE"
+		WHY=run print_advice "$@"
+		test -z "$ORIG_HEAD" || git update-ref ORIG_HEAD "$ORIG_HEAD"
+		exit 3
+	fi
+}
+
+
+options_patch="\
+patch [options] <file>
+--
+3,3way fall back to 3-way merge
+k pass to git-mailinfo (keep subject)
+n pass to git-mailinfo (no utf8)
+$OPTIONS_GENERAL
+ Options passed to git-apply
+R,reverse reverse changes
+context= ensure context of ... lines
+p= remove ... leading slashes
+unidiff-zero bypass unidiff checks
+exclude= do not apply changes to given files
+no-add ignore additions of patch
+whitespace= set whitespace error behavior
+inaccurate-eof support inaccurate EOFs
+u no-op (backward compatibility)
+binary no-op (backward compatibility)
+"
+
+# Check the "patch" instruction
+check_patch () {
+	while test $# -gt 1
+	do
+		case "$1" in
+		-3|-k|-n|-u|--binary|-R|--reverse|--unidiff-zero|--no-add|--inaccurate-eof)
+			:
+			;;
+		-p|--whitespace|--exclude|--context)
+			shift
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			check_general_option "$@" ||
+				todo_warn "Unknown option $1"
+			;;
+		*)
+			todo_error "Wrong number of arguments. ($# given, 1 wanted)"
+			break
+			;;
+		esac
+		shift $general_shift
+	done
+
+	if test -f "$1" -a -r "$1"
+	then
+		grep -e '^diff' "$1" >/dev/null ||
+			todo_error "File '$1' contains no patch."
+	else
+		todo_error "Cannot open file '$1'."
+	fi
+	return 0
+}
+
+# Realize the "patch" instruction
+insn_patch () {
+	apply_opts=
+	mailinfo_opts=
+	threeway=
+
+	# temporary files
+	infofile="$SEQ_DIR/patch-info"
+	msgfile="$SEQ_DIR/patch-msg"
+
+	while test "$#" -gt 1
+	do
+		case "$1" in
+		-3)
+			threeway=t
+			;;
+		-k|-n)
+			mailinfo_opts="$mailinfo_opts $1"
+			;;
+		-u|--binary)
+			: Do nothing. It is there due to b/c only.
+			;;
+		-R|--reverse|--unidiff-zero|--no-add|--inaccurate-eof)
+			apply_opts="$apply_opts $1"
+			;;
+		-p)
+			shift
+			apply_opts="$apply_opts -p$1"
+			;;
+		--whitespace|--exclude)
+			apply_opts="$apply_opts $1=$2"
+			shift
+			;;
+		--context)
+			shift
+			apply_opts="$apply_opts -C$1"
+			;;
+		--)
+			shift
+			break
+			;;
+		*)
+			handle_general_option "$@"
+			;;
+		esac
+		shift $general_shift
+	done
+
+	filename="$1"
+
+	git mailinfo $mailinfo_opts "$msgfile" "$PATCH" \
+		<"$filename" >"$infofile" ||
+		die_abort 'Could not read or parse mail'
+
+	# if author not set by option, read author information of patch
+	if test -z "$AUTHOR"
+	then
+		cp "$ORIG_AUTHOR_SCRIPT" "$AUTHOR_SCRIPT"
+		sed -e "s/'/'"'\\'"''/g" -n -e '
+			s/^Author: \(.*\)$/GIT_AUTHOR_NAME='\''\1'\''/p;
+			s/^Email: \(.*\)$/GIT_AUTHOR_EMAIL='\''\1'\''/p;
+			s/^Date: \(.*\)$/GIT_AUTHOR_DATE='\''\1'\''/p
+		' <"$infofile" >>"$AUTHOR_SCRIPT"
+		# If sed's result is empty, we keep the original
+		# author script by appending.
+	fi
+
+	# Ignore every mail that's not containing a patch
+	test -s "$PATCH" || {
+		warn 'Does not contain patch!'
+		return 0
+	}
+
+	edit_msg=
+	if grep -e '^Subject:' "$infofile" >/dev/null
+	then
+		# add subject to commit message
+		sed -n -e '/^Subject:/ s/Subject: //p' <"$infofile"
+		echo
+		echo
+		cat "$msgfile"
+	else
+		cat "$msgfile"
+		edit_msg=t
+	fi | git stripspace >"$MSG"
+	rm -f "$infofile" "$msgfile"
+
+	firstline=$(sed -e '1q' <"$MSG")
+
+	get_current_author
+
+	test -n "$MESSAGE" && printf '%s\n' "$MESSAGE" >"$MSG"
+	test -z "$firstline" && firstline=$(sed -e '1q' <"$MSG")
+
+	dashdash_signoff
+
+	with_author run_hook applypatch-msg "$MSG"
+	failed=
+	git apply $apply_opts --index "$PATCH" || failed=t
+
+	if test -n "$failed" -a -n "$threeway" && (with_author fallback_3way)
+	then
+		# Applying the patch to an earlier tree and merging the
+		# result may have produced the same tree as ours.
+		git diff-index --quiet --cached HEAD -- && {
+			echo 'No changes -- Patch already applied.'
+			return 0
+			# XXX: do we want that?
+		}
+		# clear apply_status -- we have successfully merged.
+		failed=
+	fi
+
+	if test -n "$failed"
+	then
+		die_to_continue "Patch failed: $firstline"
+		# XXX: We actually needed a git-apply flag that creates
+		# conflict markers and sets the DIFF_STATUS_UNMERGED flag.
+	fi
+
+	with_author run_hook pre-applypatch
+
+	test -n "$EDIT" && edit_msg=t
+	if ! has_message "$MSG" || test -n "$edit_msg"
+	then
+		echo "
+# Please enter the commit message for the applied patch.
+# (Comment lines starting with '#' will not be included)" >>"$MSG"
+
+		git_editor "$MSG" ||
+			die_with_patch HEAD 'Editor returned error.'
+		has_message "$MSG" ||
+			die_with_patch HEAD 'No commit message given.'
+	fi
+
+	tree=$(git write-tree) &&
+	parent=$(git rev-parse --verify HEAD) &&
+	commit=$(with_author git commit-tree "$tree" -p "$parent" <"$MSG") &&
+	git update-ref -m "$GIT_REFLOG_ACTION: $firstline" HEAD "$commit" "$parent" ||
+		die_to_continue 'Could not commit tree.'
+
+	test -x "$GIT_DIR/hooks/post-applypatch" &&
+		with_author "$GIT_DIR/hooks/post-applypatch"
+
+	return 0
+}
+
+
+options_pick="\
+pick [options] <commit>
+--
+R,reverse revert introduced changes
+mainline= specify parent number to use for merge commits
+$OPTIONS_GENERAL
+"
+
+# Check the "pick" instruction
+check_pick () {
+	mainline=
+	while test $# -gt 1
+	do
+		case "$1" in
+		-R)
+			;;
+		--mainline)
+			shift
+			mainline="$1"
+			test "$mainline" -gt 0 || {
+				todo_error '--mainline needs an integer beginning from 1.'
+				mainline=
+			}
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			check_general_option "$@" ||
+				todo_warn "Unknown option $1"
+			;;
+		*)
+			todo_error "Wrong number of arguments. ($# given, 1 wanted)"
+			break
+			;;
+		esac
+		shift $general_shift
+	done
+
+	if test -n "$mainline"
+	then
+		parents=$(count_parents "$1")
+		test "$parents" -lt "$mainline" &&
+			todo_error "Commit has only $parents (less than $mainline) parents."
+		test "$parents" -eq 1 &&
+			todo_warn 'Commit is not a merge at all.'
+	fi
+
+	commit_check "$1"
+
+	return 0
+}
+
+# Realize the "pick" instruction
+insn_pick () {
+	op=cherry-pick
+	mainline=
+	while test $# -gt 1
+	do
+		case "$1" in
+		-R)
+			op=revert
+			;;
+		--mainline)
+			mainline="$1 $2"
+			shift
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			handle_general_option "$@"
+			;;
+		esac
+		shift $general_shift
+	done
+
+	sha1=$(git rev-parse --verify "$1")
+
+	edit_msg="$EDIT"
+
+	# Don't edit on pick, but later, if author or message given.
+	test -n "$AUTHOR" -o -n "$MESSAGE" && edit_msg=
+
+	# Be kind to users and ignore --mainline=1 on non-merge commits
+	test -n "$mainline" -a 2 -gt $(count_parents "$sha1") && mainline=
+
+	use_perform=
+	test -n "$edit_msg" ||
+		use_perform=perform
+
+	pick_one "$op" $edit_msg $mainline $sha1 ||
+		die_with_patch $sha1 "Could not apply $sha1."
+
+	test -n "$EDIT" ||
+		use_perform=perform
+
+	get_current_author
+	signoff=
+	test -n "$SIGNOFF" && signoff=-s
+	if test -n "$AUTHOR" -a -n "$MESSAGE"
+	then
+		# this is just because we only want to do ONE amending commit
+		$use_perform git commit --amend $EDIT $signoff --no-verify \
+			--author "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" \
+			--message="$MESSAGE"
+	elif test -n "$AUTHOR"
+	then
+		# correct author if AUTHOR is set
+		$use_perform git commit --amend $EDIT --no-verify -C HEAD \
+			--author "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>"
+	elif test -n "$MESSAGE"
+	then
+		# correct commit message if MESSAGE is set
+		$use_perform git commit --amend $EDIT $signoff --no-verify \
+			-C HEAD --message="$MESSAGE"
+	elif test -n "$SIGNOFF"
+	then
+		# only add signoff
+		$use_perform git commit --amend $EDIT $signoff --no-verify \
+			-C HEAD
+	fi
+
+	return 0
+}
+
+options_edit="\
+edit <commit>
+--
+"
+
+# Check the "edit" instruction
+check_edit () {
+	shift
+	test -z "$BATCHMODE" ||
+		todo_error '"edit" instruction and --batch do not make sense together.'
+	test $# -eq 1 ||
+		todo_error "Wrong number of arguments. ($# given, 1 wanted)"
+	return 0
+}
+
+# Realize the "edit" instruction
+insn_edit () {
+	shift
+	insn_pick "$1"
+	echo '# pausing' >>"$DONE"
+	insn_pause
+}
+
+
+options_squash="\
+squash <commit>
+squash [options] --from <mark>
+--
+from squash all commits from <mark>
+collect-signoffs collect Signed-off-by: lines
+include-merges do not fail on merge commits
+$OPTIONS_GENERAL
+"
+
+# Check the "squash" instruction
+check_squash () {
+	from=
+	collect=
+	merges=
+	while test $# -gt 1
+	do
+		case "$1" in
+		--from)
+			from=t
+			;;
+		--collect-signoffs)
+			collect=t
+			todo_warn 'Not yet implemented.'
+			;;
+		--include-merges)
+			merges=t
+			;;
+		--)
+			shift
+			break
+			;;
+		*)
+			check_general_option "$@" ||
+				todo_error "Unknown option $1"
+			;;
+		esac
+		shift $general_shift
+	done
+
+	# in --from mode?
+	if test -n "$from"
+	then
+		test -z "$merges" &&
+			cat "$DONE" "$TODO" |
+			sed -n -e '/^[ \t]*mark[ \t]*:\{0,1\}'"${1#:}"'\($\|[^0-9]\)/,'"$line"'p' |
+			grep '^[ \t]*merge' >/dev/null &&
+			todo_error "$1..HEAD contains a merge commit. You may try --include-merges."
+
+		arg_is_mark_check "$1"
+	else
+		test -n "$merges" &&
+			todo_error '--include-merges only makes sense with --from <mark>.'
+		test -n "$collect" &&
+			todo_error '--collect-signoffs only makes sense with --from <mark>.'
+
+		commit_check "$1"
+	fi
+
+	return 0
+}
+
+# Realize the "squash" instruction
+insn_squash () {
+	squash_msg="$MSG-squash"
+	from=
+	while test $# -gt 1
+	do
+		case "$1" in
+		--from)
+			from=t
+			;;
+		--collect-signoffs)
+			warn '--collect-signoffs is not implemented.'
+			# XXX
+			;;
+		--include-merges)
+			: # This has to be done during check_squash
+			;;
+		--)
+			shift
+			break
+			;;
+		*)
+			handle_general_option "$@"
+			;;
+		esac
+		shift $general_shift
+	done
+
+	if test -n "$from"
+	then
+		sha1=$(mark_to_commit ":${1#:}")
+	else
+		sha1=$(git rev-parse --verify "$1")
+	fi
+
+	# Hm, somehow I don't think --skip on a conflicting squash
+	# may be useful, but if someone wants to do it, it should
+	# do the obvious: skip what squash would do.
+	echo "$(git rev-parse HEAD)" >"$SEQ_DIR/skiphead"
+
+	if test -n "$MESSAGE"
+	then
+		printf '%s\n' "$MESSAGE" >"$MSG"
+	else
+		if test -n "$from"
+		then
+			make_squash_message_multiple "$sha1" >"$MSG"
+		else
+			make_squash_message "$sha1" >"$MSG"
+		fi
+	fi
+
+	case "$(peek_next_command)" in
+	squash)
+		edit_commit=
+		use_perform=perform
+		cp "$MSG" "$squash_msg"
+		;;
+	*)
+		edit_commit=-e
+		use_perform=
+		rm -f "$squash_msg"
+		;;
+	esac
+
+	test -n "$MESSAGE" && edit_commit=
+	test -n "$EDIT" && edit_commit=-e
+
+	# is --author (or equivalent) set?
+	if test -n "$AUTHOR"
+	then
+		# evaluate author script
+		get_current_author
+	else
+		# if --author is not given, we get the authorship
+		# information from the commit before.
+		eval "$(get_author_ident_from_commit HEAD)"
+		# but we do not write an author script
+	fi
+
+	# --from or not
+	failed=
+	if test -n "$from"
+	then
+		perform git reset --soft "$sha1"
+	else
+		perform git reset --soft HEAD^
+
+		pick_one cherry-pick -n "$sha1" || failed=t
+	fi
+
+	dashdash_signoff
+
+	if test -z "$failed"
+	then
+		# This is like --amend, but with a different message
+		with_author $use_perform git commit --no-verify \
+			-F "$MSG" $edit_commit || failed=t
+	else
+		cp "$MSG" "$GIT_DIR/SQUASH_MSG"
+		warn
+		die_with_patch $sha1 "Could not apply $sha1."
+	fi
+
+	return 0
+}
+
+
+options_mark="\
+mark <mark>
+--
+"
+
+# Check the "mark" instruction
+check_mark () {
+	shift
+	test $# -eq 1 ||
+		todo_error "Wrong number of arguments. ($# given, 1 wanted)"
+	my_mark=$(expr "x${1#:}" : 'x0*\([0-9][0-9]*\)$')
+	test -n "$my_mark" ||
+		todo_error "Mark $1 not an integer."
+	expr "x$available_marks " : " $my_mark " >/dev/null &&
+		todo_error "Mark :$my_mark already defined. Choose another integer."
+	available_marks="$available_marks $my_mark"
+
+	return 0
+}
+
+# Realize the "mark" instruction
+insn_mark () {
+	shift
+	given="$1"
+
+	mark=$(mark_to_ref ":${given#:}")
+	git update-ref "$mark" HEAD
+	return 0
+}
+
+
+options_merge="\
+merge [options] <commit-ish> ...
+--
+standard generate default commit message
+s,strategy= use merge strategy ...
+$OPTIONS_GENERAL
+"
+
+# Check the "merge" instruction
+check_merge () {
+	while test $# -gt 1
+	do
+		case "$1" in
+		--standard)
+			msg_opt="t$msg_opt"
+			;;
+		-s)
+			shift
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			check_general_option "$@" ||
+				todo_error "Unknown option $1"
+			;;
+		esac
+		shift $general_shift
+	done
+
+	test $# -gt 0 ||
+		todo_error 'What are my parents? Need new parents!'
+
+	while test $# -gt 0
+	do
+		arg_is_mark_or_commit_check "$1"
+		shift
+	done
+	return 0
+}
+
+# Realize the "merge" instruction
+insn_merge () {
+	standard=
+
+	while test $# -gt 1
+	do
+		case "$1" in
+		--standard)
+			standard=t
+			;;
+		-s)
+			shift
+			strategy="-s $1"
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			handle_general_option "$@"
+			;;
+		esac
+		shift $general_shift
+	done
+
+	new_parents=
+	for p in "$@"
+	do
+		new_parents="$new_parents $(mark_to_ref $p)"
+	done
+	new_parents="${new_parents# }"
+
+	get_current_author
+
+	if test -n "$standard"
+	then
+		for cur_parent in $new_parents
+		do
+			printf '%s\t\t%s' \
+				"$(git rev-parse "$cur_parent")" "$cur_parent"
+		done | git fmt-merge-msg >"$MSG"
+	fi
+
+	test -n "$MESSAGE" &&
+		printf '%s\n' "$MESSAGE" >"$MSG"
+
+	dashdash_signoff
+	if ! has_message "$MSG" || test -n "$EDIT"
+	then
+		echo "
+# Please enter the merge commit message.
+# (Comment lines starting with '#' will not be included)" >>"$MSG"
+
+		git_editor "$MSG" ||
+			die_with_patch HEAD 'Editor returned error.'
+		has_message "$MSG" ||
+			die_with_patch HEAD 'No commit message given.'
+	fi
+
+	if ! perform git merge --no-commit $strategy $new_parents
+	then
+		perform git rerere
+		cp "$MSG" "$GIT_DIR/MERGE_MSG"
+		die_to_continue "Error merging $new_parents."
+	fi
+	with_author perform git commit -F "$MSG" --no-verify
+	return 0
+}
+
+
+options_reset="\
+reset <commit-ish>
+--
+"
+
+# Check the "reset" instruction
+check_reset () {
+	shift
+	test $# -eq 1 ||
+		todo_error "Wrong number of arguments. ($# given, 1 wanted)"
+	arg_is_mark_or_commit_check "$1"
+
+	return 0
+}
+
+# Realize the "reset" instruction
+insn_reset () {
+	shift
+	reset_almost_hard "$(mark_to_commit "$1")"
+}
+
+
+options_ref="\
+ref <ref>
+--
+"
+
+# Check the "ref" instruction
+check_ref () {
+	shift
+	test $# -eq 1 ||
+		todo_error "Wrong number of arguments. ($# given, 1 wanted)"
+	return 0
+}
+
+# Realize the "ref" instruction
+insn_ref () {
+	shift
+	perform git update-ref "$1" HEAD
+}
+
+
+### Instruction main loop
+
+# Run check_* or insn_* with massaged options
+# Usage: run_insn (check|do) <insn> <insn options>
+run_insn () {
+	c_or_i="$1"
+	insn="$2"
+	shift
+	shift
+	eval "option_spec=\"\$options_$insn\""
+	eval "$(printf "%s" "$option_spec" |
+		git rev-parse --parseopt -- "$@" ||
+		echo return 1)"
+	case "$c_or_i" in
+	check)
+		check_$insn "$@"
+		;;
+	do)
+		insn_$insn "$@"
+		;;
+	esac
+}
+
+# Execute the first line of the current TODO file
+execute_next () {
+	read -r command rol <"$TODO"
+	test "$VERBOSE" -gt 1 &&
+		printf 'Next line: %s\n' "$command $rol"
+
+	remove_from_todo
+	case "$command" in
+	'#'*|'')
+		;;
+	*)
+		comment_for_reflog $command
+		# reset general options
+		rm -f "$AUTHOR_SCRIPT" "$MSG"
+		echo 'HEAD' >"$SEQ_DIR/skiphead"
+		general_shift=1
+		AUTHOR=
+		EDIT=
+		MESSAGE=
+		SIGNOFF=
+		# XXX: eval is evil!
+		eval "run_insn do $command $rol" ||
+			die_to_continue 'An unexpected error occured.'
+		;;
+	esac
+}
+
+# Execute the rest of the TODO file and finish
+execute_rest () {
+	while has_action "$TODO"
+	do
+		execute_next
+	done
+
+	comment_for_reflog finish
+	if test -n "$ONTO"
+	then
+		git update-ref -m "$GIT_REFLOG_ACTION: $ONTO" "$ONTO" HEAD &&
+		git symbolic-ref HEAD "$ONTO"
+	fi &&
+	cleanup
+	exit
+}
+
+# We don't need to check a todo file if it has not changed
+# since it has last been acknowledged to be sane.
+todo_has_changed () {
+	test -f "$1.sum" || return 0
+	test "$(git hash-object "$1")" != "$(cat "$1.sum")"
+}
+
+# acknowledge todo file to be sane
+todo_ack () {
+	git hash-object "$1" >"$1.sum"
+}
+
+# Main loop to check instructions
+todo_check () {
+	todo="$TODO"
+	test -n "$1" && todo="$1"
+	todo_has_changed "$todo" || return 0
+
+	test "$VERBOSE" -eq 1 -a -t 1 -o "$VERBOSE" -gt 1 &&
+		printf 'Checking...\r'
+	test "$VERBOSE" -lt 2 || echo
+	available_marks=' '
+	for cur_mark in $(git for-each-ref --format='%(refname)' "$MARK_PREFIX")
+	do
+		available_marks="$available_marks ${cur_mark##*/}"
+	done
+
+	retval=0
+	line=1
+	while read -r command rol
+	do
+		case "$command" in
+		'#'*|'')
+			;;
+		*)
+			eval 'test -n "$options_'"$command"'"' || {
+				retval=1
+				todo_error "Unknown $command instruction"
+				continue
+			}
+
+			general_shift=1
+			msg_opt=
+			author_opt=
+			eval "run_insn check $command $rol" ||
+				todo_error "Unknown option used"
+			expr "$msg_opt" : 'ttt*' >/dev/null &&
+				todo_error 'You can only provide one commit message option.'
+			expr "$author_opt" : 'ttt*' >/dev/null &&
+				todo_error 'You can only provide one author option.'
+			;;
+		esac
+		line=$(expr "$line" + 1)
+	done <"$todo"
+	test $retval -ne 0 || todo_ack "$todo"
+	return $retval
+}
+
+prepare_editable_todo () {
+	echo '# ALREADY DONE:'
+	sed -e 's/^/#  /' <"$DONE"
+	echo '# '
+	echo "$markline"
+	cat "$TODO"
+}
+
+# expand shortcuts in TODO file $1
+expand_shortcuts () {
+	sed -e '
+		s/^[ \t]*p\>/pick/;
+		s/^[ \t]*e\>/edit/;
+		s/^[ \t]*s\>/squash/;
+	' <"$1" >"$TODO.cut"
+	mv "$TODO.cut" "$1"
+}
+
+get_saved_options () {
+	read ALLOW_DIRTY <"$SEQ_DIR/allow-dirty"
+	read VERBOSE <"$SEQ_DIR/verbose"
+	read ADVICE <"$SEQ_DIR/advice"
+	read ONTO <"$SEQ_DIR/onto"
+	read ORIG_HEAD <"$SEQ_DIR/head"
+	test -f "$WHY_FILE" &&
+		read WHY <"$WHY_FILE"
+	return 0
+}
+
+# Realize sequencer invocation
+do_startup () {
+	test -d "$SEQ_DIR" &&
+		die 'sequencer already started'
+
+	require_clean_work_tree
+
+	ORIG_HEAD=$(git rev-parse --verify HEAD) ||
+		die 'No HEAD?'
+
+	mkdir "$SEQ_DIR" ||
+		die "Could not create temporary $SEQ_DIR"
+
+	# save options
+	echo "$ALLOW_DIRTY" >"$SEQ_DIR/allow-dirty"
+	echo "$VERBOSE" >"$SEQ_DIR/verbose"
+	echo "$ADVICE" >"$SEQ_DIR/advice"
+	test -n "$CALLERSTRING" &&
+		generate_caller_script "$CALLERSTRING"
+	# generate empty DONE and "onto" file
+	: >"$DONE"
+	: >"$SEQ_DIR/onto"
+
+	if test -n "$BATCHMODE"
+	then
+		GIT_CHERRY_PICK_HELP='  Aborting (batch mode)'
+		export GIT_CHERRY_PICK_HELP
+	else
+		assure_caller
+	fi
+
+	comment_for_reflog start
+
+	# save old head before checking out the given <branch>
+	git symbolic-ref HEAD >"$SEQ_DIR/head-name" 2>/dev/null ||
+		echo 'detached HEAD' >"$SEQ_DIR/head-name"
+	echo $ORIG_HEAD >"$SEQ_DIR/head"
+	# do it here so that die_abort can work ;)
+
+	if test -n "$ONTO"
+	then
+		# if ONTO is a branch name, then keep it, otherwise
+		# we don't care anymore and erase ONTO.
+		if git-show-ref --quiet --verify -- "refs/heads/${ONTO##*/}"
+		then
+			ONTO="refs/heads/${ONTO##*/}"
+			echo "$ONTO" >"$SEQ_DIR/onto"
+			perform git checkout "$(git rev-parse "$ONTO")" ||
+				die_abort "Could not checkout branch $ONTO"
+		else
+			perform git checkout "$ONTO" ||
+				die_abort "Could not checkout commit $ONTO"
+			ONTO=
+		fi
+	fi
+
+	(git var GIT_AUTHOR_IDENT || git var COMMITTER_IDENT) |
+		make_author_script_from_string >"$ORIG_AUTHOR_SCRIPT"
+
+	# read from file or from stdin?
+	if test -z "$1"
+	then
+		: >"$TODO" ||
+			die_abort "Could not generate TODO file $TODO"
+		while read -r line
+		do
+			printf '%s\n' "$line" >>"$TODO" ||
+				die_abort "Could not append to TODO file $TODO"
+		done
+	else
+		cp "$1" "$TODO" ||
+			die_abort "Could not find TODO file $1."
+	fi
+	expand_shortcuts "$TODO"
+
+	has_action "$TODO" || die_abort 'Nothing to do'
+	todo_check || WHY=todo die_to_continue "TODO file contains errors."
+
+	execute_rest
+	exit
+}
+
+# Realize --continue.
+do_continue () {
+	test -d "$SEQ_DIR" || die 'No sequencer running'
+	test_caller 'continue'
+
+	expand_shortcuts "$TODO"
+	todo_check || WHY=todo die_to_continue "TODO file contains errors."
+
+	comment_for_reflog continue
+
+	git rev-parse --verify HEAD >/dev/null ||
+		die_to_continue 'Cannot read HEAD'
+
+	get_saved_options
+	if test -z "$ALLOW_DIRTY"
+	then
+		git update-index --ignore-submodules --refresh &&
+			git diff-files --quiet --ignore-submodules ||
+			die_to_continue 'Working tree is dirty. (Use git add or git stash first?)'
+	fi
+
+	# do we have anything to commit? (staged changes)
+	if ! git diff-index --cached --quiet --ignore-submodules HEAD --
+	then
+		get_current_author
+
+		# After "pause", we should amend (merge-safe!).
+		# On conflict, we do not have a commit to amend, so we
+		# should just commit.
+		case "$WHY" in
+		pause|run)
+			with_author git commit --amend --no-verify -F "$MSG" -e ||
+				die_to_continue 'Could not commit staged changes.'
+			;;
+		conflict)
+			with_author git commit --no-verify -F "$MSG" -e ||
+				die_to_continue 'Could not commit staged changes.'
+			echo '# resolved CONFLICTS of the last instruction' >>"$DONE"
+			;;
+		*)
+			die_to_continue 'There are staged changes. Do not know what to do with them.'
+		esac
+	fi
+
+	require_clean_work_tree
+	clean_why
+	execute_rest
+	exit
+}
+
+# Realize --abort.
+do_abort () {
+	test -d "$SEQ_DIR" || die 'No sequencer running'
+	test_caller 'abort'
+	get_saved_options
+
+	comment_for_reflog abort
+	perform git rerere clear
+	restore
+	cleanup
+	exit
+}
+
+# Realize --skip.
+do_skip () {
+	test -d "$SEQ_DIR" || die 'No sequencer running'
+	test_caller 'skip'
+
+	expand_shortcuts "$TODO"
+	todo_check || WHY=todo die_to_continue "TODO file contains errors."
+	get_saved_options
+
+	comment_for_reflog skip
+	perform git rerere clear
+	clean_why
+
+	reset_almost_hard "$(cat "$SEQ_DIR/skiphead")" &&
+	echo '# SKIPPED the last instruction' >>"$DONE" &&
+		execute_rest
+	exit
+}
+
+# Realize --edit.
+do_edit () {
+	test -d "$SEQ_DIR" || die 'No sequencer running'
+
+	markline='### BEGIN EDITING BELOW THIS LINE ###'
+	prepare_editable_todo >"$TODO.new"
+	if todo_has_changed "$TODO"
+	then
+		sane=
+	else
+		sane=t
+		todo_ack "$TODO.new"
+	fi
+
+	# XXX: does not make sense
+	#      when input does not come from a terminal
+	git_editor "$TODO.new" ||
+		die 'Editor returned an error.'
+
+	if test -t 0 -a -t 1
+	then
+		sane=t
+		echo
+		# interactive:
+		until has_action "$TODO.new" && todo_check "$TODO.new"
+		do
+			has_action "$TODO.new" || echo 'TODO file empty.'
+			printf 'What to do with the file? [c]orrect/[e]dit again/[r]ewind/[s]ave/[?] '
+			read reply
+			case "$reply" in
+			[cC]*)
+				git_editor "$TODO.new"
+				expand_shortcuts "$TODO.new"
+				;;
+			[eE]*)
+				prepare_editable_todo >"$TODO.new"
+				git_editor "$TODO.new"
+				expand_shortcuts "$TODO.new"
+				;;
+			[rRxXqQ]*)
+				rm -f "$TODO.new" "$TODO.new.sum"
+				exit 0
+				;;
+			[sS]*)
+				test "$WHY" != 'pause' ||
+					echo 'todo' >"$WHY_FILE"
+				sane=
+				break
+				;;
+			[?hH]*)
+				cat <<EOF
+
+Help:
+s - save TODO file and exit
+c - respawn editor to correct TODO file
+e - drop changes and respawn editor on original TODO file
+r - drop changes and exit as if nothing happened
+? - print this help
+EOF
+				;;
+			esac
+			echo
+		done
+	else
+		# defaults:
+		has_action "$TODO.new" || {
+			echo "Nothing to do"
+			exit
+		}
+		todo_check "$TODO.new" ||
+			die 'TODO file contains errors. Aborting.'
+	fi
+	cp "$TODO" "$TODO.old"
+	if grep "^$markline" "$TODO.new" >/dev/null
+	then
+		sed -e "1,/^$markline/d" <"$TODO.new" >"$TODO"
+	else
+		cp "$TODO.new" "$TODO"
+	fi
+	rm -f "$TODO.new" "$TODO.new.sum"
+	test -z "$sane" || todo_ack "$TODO"
+	echo
+	echo 'TODO file contains:'
+	echo
+	cat "$TODO"
+	exit 0
+}
+
+# Realize --status.
+do_status () {
+	test -d "$SEQ_DIR" || die 'No sequencer running.'
+	get_saved_options
+
+	if has_action "$DONE"
+	then
+		echo 'Already done (or tried):'
+		sed -e 's/^/  /' <"$DONE"
+		echo
+	fi
+	case "$WHY" in
+	pause)
+		echo 'Intentionally paused.'
+		;;
+	run)
+		echo 'Interrupted because running failed.'
+		;;
+	conflict)
+		echo 'Interrupted by conflict at'
+		sed -n -e 's/^/  /;$p' <"$DONE"
+		;;
+	todo)
+		echo 'Interrupted because of errors in the TODO file.'
+		;;
+	*)
+		echo 'Current state is broken.'
+	esac
+	test "$VERBOSE" -gt 1 && echo 'Running verbosely.'
+	test "$VERBOSE" -lt 1 && echo 'Running quietly.'
+	test -n "$ONTO" &&
+		echo "Sequencing on branch ${ONTO##*/}."
+	if has_action "$TODO"
+	then
+		echo
+
+		echo 'Still to do:'
+		sed -e 's/^/  /' <"$TODO"
+	fi
+	if test "$WHY" = todo
+	then
+		echo
+		echo 'But there are errors. To edit, run:'
+		echo '    git sequencer --edit'
+	else
+		echo
+		echo 'To abort & restore, invoke:'
+		echo "    $(print_caller --abort)"
+		echo 'To continue, invoke:'
+		echo "    $(print_caller --continue)"
+		test "$WHY" = 'pause' -o "$WHY" = 'run' || {
+			echo 'To skip the current instruction, invoke:'
+			echo "    $(print_caller --skip)"
+		}
+	fi
+	exit 0
+}
+
+is_standalone () {
+	test $# -eq 2 &&
+	test "$2" = '--' &&
+	test -z "$ALLOW_DIRTY" &&
+	test -z "$BATCHMODE" &&
+	test "$ADVICE" = t &&
+	test -z "$onto" &&
+	test "$VERBOSE" -eq 1
+}
+
+
+### Option handling and startup
+
+onto=
+BATCHMODE=
+ALLOW_DIRTY=
+CALLERSTRING=
+VERBOSE=1
+ADVICE=t
+CALLER=
+CALLER_ABRT=
+CALLER_CONT=
+CALLER_SKIP=
+while test $# -gt 0
+do
+	case "$1" in
+	--continue)
+		is_standalone "$@" || usage
+		assure_caller
+		do_continue
+		;;
+	--abort)
+		is_standalone "$@" || usage
+		assure_caller
+		do_abort
+		;;
+	--skip)
+		is_standalone "$@" || usage
+		assure_caller
+		do_skip
+		;;
+	--status)
+		is_standalone "$@" || usage
+		assure_caller
+		do_status
+		;;
+	--edit)
+		is_standalone "$@" || usage
+		do_edit
+		;;
+	--caller)
+		###############################################################
+		# This feature is for user scripts only. (Hence undocumented.)
+		# User scripts should pass an argument like:
+		# --caller="git foo|abrt|go|next"
+		# on every git sequencer call. (It is only ignored on
+		# --edit and --status.)
+		# So git sequencer knows that
+		# "git foo abrt" will abort,
+		# "git foo go" will continue and
+		# "git foo next" will skip the sequencing process.
+		# This is useful if your user script does some extra
+		# preparations or cleanup before/after calling
+		#   git sequencer --caller="..." --abort|--continue|--skip
+		#
+		# Running git-sequencer without the same --caller string
+		# fails then, until the sequencing process has finished or
+		# aborted.
+		#
+		# Btw, --caller="my_tiny_script.sh|-a||" will be
+		# interpreted, that users must invoke "my_tiny_script.sh -a"
+		# to abort, but can invoke "git sequencer --continue" and
+		# "git sequencer --skip" to continue or skip.
+		# And it is also possible to provide three different scripts
+		# by --caller="|script 1|tool 2|util 3".
+		#
+		# If your user script does not need any special
+		# abort/continue/skip behavior, then just do NOT pass
+		# the --caller option.
+		###############################################################
+		shift
+		expr "x$1" : 'x.*|.*|.*|.*$' >/dev/null ||
+			die 'Wrong --caller format.'
+		CALLERSTRING="$1"
+		;;
+	--allow-dirty)
+		ALLOW_DIRTY='HEAD'
+		# this means: true
+		;;
+	-B)
+		BATCHMODE=t
+		# XXX: we still have abort on editor invokations
+		;;
+	--no-advice)
+		ADVICE=f
+		;;
+	--onto)
+		shift
+		ONTO="$1"
+		test $(git cat-file -t "$ONTO") = 'commit' ||
+			die "$ONTO is no commit or branch."
+		;;
+	-q)
+		VERBOSE=0
+		ADVICE=f
+		# XXX: If there were no editors,
+		# we could just do exec >/dev/null
+		;;
+	-v)
+		VERBOSE=2
+		# or increment it if we have several verbosity steps
+		;;
+	--)
+		shift
+		break
+		;;
+	*)
+		die "$1 currently not implemented."
+		;;
+	esac
+	shift
+done
+test $# -gt 1 && usage
+
+do_startup "$1"
-- 
1.6.0.rc0.49.gd39f

^ permalink raw reply related

* [PATCH 5/5] Migrate rebase-i to sequencer
From: Stephan Beyer @ 2008-07-26  5:20 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Daniel Barkalow, Stephan Beyer
In-Reply-To: <1217049644-8874-5-git-send-email-s-beyer@gmx.net>

The migration of pure rebase-i to sequencer is simply done by
generating the todo list, but with a comment marker (`#')
before the description, and then feed it to git sequencer.

For git-rebase-i -p (preserving merges) merges should be
rewritten. For this, the sequencer instructions "mark", "merge"
and "reset" are used.

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 git-rebase--interactive.sh    |  436 ++++++++++-------------------------------
 t/t3404-rebase-interactive.sh |    8 +-
 2 files changed, 110 insertions(+), 334 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 4e334ba..2136e02 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -42,11 +42,6 @@ STRATEGY=
 ONTO=
 VERBOSE=
 
-GIT_CHERRY_PICK_HELP="  After resolving the conflicts,
-mark the corrected paths with 'git add <paths>', and
-run 'git rebase --continue'"
-export GIT_CHERRY_PICK_HELP
-
 warn () {
 	echo "$*" >&2
 }
@@ -74,48 +69,6 @@ require_clean_work_tree () {
 	die "Working tree is dirty"
 }
 
-ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
-
-comment_for_reflog () {
-	case "$ORIG_REFLOG_ACTION" in
-	''|rebase*)
-		GIT_REFLOG_ACTION="rebase -i ($1)"
-		export GIT_REFLOG_ACTION
-		;;
-	esac
-}
-
-last_count=
-mark_action_done () {
-	sed -e 1q < "$TODO" >> "$DONE"
-	sed -e 1d < "$TODO" >> "$TODO".new
-	mv -f "$TODO".new "$TODO"
-	count=$(grep -c '^[^#]' < "$DONE")
-	total=$(($count+$(grep -c '^[^#]' < "$TODO")))
-	if test "$last_count" != "$count"
-	then
-		last_count=$count
-		printf "Rebasing (%d/%d)\r" $count $total
-		test -z "$VERBOSE" || echo
-	fi
-}
-
-make_patch () {
-	parent_sha1=$(git rev-parse --verify "$1"^) ||
-		die "Cannot get patch for $1^"
-	git diff-tree -p "$parent_sha1".."$1" > "$DOTEST"/patch
-	test -f "$DOTEST"/message ||
-		git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message
-	test -f "$DOTEST"/author-script ||
-		get_author_ident_from_commit "$1" > "$DOTEST"/author-script
-}
-
-die_with_patch () {
-	make_patch "$1"
-	git rerere
-	die "$2"
-}
-
 die_abort () {
 	rm -rf "$DOTEST"
 	die "$1"
@@ -125,48 +78,20 @@ has_action () {
 	grep '^[^#]' "$1" >/dev/null
 }
 
-pick_one () {
-	no_ff=
-	case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac
-	output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
-	test -d "$REWRITTEN" &&
-		pick_one_preserving_merges "$@" && return
-	parent_sha1=$(git rev-parse --verify $sha1^) ||
-		die "Could not get the parent of $sha1"
-	current_sha1=$(git rev-parse --verify HEAD)
-	if test "$no_ff$current_sha1" = "$parent_sha1"; then
-		output git reset --hard $sha1
-		test "a$1" = a-n && output git reset --soft $current_sha1
-		sha1=$(git rev-parse --short $sha1)
-		output warn Fast forward to $sha1
-	else
-		output git cherry-pick "$@"
-	fi
-}
-
-pick_one_preserving_merges () {
-	case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
+create_todo_preserving_merges () {
+	shortsha1=$sha1
 	sha1=$(git rev-parse $sha1)
 
-	if test -f "$DOTEST"/current-commit
-	then
-		current_commit=$(cat "$DOTEST"/current-commit) &&
-		git rev-parse HEAD > "$REWRITTEN"/$current_commit &&
-		rm "$DOTEST"/current-commit ||
-		die "Cannot write current commit's replacement sha1"
-	fi
-
-	# rewrite parents; if none were rewritten, we can fast-forward.
-	fast_forward=t
 	preserve=t
 	new_parents=
+	first_parent=
 	for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)
 	do
+		# check if we've already seen this parent
 		if test -f "$REWRITTEN"/$p
 		then
 			preserve=f
 			new_p=$(cat "$REWRITTEN"/$p)
-			test $p != $new_p && fast_forward=f
 			case "$new_parents" in
 			*$new_p*)
 				;; # do nothing; that parent is already there
@@ -177,185 +102,46 @@ pick_one_preserving_merges () {
 		else
 			new_parents="$new_parents $p"
 		fi
+		test -n "$first_parent" || first_parent=$p
 	done
-	case $fast_forward in
-	t)
-		output warn "Fast forward to $sha1"
-		test $preserve = f || echo $sha1 > "$REWRITTEN"/$sha1
-		;;
-	f)
-		test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
-
-		first_parent=$(expr "$new_parents" : ' \([^ ]*\)')
-		# detach HEAD to current parent
-		output git checkout $first_parent 2> /dev/null ||
-			die "Cannot move HEAD to $first_parent"
-
-		echo $sha1 > "$DOTEST"/current-commit
-		case "$new_parents" in
-		' '*' '*)
-			# redo merge
-			author_script=$(get_author_ident_from_commit $sha1)
-			eval "$author_script"
-			msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')"
-			# No point in merging the first parent, that's HEAD
-			new_parents=${new_parents# $first_parent}
-			if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
-				GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
-				GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
-				output git merge $STRATEGY -m "$msg" \
-					$new_parents
-			then
-				git rerere
-				printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
-				die Error redoing merge $sha1
-			fi
-			;;
-		*)
-			output git cherry-pick "$@" ||
-				die_with_patch $sha1 "Could not pick $sha1"
-			;;
-		esac
-		;;
-	esac
-}
+	# We do not have parent, so ignore this commit
+	test t = $preserve && return
 
-nth_string () {
-	case "$1" in
-	*1[0-9]|*[04-9]) echo "$1"th;;
-	*1) echo "$1"st;;
-	*2) echo "$1"nd;;
-	*3) echo "$1"rd;;
-	esac
-}
+	# We always write a mark, because we do not know if there will
+	# be a "reset" or "merge"
+	# Filter the unneeded marks out afterwards.
+	echo "mark :$mark"
+	mark=$(($mark+1))
 
-make_squash_message () {
-	if test -f "$SQUASH_MSG"; then
-		COUNT=$(($(sed -n "s/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p" \
-			< "$SQUASH_MSG" | sed -ne '$p')+1))
-		echo "# This is a combination of $COUNT commits."
-		sed -e 1d -e '2,/^./{
-			/^$/d
-		}' <"$SQUASH_MSG"
-	else
-		COUNT=2
-		echo "# This is a combination of two commits."
-		echo "# The first commit's message is:"
-		echo
-		git cat-file commit HEAD | sed -e '1,/^$/d'
-	fi
-	echo
-	echo "# This is the $(nth_string $COUNT) commit message:"
-	echo
-	git cat-file commit $1 | sed -e '1,/^$/d'
-}
+	new_first_parent=$(expr "$new_parents" : ' \([^ ]*\)')
 
-peek_next_command () {
-	sed -n "1s/ .*$//p" < "$TODO"
-}
+	# Reset if needed
+	test -z "$first_parent" -o "$first_parent" = $lastsha1 ||
+		echo "reset $new_first_parent"
 
-do_next () {
-	rm -f "$DOTEST"/message "$DOTEST"/author-script \
-		"$DOTEST"/amend || exit
-	read command sha1 rest < "$TODO"
-	case "$command" in
-	'#'*|'')
-		mark_action_done
-		;;
-	pick|p)
-		comment_for_reflog pick
+	echo ":$mark" > "$REWRITTEN"/$sha1
 
-		mark_action_done
-		pick_one $sha1 ||
-			die_with_patch $sha1 "Could not apply $sha1... $rest"
-		;;
-	edit|e)
-		comment_for_reflog edit
-
-		mark_action_done
-		pick_one $sha1 ||
-			die_with_patch $sha1 "Could not apply $sha1... $rest"
-		make_patch $sha1
-		: > "$DOTEST"/amend
-		warn "Stopped at $sha1... $rest"
-		warn "You can amend the commit now, with"
-		warn
-		warn "	git commit --amend"
-		warn
-		warn "Once you are satisfied with your changes, run"
-		warn
-		warn "	git rebase --continue"
-		warn
-		exit 0
-		;;
-	squash|s)
-		comment_for_reflog squash
-
-		has_action "$DONE" ||
-			die "Cannot 'squash' without a previous commit"
-
-		mark_action_done
-		make_squash_message $sha1 > "$MSG"
-		case "$(peek_next_command)" in
-		squash|s)
-			EDIT_COMMIT=
-			USE_OUTPUT=output
-			cp "$MSG" "$SQUASH_MSG"
-			;;
-		*)
-			EDIT_COMMIT=-e
-			USE_OUTPUT=
-			rm -f "$SQUASH_MSG" || exit
-			;;
-		esac
-
-		failed=f
-		author_script=$(get_author_ident_from_commit HEAD)
-		output git reset --soft HEAD^
-		pick_one -n $sha1 || failed=t
-		echo "$author_script" > "$DOTEST"/author-script
-		if test $failed = f
-		then
-			# This is like --amend, but with a different message
-			eval "$author_script"
-			GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
-			GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
-			GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
-			$USE_OUTPUT git commit --no-verify -F "$MSG" $EDIT_COMMIT || failed=t
-		fi
-		if test $failed = t
-		then
-			cp "$MSG" "$GIT_DIR"/MERGE_MSG
-			warn
-			warn "Could not apply $sha1... $rest"
-			die_with_patch $sha1 ""
-		fi
+	# Merge or pick
+	case "$new_parents" in
+	' '*' '*)
+		new_parents=${new_parents# $new_first_parent}
+		printf 'merge%s -C %s%s\t%s\n' "$STRATEGY" \
+			"$shortsha1" "$new_parents" "$rest"
 		;;
 	*)
-		warn "Unknown command: $command $sha1 $rest"
-		die_with_patch $sha1 "Please fix this in the file $TODO."
+		printf 'pick %s\t%s\n' "$shortsha1" "$rest"
 		;;
 	esac
-	test -s "$TODO" && return
 
-	comment_for_reflog finish &&
+	lastsha1="$sha1"
+	return 0
+}
+
+update_refs_and_exit () {
 	HEADNAME=$(cat "$DOTEST"/head-name) &&
 	OLDHEAD=$(cat "$DOTEST"/head) &&
 	SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
-	if test -d "$REWRITTEN"
-	then
-		test -f "$DOTEST"/current-commit &&
-			current_commit=$(cat "$DOTEST"/current-commit) &&
-			git rev-parse HEAD > "$REWRITTEN"/$current_commit
-		if test -f "$REWRITTEN"/$OLDHEAD
-		then
-			NEWHEAD=$(cat "$REWRITTEN"/$OLDHEAD)
-		else
-			NEWHEAD=$OLDHEAD
-		fi
-	else
-		NEWHEAD=$(git rev-parse HEAD)
-	fi &&
+	NEWHEAD=$(git rev-parse HEAD) &&
 	case $HEADNAME in
 	refs/*)
 		message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
@@ -373,13 +159,6 @@ do_next () {
 	exit
 }
 
-do_rest () {
-	while :
-	do
-		do_next
-	done
-}
-
 # check if no other options are set
 is_standalone () {
 	test $# -eq 2 -a "$2" = '--' &&
@@ -395,80 +174,47 @@ get_saved_options () {
 	test -f "$DOTEST"/verbose && VERBOSE=t
 }
 
-while test $# != 0
-do
-	case "$1" in
-	--continue)
-		is_standalone "$@" || usage
-		get_saved_options
-		comment_for_reflog continue
-
-		test -d "$DOTEST" || die "No interactive rebase running"
-
-		# Sanity check
-		git rev-parse --verify HEAD >/dev/null ||
-			die "Cannot read HEAD"
-		git update-index --ignore-submodules --refresh &&
-			git diff-files --quiet --ignore-submodules ||
-			die "Working tree is dirty"
-
-		# do we have anything to commit?
-		if git diff-index --cached --quiet --ignore-submodules HEAD --
+run_sequencer () {
+	git sequencer --caller='git rebase -i|--abort|--continue|--skip' "$@"
+	case "$?" in
+	0)
+		if test "$1" = --abort
 		then
-			: Nothing to commit -- skip this
+			rm -rf "$DOTEST"
+			exit
 		else
-			. "$DOTEST"/author-script ||
-				die "Cannot find the author identity"
-			if test -f "$DOTEST"/amend
-			then
-				git reset --soft HEAD^ ||
-				die "Cannot rewind the HEAD"
-			fi
-			export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE &&
-			git commit --no-verify -F "$DOTEST"/message -e ||
-			die "Could not commit staged changes."
+			update_refs_and_exit
 		fi
-
-		require_clean_work_tree
-		do_rest
 		;;
-	--abort)
-		is_standalone "$@" || usage
-		get_saved_options
-		comment_for_reflog abort
-
-		git rerere clear
-		test -d "$DOTEST" || die "No interactive rebase running"
-
-		HEADNAME=$(cat "$DOTEST"/head-name)
-		HEAD=$(cat "$DOTEST"/head)
-		case $HEADNAME in
-		refs/*)
-			git symbolic-ref HEAD $HEADNAME
-			;;
-		esac &&
-		output git reset --hard $HEAD &&
-		rm -rf "$DOTEST"
-		exit
+	2)
+		# pause
+		exit 0
 		;;
-	--skip)
-		is_standalone "$@" || usage
-		get_saved_options
-		comment_for_reflog skip
-
-		git rerere clear
-		test -d "$DOTEST" || die "No interactive rebase running"
+	3)
+		# conflict
+		exit 1
+		;;
+	*)
+		die_abort 'git-sequencer died unexpected.'
+		;;
+	esac
+}
 
-		output git reset --hard && do_rest
+while test $# != 0
+do
+	case "$1" in
+	--abort|--continue|--skip)
+		is_standalone "$@" || usage
+		run_sequencer "$1"
 		;;
 	-s)
 		case "$#,$1" in
 		*,*=*)
-			STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
+			STRATEGY=" -s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
 		1,*)
 			usage ;;
 		*)
-			STRATEGY="-s $2"
+			STRATEGY=" -s $2"
 			shift ;;
 		esac
 		;;
@@ -494,12 +240,12 @@ do
 		test $# -eq 1 -o $# -eq 2 || usage
 		test -d "$DOTEST" &&
 			die "Interactive rebase already started"
+		git sequencer --status >/dev/null 2>&1 &&
+			die "Sequencer already started. Cannot run rebase."
 
 		git var GIT_COMMITTER_IDENT >/dev/null ||
 			die "You need to set your committer info first"
 
-		comment_for_reflog start
-
 		require_clean_work_tree
 
 		UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
@@ -516,42 +262,72 @@ do
 		HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
 		mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
 
-		: > "$DOTEST"/interactive || die "Could not mark as interactive"
+		: > "$DOTEST"/interactive || die_abort "Could not mark as interactive"
 		git symbolic-ref HEAD > "$DOTEST"/head-name 2> /dev/null ||
 			echo "detached HEAD" > "$DOTEST"/head-name
 
 		echo $HEAD > "$DOTEST"/head
-		echo $UPSTREAM > "$DOTEST"/upstream
 		echo $ONTO > "$DOTEST"/onto
 		test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy
 		test t = "$VERBOSE" && : > "$DOTEST"/verbose
 		if test t = "$PRESERVE_MERGES"
 		then
+			lastsha1=
 			# $REWRITTEN contains files for each commit that is
-			# reachable by at least one merge base of $HEAD and
-			# $UPSTREAM. They are not necessarily rewritten, but
-			# their children might be.
-			# This ensures that commits on merged, but otherwise
-			# unrelated side branches are left alone. (Think "X"
-			# in the man page's example.)
+			# reachable on the way between $UPSTREAM and $HEAD.
+			# The filename is the SHA1 of the old value and the
+			# content is the SHA1 or :mark of the new one.
 			mkdir "$REWRITTEN" &&
 			for c in $(git merge-base --all $HEAD $UPSTREAM)
 			do
+				test -n "$lastsha1" || lastsha1=$c
 				echo $ONTO > "$REWRITTEN"/$c ||
 					die "Could not init rewritten commits"
 			done
-			MERGES_OPTION=
-		else
-			MERGES_OPTION=--no-merges
+			git rev-list --abbrev-commit --abbrev=7 \
+				--pretty=format:"%m%h	# %s" --topo-order \
+				--reverse --cherry-pick $UPSTREAM...$HEAD | \
+				sed -n -e "s/^>//p" > "$DOTEST"/commit-list
+
+			mark=0
+			while read -r sha1 rest
+			do
+				create_todo_preserving_merges
+			done < "$DOTEST"/commit-list > "$TODO"
+
+			# We now have more "mark :..." lines than needed.
+			# Remove the unused.  This is just a step to keep
+			# the list clean.
+			keep_marks=$(sed -e "/^mark :/d" <"$TODO" |
+				sed -n -e 's/^[^#]* :\([0-9][0-9]*\).*$/:\1:/p')
+			while read -r line
+			do
+				case "$line" in
+				'mark :'*)
+					case "$keep_marks " in
+					*${line#mark }:*)
+						echo "$line"
+						;;
+					esac
+					;;
+				*)
+					printf '%s\n' "$line"
+					;;
+				esac
+			done < "$TODO" > "$TODO".new
+			mv "$TODO".new "$TODO"
+		fi
+		if test -z "$PRESERVE_MERGES"
+		then
+			git rev-list --no-merges --abbrev-commit --abbrev=7 \
+				--pretty=format:"%mpick %h	# %s" \
+				--reverse --cherry-pick $UPSTREAM...$HEAD | \
+				sed -n -e "s/^>//p" > "$TODO"
 		fi
 
 		SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
 		SHORTHEAD=$(git rev-parse --short $HEAD)
 		SHORTONTO=$(git rev-parse --short $ONTO)
-		git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
-			--abbrev=7 --reverse --left-right --cherry-pick \
-			$UPSTREAM...$HEAD | \
-			sed -n "s/^>/pick /p" > "$TODO"
 		cat >> "$TODO" << EOF
 
 # Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
@@ -577,7 +353,7 @@ EOF
 			die_abort "Nothing to do"
 
 		git update-ref ORIG_HEAD $HEAD
-		output git checkout $ONTO && do_rest
+		run_sequencer --onto "$ONTO" "$TODO"
 		;;
 	esac
 	shift
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index ffe3dd9..506477b 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -159,19 +159,19 @@ test_expect_success 'stop on conflicting pick' '
 	git tag new-branch1 &&
 	test_must_fail git rebase -i master &&
 	test "$(git rev-parse HEAD~3)" = "$(git rev-parse master)" &&
-	test_cmp expect .git/rebase-merge/patch &&
 	test_cmp expect2 file1 &&
 	test "$(git-diff --name-status |
 		sed -n -e "/^U/s/^U[^a-z]*//p")" = file1 &&
-	test 4 = $(grep -v "^#" < .git/rebase-merge/done | wc -l) &&
-	test 0 = $(grep -c "^[^#]" < .git/rebase-merge/git-rebase-todo)
+	test 4 = $(grep -v "^#" < .git/sequencer/done | wc -l) &&
+	test 0 = $(grep -c "^[^#]" < .git/sequencer/todo) &&
+	test -d .git/rebase-merge
 '
 
 test_expect_success 'abort' '
 	git rebase --abort &&
 	test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
 	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
-	! test -d .git/rebase-merge
+	! test -d .git/sequencer
 '
 
 test_expect_success 'retain authorship' '
-- 
1.6.0.rc0.49.gd39f

^ permalink raw reply related

* git sequencer prototype
From: Stephan Beyer @ 2008-07-26  5:20 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Daniel Barkalow, Stephan Beyer

Hi,

for those who are interested in git-sequencer: here's the latest
prototype, that should be able to apply to master.

An outline of the differences to the last sequencer prototype patchset:
 - typofixes, minor bugfixes
 - simplifications (seen due to builtin-ification)
 - introduced --allow-dirty
   that is used by the git-am migration, to allow
   usage of git-am on dirty index
 - set ("keep") ORIG_HEAD; somehow experimental


In the last patchset I mentioned the issue, that the prototype is slow
as hell.  I know some bottlenecks, but I have not even tried to change
that, because this is no issue for the builtin.

I paste the experiments that I did on my test machine some time ago:

git-am: Apply 100 (trivial) patches
        original: 5.1s
 prototype-based: 17s  (wtf!)
   builtin-based: 2.8s

git-rebase--interactive: Pick 100 (trivial) commits
        original: 4.8s
 prototype-based: 10.1s
   builtin-based: 1.7s

Those times don't have any methodic value, it's just to get an impression.
Nevertheless some information about that:
 - performance was only tested one or two times
 - /proc/cpuinfo says my machine is an AMD 64 X2 with 4013 BogoMIPS
 - /bin/sh is dash (if the propaganda is true, bash is even slower)
 - the changes of the patches are equivalent to those of the commits

Regards,
  Stephan


Stephan Beyer (5):
  Add git-sequencer shell prototype
  Add git-sequencer documentation
  Add git-sequencer test suite (t3350)
  Migrate git-am to use git-sequencer
  Migrate rebase-i to sequencer

 .gitignore                      |    1 +
 Documentation/git-sequencer.txt |  676 +++++++++++++
 Makefile                        |    1 +
 command-list.txt                |    1 +
 git-am.sh                       |  632 +++++--------
 git-rebase--interactive.sh      |  436 ++-------
 git-rebase.sh                   |    7 +-
 git-sequencer.sh                | 2042 +++++++++++++++++++++++++++++++++++++++
 t/t3350-sequencer.sh            |  838 ++++++++++++++++
 t/t3404-rebase-interactive.sh   |    8 +-
 t/t4150-am.sh                   |    4 +-
 11 files changed, 3889 insertions(+), 757 deletions(-)
 create mode 100644 Documentation/git-sequencer.txt
 create mode 100755 git-sequencer.sh
 create mode 100755 t/t3350-sequencer.sh

^ permalink raw reply

* Re: git-scm.com
From: Scott Chacon @ 2008-07-26  4:55 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Stephan Beyer, git
In-Reply-To: <alpine.DEB.1.00.0807260506020.26810@eeepc-johanness>

On Fri, Jul 25, 2008 at 8:07 PM, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
> Hi,
>
> On Sat, 26 Jul 2008, Stephan Beyer wrote:
>
>> Johannes Schindelin wrote:
>> > I do not like the implication that Git eats trees.
>>
>> I still like the picture, though it can hurt environmentalists.
>
> It's not just environmentalists.  If I put myself in the shoes of a Git
> newbie, I would get the impression that Git eats my trees, i.e. destroys
> them.
>
> Very good first impression.
>
> Not,
> Dscho
>
>

I was a bit concerned about using the little guy too, but I've gotten
overall very good feedback about him - people seem to like him.  I
think it's good to have a little bit of illustration on a page.
However, as for your concerns, I think a) it's really hard to argue
that environmentalists would actually care what that thing is doing
and b) a newbie to Git will have no idea what a 'tree' is - that is
really only a sort of inside joke.  You would have to have been using
git for a good amount of time to know that 'eating a tree' would be a
bad thing.  That's why I've been telling people that he's _storing_
trees and that you don't want to be around when he 'gc --prune's :)

Scott "not top-posting" Chacon

^ permalink raw reply

* Re: Official Git Homepage change? Re: git-scm.com
From: Johannes Schindelin @ 2008-07-26  4:54 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Petr Baudis, Scott Chacon, git
In-Reply-To: <7vtzedmeqh.fsf@gitster.siamese.dyndns.org>

Hi,

On Fri, 25 Jul 2008, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> we may simply be too close to git, we may have been breathing git for 
> too long, and what feels the most natural thing to be taught first for 
> us may not be the best first thing to be taught to the new people (even 
> though they may eventually grow to think like we do when they become 
> proficient enough).

Yet, when I see obvious errors, I have an urge to correct them.  I know, 
it is wrong, it is not my itch, and I know I will get crap for it.  But I 
just cannot help myself...


Ciao,
Dscho

^ permalink raw reply

* Re: Official Git Homepage change? Re: git-scm.com
From: Junio C Hamano @ 2008-07-26  4:49 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Petr Baudis, Scott Chacon, git
In-Reply-To: <alpine.DEB.1.00.0807260627480.26810@eeepc-johanness>

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> On Fri, 25 Jul 2008, Junio C Hamano wrote:
>
>> It's also somewhat interesting to observe that several people I have 
>> never heard of in the git circle are simultaneously doing new git books, 
>> apparently never asking for much technical advice from core git people, 
>> by the way.
>
> FWIW my criticism in the same direction was met with ridicule, which does 
> not let me expect much of them.

Oh, mine was not a criticism but was just an observation.

Maybe the folks we consider as "git community members" are either too
narrow, or too detached from the "real user community", and it could be
that git books are better written without us.

I am not being sarcastic nor sardonic; we may simply be too close to git,
we may have been breathing git for too long, and what feels the most
natural thing to be taught first for us may not be the best first thing to
be taught to the new people (even though they may eventually grow to think
like we do when they become proficient enough).

^ permalink raw reply

* Re: [RFC/PATCH] merge-base: teach "git merge-base" to accept more than 2 arguments
From: Junio C Hamano @ 2008-07-26  4:37 UTC (permalink / raw)
  To: Christian Couder; +Cc: git, Miklos Vajna
In-Reply-To: <20080726055920.3a2fc8e7.chriscool@tuxfamily.org>

Christian Couder <chriscool@tuxfamily.org> writes:

> Before this patch "git merge-base" accepted only 2 arguments, so
> only merge bases between 2 references could be computed.
>
> The purpose of this patch is to make "git merge-base" accept more
> than 2 arguments so that the merge bases between the first given
> reference and all the other references can be computed.

I think this is a logical conclusion of merge_bases_many().  You need
tests and docs, though.

^ permalink raw reply

* Re: Official Git Homepage change? Re: git-scm.com
From: Johannes Schindelin @ 2008-07-26  4:28 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Petr Baudis, Scott Chacon, git
In-Reply-To: <7v4p6dnv5k.fsf@gitster.siamese.dyndns.org>

Hi,

On Fri, 25 Jul 2008, Junio C Hamano wrote:

> It's also somewhat interesting to observe that several people I have 
> never heard of in the git circle are simultaneously doing new git books, 
> apparently never asking for much technical advice from core git people, 
> by the way.

FWIW my criticism in the same direction was met with ridicule, which does 
not let me expect much of them.

Ciao,
Dscho

^ permalink raw reply

* Re: [RFC/PATCH] merge-base: teach "git merge-base" to accept more than 2 arguments
From: Johannes Schindelin @ 2008-07-26  4:25 UTC (permalink / raw)
  To: Christian Couder; +Cc: git, Junio C Hamano, Miklos Vajna
In-Reply-To: <20080726055920.3a2fc8e7.chriscool@tuxfamily.org>

Hi,

On Sat, 26 Jul 2008, Christian Couder wrote:

> 	I suspect this patch may be usefull to simplify my
> 	"bisect: add merge bases check" series and perhaps
> 	also generally usefull.

s/usefull/useful/

> diff --git a/builtin-merge-base.c b/builtin-merge-base.c
> index 1cb2925..9c41849 100644
> --- a/builtin-merge-base.c
> +++ b/builtin-merge-base.c
> @@ -2,9 +2,14 @@
>  #include "cache.h"
>  #include "commit.h"
>  
> -static int show_merge_base(struct commit *rev1, struct commit *rev2, int show_all)
> +static struct commit *rev1, **prev2;
> +static size_t prev2_nr, prev2_alloc;
> +
> +
> +static int show_merge_base(int show_all)
>  {

Changing the arguments to be static variables?

NACK!

Ciao,
Dscho

^ permalink raw reply

* [PATCH] Documentation/git-submodule.txt: fix doubled word
From: Cesar Eduardo Barros @ 2008-07-26  4:17 UTC (permalink / raw)
  To: git, gitster; +Cc: Cesar Eduardo Barros

Signed-off-by: Cesar Eduardo Barros <cesarb@cesarb.net>
---
 Documentation/git-submodule.txt |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt
index 829b032..35efeef 100644
--- a/Documentation/git-submodule.txt
+++ b/Documentation/git-submodule.txt
@@ -63,7 +63,7 @@ COMMANDS
 add::
 	Add the given repository as a submodule at the given path
 	to the changeset to be committed next to the current
-	project: the current project is termed termed the "superproject".
+	project: the current project is termed the "superproject".
 +
 This requires two arguments: <repository> and <path>.
 +
-- 
1.5.6.4

^ permalink raw reply related

* Re: Official Git Homepage change? Re: git-scm.com
From: Junio C Hamano @ 2008-07-26  4:09 UTC (permalink / raw)
  To: Petr Baudis; +Cc: Scott Chacon, git
In-Reply-To: <20080726020951.GV32184@machine.or.cz>

Petr Baudis <pasky@suse.cz> writes:

> .... Of course, I would be transferring the control of the homepage
> from my hands so I would like to poll the community about how do people
> feel about this - opinion of core Git contributors would be especially
> welcome...
> ...
>   - The new site is affiliated with a commercial entity - GitHub.
> The website maintainer also has commercial interest in some published
> Git learning materials, which might generate certain conflict of
> interests; we must trust them that they handle this well.
>   - Both GitHub and Scott seem to be rather distanced from the "core"
> Git development community. This might or might not be an issue.

These two are directly related.  They might be friendly and well-meaning
folks, but I agree that they haven't earned our trust yet.

But I do not think it matters that much.

The thing is, git.or.cz may have been the closest thing to the "official"
homepage we have had, but that is not because Linus or I or Shawn declared
the site is official and/or that the site is trustworthy.  It was because
you put efforts preparing the contents worthy to be one-stop shop for git
related information, back when there was no such thing.  And the members
of the comminity found it a good site.  And you have the wiki there, where
there truly have been community participation to enhance the contents.

For me personally, pages outside the wiki have never felt like "the
official git homepage", not because the contents you prepared were
inadequate, but because I did not see much community participation to help
enrich it.

So I wish the new site success, but the definition of success from my
point of view is not how many random visitors it will attract, but how
well the site makes the contributors (both to git software itself, and to
the site's contents) feel welcomed.  Maybe in time it will become
successful enough by _my_ definition of success, and I may recommend
kernel.org folks to point at it from http://git.kernel.org/ (link with
text "overview") if/when that happens, and I may start mentioning them in
the "Note".  We'll see.

>   The negatives section writeup is longer, but in fact I think the
> positives win here; I also have a bit of bad conscience about not giving
> git.or.cz the amount of time it would deserve...

Let me thank you for maintaining not just git.or.cz/ but also repo.or.cz/
and the wiki.  I personally never visited the "Homepage" but the
repositories and the wiki are valuable services you gave back to the
community.

It's also somewhat interesting to observe that several people I have never
heard of in the git circle are simultaneously doing new git books,
apparently never asking for much technical advice from core git people, by
the way.

^ permalink raw reply

* [RFC/PATCH] merge-base: teach "git merge-base" to accept more than 2 arguments
From: Christian Couder @ 2008-07-26  3:59 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Miklos Vajna

Before this patch "git merge-base" accepted only 2 arguments, so
only merge bases between 2 references could be computed.

The purpose of this patch is to make "git merge-base" accept more
than 2 arguments so that the merge bases between the first given
reference and all the other references can be computed.

This is easily implemented because the "get_merge_bases_many"
function in "commit.c" already implements the computation.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 builtin-merge-base.c |   51 ++++++++++++++++++++++++++++++++++++-------------
 commit.h             |    2 +
 2 files changed, 39 insertions(+), 14 deletions(-)

	I suspect this patch may be usefull to simplify my
	"bisect: add merge bases check" series and perhaps
	also generally usefull.

	I will add documentation and perhaps tests too if
	people are ok with something like that.

	By the way perhaps such a patch has already been
	posted by someone else. In this case sorry for the
	noise and thanks for any pointer to the previous
	patch.

diff --git a/builtin-merge-base.c b/builtin-merge-base.c
index 1cb2925..9c41849 100644
--- a/builtin-merge-base.c
+++ b/builtin-merge-base.c
@@ -2,9 +2,14 @@
 #include "cache.h"
 #include "commit.h"
 
-static int show_merge_base(struct commit *rev1, struct commit *rev2, int show_all)
+static struct commit *rev1, **prev2;
+static size_t prev2_nr, prev2_alloc;
+
+
+static int show_merge_base(int show_all)
 {
-	struct commit_list *result = get_merge_bases(rev1, rev2, 0);
+	struct commit_list *result = get_merge_bases_many(rev1, prev2_nr,
+							  prev2, 0);
 
 	if (!result)
 		return 1;
@@ -20,12 +25,24 @@ static int show_merge_base(struct commit *rev1, struct commit *rev2, int show_al
 }
 
 static const char merge_base_usage[] =
-"git merge-base [--all] <commit-id> <commit-id>";
+"git merge-base [--all] <commit-id> <commit-id>...";
+
+static void append_rev2(struct commit *rev)
+{
+	ALLOC_GROW(prev2, prev2_nr + 1, prev2_alloc);
+	prev2[prev2_nr++] = rev;
+}
+
+static struct commit *get_commit_reference(const char *arg)
+{
+	unsigned char revkey[20];
+	if (get_sha1(arg, revkey))
+		die("Not a valid object name %s", arg);
+	return lookup_commit_reference(revkey);
+}
 
 int cmd_merge_base(int argc, const char **argv, const char *prefix)
 {
-	struct commit *rev1, *rev2;
-	unsigned char rev1key[20], rev2key[20];
 	int show_all = 0;
 
 	git_config(git_default_config, NULL);
@@ -38,15 +55,21 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix)
 			usage(merge_base_usage);
 		argc--; argv++;
 	}
-	if (argc != 3)
+	if (argc < 3)
 		usage(merge_base_usage);
-	if (get_sha1(argv[1], rev1key))
-		die("Not a valid object name %s", argv[1]);
-	if (get_sha1(argv[2], rev2key))
-		die("Not a valid object name %s", argv[2]);
-	rev1 = lookup_commit_reference(rev1key);
-	rev2 = lookup_commit_reference(rev2key);
-	if (!rev1 || !rev2)
+
+	rev1 = get_commit_reference(argv[1]);
+	if (!rev1)
 		return 1;
-	return show_merge_base(rev1, rev2, show_all);
+	argc--; argv++;
+
+	do {
+		struct commit *rev2 = get_commit_reference(argv[1]);
+		if (!rev2)
+			return 1;
+		append_rev2(rev2);
+		argc--; argv++;
+	} while (argc > 1);
+
+	return show_merge_base(show_all);
 }
diff --git a/commit.h b/commit.h
index 77de962..4829a5c 100644
--- a/commit.h
+++ b/commit.h
@@ -121,6 +121,8 @@ int read_graft_file(const char *graft_file);
 struct commit_graft *lookup_commit_graft(const unsigned char *sha1);
 
 extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
+extern struct commit_list *get_merge_bases_many(struct commit *one,
+		int n, struct commit **twos, int cleanup);
 extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
 
 extern int register_shallow(const unsigned char *sha1);
-- 
1.6.0.rc0.43.g62478.dirty

^ permalink raw reply related

* Re: [PATCH] Remove references to git-fetch-pack from "git clone" documentation.
From: Johannes Schindelin @ 2008-07-26  3:24 UTC (permalink / raw)
  To: Steve Haslam; +Cc: git
In-Reply-To: <1217011068-1675-1-git-send-email-shaslam@lastminute.com>

Hi,

On Fri, 25 Jul 2008, Steve Haslam wrote:

> "git clone" no longer calls "git-fetch-pack",

So it calls it internally, avoiding fork() and exec().  But the code is 
still git-fetch-pack's.  The difference should be lost on the regular Git 
user.

Ciao,
Dscho

^ permalink raw reply

* Re: Bizarre missing changes (git bug?)
From: Roman Zippel @ 2008-07-26  3:12 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Tim Harper, git
In-Reply-To: <alpine.LFD.1.10.0807211331390.31863@woody.linux-foundation.org>

Hi,

On Monday 21. July 2008, Linus Torvalds wrote:

> Read up on '--full-history'.
>
> By default, git simplifies the history for logs that have path
> simplification: only walking down the side of a merge that all the data
> came from (ie the unchanged side). So it only leaves merges around if
> there was changes from _all_ parents.
>
> So without --full-history, if any parent matches the state, it just
> removes the merge and picks that parent that contained all the state.
> Obviously, any changes to that file can be sufficiently explained by
> walking just that limited history, because they must have changed in
> _that_ history too!

Is that really a good default behaviour? Is there a way to change that 
default?

I'm currently looking into converting the m68k CVS repository and I'd like to 
properly regenerate it as two separate lines of development. The problem is 
if I look at the file history, I often only see one side of the changes when 
things got merged because of this default.
What makes this worse is that graphical front ends may inherit this behaviour. 
I tested this with qgit and it only shows half of the history. giggle 
retrieves the history like --full-history, but it lacks empty merges, so it 
makes it harder to see what got merged when.
For example a history that actually looks this:

linux -+-----import----+-----------import----+------...
m68k   \-commit-commit-\-merge-commit-commit-\-merge...

Without the merges it looks like this:

linux -+-----import----------------import--------------+...
m68k   \-commit-commit---------commit-commit           \...

And without --full-history these "loose ends" aren't visible as in qgit.

When researching historical changes one wants to know when something was 
introduced and when it was merged, but this simplification makes it harder 
than it really has to be.
IMO the default should be to show the complete history, so one doesn't miss 
something by accident that might be important or as the original example 
shows it might be confusing if one sees a change with "git log --stat id.." 
and the change disappears when one looks at "git log path".

bye, Roman

^ permalink raw reply

* Re: [PATCH 1/2] Move launch_editor() from builtin-tag.c to editor.c
From: Stephan Beyer @ 2008-07-26  3:14 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, Junio C Hamano
In-Reply-To: <alpine.DEB.1.00.0807260456360.26810@eeepc-johanness>

Hi,

Johannes Schindelin wrote:
> On Fri, 25 Jul 2008, Stephan Beyer wrote:
> 
> > To be kind to the maintainer, I've also run the test suite again, all 
> > tests passed except t4116*.sh, but this is not my fault. It's the fault 
> > of 9a885fac.
> 
> You do understand that you cost everybody, who actually cared to take a 
> look for herself, a few minutes?

Yes, now I see that I forgot to mention the subject.

I'm sorry.

> Dscho "who thinks that so many mails would be better if the posters would 
> read the mails themselves and try to imagine how readers would perceive 
> them"

You're right.

In my case I perhaps should've *first* looked if I could fix the TAR issue
and then first send the fix for the TAR stuff and then remove the text from
here. But I noticed the failure, wrote about it here, sent this mail, looked
for the reason of the error, ... the wrong order, as it seems.

I try to improve.

Regards,
  Stephan

-- 
Stephan Beyer <s-beyer@gmx.net>, PGP 0x6EDDD207FCC5040F

^ permalink raw reply

* Re: git-scm.com
From: Johannes Schindelin @ 2008-07-26  3:07 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: Scott Chacon, git
In-Reply-To: <20080726025402.GF13539@leksak.fem-net>

Hi,

On Sat, 26 Jul 2008, Stephan Beyer wrote:

> Johannes Schindelin wrote:
> > I do not like the implication that Git eats trees.
> 
> I still like the picture, though it can hurt environmentalists.

It's not just environmentalists.  If I put myself in the shoes of a Git 
newbie, I would get the impression that Git eats my trees, i.e. destroys 
them.

Very good first impression.

Not,
Dscho

^ permalink raw reply

* Re: [PATCH] index-pack: correctly initialize appended objects
From: Johannes Schindelin @ 2008-07-26  3:04 UTC (permalink / raw)
  To: Björn Steinbrink; +Cc: Junio C Hamano, Nicolas Pitre, spearce, git
In-Reply-To: <20080725171315.GA27285@atjola.homenet>

[-- Attachment #1: Type: TEXT/PLAIN, Size: 213 bytes --]

Hi,

On Fri, 25 Jul 2008, Björn Steinbrink wrote:

> +	// This object comes from outside the thin pack, so we need to
> +	// initialize the size and type fields

Do not use C++ comments in Git.  Ever.

Ciao,
Dscho

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox