* Re: [PATCH 0/7] Some superficial documentation changes
From: Junio C Hamano @ 2008-07-01 8:42 UTC (permalink / raw)
To: Jonathan Nieder; +Cc: git, Christian Couder, Nguyen Thai Ngoc Duy, Jon Loeliger
In-Reply-To: <Pine.GSO.4.62.0806301650530.7190@harper.uchicago.edu>
Jonathan Nieder <jrnieder@uchicago.edu> writes:
> But at least I was able to check that patch 6/7 (which redistributes the
> dashes in the world) doesn't touch anything but spaces and hyphens.
> Hopefully that should make this easier to review.
Yes, the earlier "line break adjustments" patch really helped.
I've queued all of them to 'pu' while reading them over, except for the
last one. I did not look very carefully, but the parts I did look at made
sense.
In general, I've always preferred to see command names "git-foo" typeset
in teletype face, consistent with examples (also typeset in tt), because
they are both something the end users are expected to type. With this
transition, we are however making "git-foo" not something users are
expected to type, which means that the use of a typeface that is different
from the body text to spell command names is now strictly for making them
stand out in context. In that sense, I share your hesitation about the
last one to use tt for command names. It probably is better to use italic
now.
On my next git day (coming Wednesday, if nothing goes wrong at day job),
I'll merge all except the last one to 'master' so that the HTML version of
the manual page k.org serves to the general public is updated and we can
get a wider exposure to looking for conversion errors more easily. If you
also prefer 'italics' and list agrees, it might be a better idea to also
apply that patch when I do so, as that step is the most error prone one
and benefits from more eyeballs.
Thanks.
^ permalink raw reply
* Re: Patches for qgit on MacOS X
From: Olivier Croquette @ 2008-07-01 8:05 UTC (permalink / raw)
To: Marco Costalba; +Cc: git
In-Reply-To: <e5bfff550806300300s2a0c0e60sdaa86453116c531f@mail.gmail.com>
Marco Costalba wrote, On 30/06/08 12:00:
> Thanks for the patches, I'll apply them.
Great!
> Can I ask why don't you use qgit-2.X series ?
Sure you can, it is just because I overlooked the existence of the
version 2 :)
After compiling qt4-mac from source (it took quite some time), I have
been able to compile qgit2 too without a hitch, so I guess the patch is
irrelevant for qgit2. Are you using a different mechanism to access the
environment?
^ permalink raw reply
* Re: [PATCH] Make default expiration period of reflog used for stash infinite
From: Junio C Hamano @ 2008-07-01 7:28 UTC (permalink / raw)
To: Olivier Marin; +Cc: git
In-Reply-To: <4869700C.6060803@free.fr>
Olivier Marin <dkr+ml.git@free.fr> writes:
> Junio C Hamano a écrit :
>> This makes the default expiration period for the reflog that implements
>> stash infinite.
>
> I did not read the whole thread so maybe I missed something but I though you
> wanted to apply Nanako's patch before?
>
> The patch: http://article.gmane.org/gmane.comp.version-control.git/85055
Thanks for reminding, but I am of two minds about the change.
(1) The change would untie the base tree of the stash from the history
behind it and allow previously rewound tips of branches that these
stashes were built on top of. Without the patch, these otherwise
unreachable commits will never be reclaimed.
(2) Today, you can say "git log stash" (note the lack of "-g" option) to
view the history behind the stash through two artificial commits that
stash creates. This will become impossible with the patch.
Probably I am worrying too much; I do not personally think the second
point matters in the real life. If "git log stash" _were_ any useful,
it means the history behind the stash entries are not useless at all, but
in that case the user would be using regular branches to store them
anyway.
^ permalink raw reply
* Re: [PATCH 14/14] Build in merge
From: Junio C Hamano @ 2008-07-01 7:27 UTC (permalink / raw)
To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin
In-Reply-To: <5e65b37998d1fdd9d314e48cea2cf67fd73ba8cd.1214879690.git.vmiklos@frugalware.org>
Miklos Vajna <vmiklos@frugalware.org> writes:
> + /* See if remote matches <name>~<number>, or <name>^ */
> + ptr = strrchr(remote, '^');
> + if (ptr && ptr[1] == '\0') {
> + for (len = 0, ptr = remote + strlen(remote);
> + remote < ptr && ptr[-1] == '^';
> + ptr--)
> + len++;
> + }
> + else {
> + ptr = strrchr(remote, '~');
> + if (ptr && ptr[1] != '0' && isdigit(ptr[1])) {
> + len = ptr-remote;
> + ptr++;
> + for (ptr++; *ptr; ptr++)
> + if (!isdigit(*ptr)) {
> + len = 0;
> + break;
> + }
> + }
> + }
I still have problems with the above. I'd write it this way:
int len, early;
...
/* See if remote matches <name>^^^.. or <name>~<number> */
for (len = 0, ptr = remote + strlen(remote);
remote < ptr && ptr[-1] == '^';
ptr--)
len++;
if (len)
early = 1;
else {
early = 0;
ptr = strrchr(remote, '~');
if (ptr) {
int seen_nonzero = 0;
len++; /* count ~ */
while (*++ptr && isdigit(*ptr)) {
seen_nonzero |= (*ptr != '0');
len++;
}
if (*ptr)
len = 0; /* not ...~<number> */
else if (seen_nonzero)
early = 1;
else if (len == 1)
early = 1; /* "name~" is "name~1"! */
}
}
if (len) {
struct strbuf truname = STRBUF_INIT;
strbuf_addstr(&truname, "refs/heads/");
strbuf_addstr(&truname, remote);
strbuf_setlen(&truname, len+11);
if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
strbuf_addf(msg,
"%s\t\tbranch '%s'%s of .\n",
sha1_to_hex(remote_head->sha1),
truname.buf,
(early ? " (early part)" : ""));
return;
}
}
The first loop is obvious. If the tail end is ^, we set "len" and see if
the remainder is a branch name (and if that is the case we are always
talking about an early part of it of the branch).
Otherwise, we do want to say "early part" if "$name~<number>" is given,
and another special case is "$name~" which is "$name~1" these days. As
long as number is not zero we would want to say "early part". Otherwise
we would want to say it is a branch itself, not its early part.
I'll queue the fixed-up result in 'pu', but I have to tend to other topics
before I can actually publish. Together with the fix to "head_invalid"
confusion I mentioned in another message squashed in to this commit, all
the tests now finally seem to pass on the topic branch.
Oh, by the way, you sent this and the previous round without marking them
as RFC nor WIP, even though they obviously did not even pass the test
suite. For example, without the head_invalid fix, anything that runs
merge on detached head, most notably "git rebase -i", would not work at
all.
^ permalink raw reply
* Re: [PATCH 14/14] Build in merge
From: Junio C Hamano @ 2008-07-01 6:23 UTC (permalink / raw)
To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin
In-Reply-To: <5e65b37998d1fdd9d314e48cea2cf67fd73ba8cd.1214879690.git.vmiklos@frugalware.org>
Miklos Vajna <vmiklos@frugalware.org> writes:
> +static int read_tree_trivial(unsigned char *common, unsigned char *head,
> + unsigned char *one)
> +{
> + int i, nr_trees = 0;
> + struct tree *trees[MAX_UNPACK_TREES];
> + struct tree_desc t[MAX_UNPACK_TREES];
> + struct unpack_trees_options opts;
> +
> + memset(&opts, 0, sizeof(opts));
> + opts.head_idx = 2;
Do you still need this assignment here?
> +int cmd_merge(int argc, const char **argv, const char *prefix)
> +{
> + unsigned char result_tree[20];
> + struct strbuf buf;
> + const char *head_arg;
> + int flag, head_invalid = 0, i;
> + int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
> + struct commit_list *common = NULL;
> + struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
> + struct commit_list **remotes = &remoteheads;
> +
> + setup_work_tree();
> + if (unmerged_cache())
> + die("You are in the middle of a conflicted merge.");
> +
> + /*
> + * Check if we are _not_ on a detached HEAD, i.e. if there is a
> + * current branch.
> + */
> + branch = resolve_ref("HEAD", head, 0, &flag);
> + if (branch && flag & REF_ISSYMREF) {
> + const char *ptr = skip_prefix(branch, "refs/heads/");
> + if (ptr)
> + branch = ptr;
> + } else
> + head_invalid = 1;
Wait a minute... Are you calling a detached HEAD as head_invalid here? I
am not too much worried about variable naming, but you later do ...
> + if (!have_message && is_old_style_invocation(argc, argv)) {
> ...
> + } else if (head_invalid) {
> + struct object *remote_head;
> + /*
> + * If the merged head is a valid one there is no reason
> + * to forbid "git merge" into a branch yet to be born.
> + * We do the same for "git pull".
> + */
> + if (argc != 1)
> + die("Can merge only exactly one commit into "
> + "empty head");
Which is about HEAD pointing at a branch that isn't born yet. They are
two very different concepts.
Either the above "else if (head_invalid)" is wrong, or more likely the
setting of head_invalid we saw earlier is wrong.
Probably what you meant was:
- "char *branch" points at either "HEAD" (when detached) or
the name of the branch (e.g. "master" when "refs/heads/master");
- "unsigned char head[]" stores the commit object name of the
current HEAD (or 0{40} if the current branch is unborn);
- set head_invalid to true only when the current branch is unborn.
So perhaps...
branch = resolve_ref("HEAD", head, 0, &flag);
if (branch && (flag & REF_ISSYMREF) && !prefixcmp(branch, "refs/heads/"))
branch += 11;
head_invalid = is_null_sha1(head);
And probably we can even drop (flag & REF_ISSYMREF) from the above safely.
Do we even care if the head is detached in this program? I doubt it.
> ...
> + strbuf_init(&msg, 0);
> + strbuf_addstr(&msg, "Fast forward");
> + if (have_message)
> + strbuf_addstr(&msg,
> + " (no commit created; -m option ignored)");
> + o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
> + 0, NULL, OBJ_COMMIT);
> + if (!o)
> + return 1;
> +
> + if (checkout_fast_forward(head, remoteheads->item->object.sha1))
> + return 1;
Not exiting with 0 status from around here upon error is an improvement,
but does the user see sensible error messages in addition to the exit
status?
Getting warmer ;-)
^ permalink raw reply
* Re: [PATCH 13/14] git-commit-tree: make it usable from other builtins
From: Junio C Hamano @ 2008-07-01 5:50 UTC (permalink / raw)
To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin
In-Reply-To: <666ec9b342a0c3254ae8f917d5bd2dea36314f08.1214878711.git.vmiklos@frugalware.org>
Miklos Vajna <vmiklos@frugalware.org> writes:
> This patch is against master, you need to pull from me or first rebase
> mv/merge-in-c against current master.
That's a bad practice. It makes it much harder to review the incremental
changes from the previous round.
I'll cope, though.
^ permalink raw reply
* Re: [PATCH 13/14] git-commit-tree: make it usable from other builtins
From: Johannes Schindelin @ 2008-07-01 5:07 UTC (permalink / raw)
To: Miklos Vajna; +Cc: Junio C Hamano, git, Olivier Marin
In-Reply-To: <666ec9b342a0c3254ae8f917d5bd2dea36314f08.1214878711.git.vmiklos@frugalware.org>
Hi,
On Tue, 1 Jul 2008, Miklos Vajna wrote:
> I did it this way to avoid an unnecessary conflict with Dscho's recent
> patch to commit-tree (ef98c5c).
Oh, sorry!
Funnily enough, I committed and tested this change in my clone of
builtin-merge before I realized that I am in the wrong directory ;-)
Ciao,
Dscho
^ permalink raw reply
* [JGIT PATCH 3/8] Refuse to create or delete funny ref names over dumb transports
From: Shawn O. Pearce @ 2008-07-01 3:04 UTC (permalink / raw)
To: Robin Rosenberg, Marek Zawirski; +Cc: git
In-Reply-To: <1214881445-3931-3-git-send-email-spearce@spearce.org>
If the RemoteRefUpdate requests us to create a remote name of just
"master" (say due to a bug in the push command line tool) we would do
just that, creating "$project.git/master" on the remote side. This is
not a valid ref for Git and confusion ensues when C Git tries to
operate on the same repository.
Normally these sorts of bad refs are blocked by git-receive-pack
on the remote side, but here we don't have that so we must do the
blocking as part of the push connection.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
.../spearce/jgit/transport/WalkPushConnection.java | 7 +++++++
1 files changed, 7 insertions(+), 0 deletions(-)
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/WalkPushConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/WalkPushConnection.java
index 5450b84..e11b85a 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/WalkPushConnection.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/WalkPushConnection.java
@@ -129,6 +129,13 @@ class WalkPushConnection extends BaseConnection implements PushConnection {
//
final List<RemoteRefUpdate> updates = new ArrayList<RemoteRefUpdate>();
for (final RemoteRefUpdate u : refUpdates.values()) {
+ final String n = u.getRemoteName();
+ if (!n.startsWith("refs/") || !Repository.isValidRefName(n)) {
+ u.setStatus(Status.REJECTED_OTHER_REASON);
+ u.setMessage("funny refname");
+ continue;
+ }
+
if (AnyObjectId.equals(ObjectId.zeroId(), u.getNewObjectId()))
deleteCommand(u);
else
--
1.5.6.74.g8a5e
^ permalink raw reply related
* [JGIT PATCH 6/8] Support 'git upload-pack' and 'git receive-pack' over SSH
From: Shawn O. Pearce @ 2008-07-01 3:04 UTC (permalink / raw)
To: Robin Rosenberg, Marek Zawirski; +Cc: git
In-Reply-To: <1214881445-3931-6-git-send-email-spearce@spearce.org>
Within the next 6 months C git clients will begin asking remote
servers for 'git $command' rather than 'git-$command' when using the
SSH transport. This change is to allow the C git programs to be
removed from the user's $PATH, leaving only the git wrapper binary.
For the first 6 months after C git 1.6.0 gets released clients will
continue to ask for 'git-$command' but users may change that behavior
by specifically asking for 'git $command' in remote.$name.uploadpack
or remote.$name.receivepack. Later clients (including jgit) will
change to ask for 'git $command' by default.
If we are asking for 'git $command' we cannot quote this as a single
command with a space in the path. We split on the whitespace and
quote both sides (if necessary) to protect the strings from the shell.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
.../spearce/jgit/transport/TransportGitSsh.java | 8 +++++++-
1 files changed, 7 insertions(+), 1 deletions(-)
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java
index caf531d..1bbdf04 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java
@@ -182,7 +182,13 @@ class TransportGitSsh extends PackTransport {
path = (uri.getPath().substring(1));
final StringBuilder cmd = new StringBuilder();
- sqMinimal(cmd, exe);
+ final int gitspace = exe.indexOf("git ");
+ if (gitspace >= 0) {
+ sqMinimal(cmd, exe.substring(0, gitspace + 3));
+ cmd.append(' ');
+ sqMinimal(cmd, exe.substring(gitspace + 4));
+ } else
+ sqMinimal(cmd, exe);
cmd.append(' ');
sqAlways(cmd, path);
channel.setCommand(cmd.toString());
--
1.5.6.74.g8a5e
^ permalink raw reply related
* [JGIT PATCH 4/8] Shorten progress message text from PackWriter
From: Shawn O. Pearce @ 2008-07-01 3:04 UTC (permalink / raw)
To: Robin Rosenberg, Marek Zawirski; +Cc: git
In-Reply-To: <1214881445-3931-4-git-send-email-spearce@spearce.org>
There are traditionally three phases to a pack algorithm:
the enumeration phase, commonly called "Counting objects"
the delta search phase, commonly called "Compressing objects"
the output phase, commonly called "Writing objects"
Our prior messages were too long for the text progress display
and caused the progress meters to not line up vertically on the
console. The shorter (and more traditional) text does fit into
the column structure used by the text progress display.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
.../src/org/spearce/jgit/lib/PackWriter.java | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/PackWriter.java b/org.spearce.jgit/src/org/spearce/jgit/lib/PackWriter.java
index 9a427da..0439656 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/PackWriter.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/PackWriter.java
@@ -102,7 +102,7 @@ public class PackWriter {
*
* @see #preparePack(Collection, Collection, boolean, boolean)
*/
- public static final String COUNTING_OBJECTS_PROGRESS = "Counting objects to pack";
+ public static final String COUNTING_OBJECTS_PROGRESS = "Counting objects";
/**
* Title of {@link ProgressMonitor} task used during searching for objects
@@ -110,7 +110,7 @@ public class PackWriter {
*
* @see #writePack(OutputStream)
*/
- public static final String SEARCHING_REUSE_PROGRESS = "Searching for delta and object reuse";
+ public static final String SEARCHING_REUSE_PROGRESS = "Compressing objects";
/**
* Title of {@link ProgressMonitor} task used during writing out pack
--
1.5.6.74.g8a5e
^ permalink raw reply related
* [JGIT PATCH 2/8] Delete reflog when deleting ref during dumb transport push
From: Shawn O. Pearce @ 2008-07-01 3:03 UTC (permalink / raw)
To: Robin Rosenberg, Marek Zawirski; +Cc: git
In-Reply-To: <1214881445-3931-2-git-send-email-spearce@spearce.org>
If we remove a ref we must also remove the reflog.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
.../spearce/jgit/transport/WalkPushConnection.java | 7 +++++++
.../jgit/transport/WalkRemoteObjectDatabase.java | 13 +++++++++++++
2 files changed, 20 insertions(+), 0 deletions(-)
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/WalkPushConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/WalkPushConnection.java
index f63bedd..5450b84 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/WalkPushConnection.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/WalkPushConnection.java
@@ -280,6 +280,13 @@ class WalkPushConnection extends BaseConnection implements PushConnection {
u.setMessage(e.getMessage());
}
}
+
+ try {
+ dest.deleteRefLog(u.getRemoteName());
+ } catch (IOException e) {
+ u.setStatus(Status.REJECTED_OTHER_REASON);
+ u.setMessage(e.getMessage());
+ }
}
private void updateCommand(final RemoteRefUpdate u) {
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/WalkRemoteObjectDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/transport/WalkRemoteObjectDatabase.java
index 1e13b33..b99144a 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/WalkRemoteObjectDatabase.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/WalkRemoteObjectDatabase.java
@@ -269,6 +269,19 @@ abstract class WalkRemoteObjectDatabase {
}
/**
+ * Delete a reflog from the remote repository.
+ *
+ * @param name
+ * name of the ref within the ref space, for example
+ * <code>refs/heads/pu</code>.
+ * @throws IOException
+ * deletion is not supported, or deletion failed.
+ */
+ void deleteRefLog(final String name) throws IOException {
+ deleteFile("../logs/" + name);
+ }
+
+ /**
* Overwrite (or create) a loose ref in the remote repository.
* <p>
* This method creates any missing parent directories, if necessary.
--
1.5.6.74.g8a5e
^ permalink raw reply related
* [JGIT PATCH 8/8] Don't try to pack 0{40} during push of delete and update
From: Shawn O. Pearce @ 2008-07-01 3:04 UTC (permalink / raw)
To: Robin Rosenberg, Marek Zawirski; +Cc: git
In-Reply-To: <1214881445-3931-8-git-send-email-spearce@spearce.org>
`jgit push origin :refs/heads/die refs/heads/master` tries to pack
0{40} to delete branch "die" while also packing the objects needed
to update branch master. Since "die" is being removed we do not
want to pack any objects for it, as there is nothing to pack.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
.../jgit/transport/BasePackPushConnection.java | 6 ++++--
1 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackPushConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackPushConnection.java
index 7ae3aa7..784a578 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackPushConnection.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackPushConnection.java
@@ -169,8 +169,10 @@ class BasePackPushConnection extends BasePackConnection implements
for (final Ref r : getRefs())
remoteObjects.add(r.getObjectId());
- for (final RemoteRefUpdate r : refUpdates.values())
- newObjects.add(r.getNewObjectId());
+ for (final RemoteRefUpdate r : refUpdates.values()) {
+ if (!ObjectId.zeroId().equals(r.getNewObjectId()))
+ newObjects.add(r.getNewObjectId());
+ }
writer.preparePack(newObjects, remoteObjects, thinPack, true);
writer.writePack(out);
--
1.5.6.74.g8a5e
^ permalink raw reply related
* [JGIT PATCH 7/8] Pack jgit into a portable single file command line utility
From: Shawn O. Pearce @ 2008-07-01 3:04 UTC (permalink / raw)
To: Robin Rosenberg, Marek Zawirski; +Cc: git
In-Reply-To: <1214881445-3931-7-git-send-email-spearce@spearce.org>
On UNIX (and even Cygwin) we can package the entire jgit library into
a single JAR file and prefix that JAR with a shell script to start the
JVM, passing in the script/JAR file as the only classpath. This trick
works because the table of contents for the ZIP file is at the end of
the stream, and the JVM tends to only do random access through that
table when it looks up classes for loading.
A tiny shell script called make_jgit.sh compiles everything with the
command line javac and packages it into the portable jgit shell
script, making it possible to build and use jgit without touching
either Eclipse or Maven.
Currently this is quite easy to implement as we have only one support
library (JSch), but things may get more complicated if we add another
JAR to our classpath.
With this I can install JGit into my personal account and make use
of it on a daily basis from the command line:
./make_jgit.sh
mv jgit ~/bin
jgit push ...
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
.gitignore | 1 +
jgit | 37 -------------------------------------
jgit.sh | 45 +++++++++++++++++++++++++++++++++++++++++++++
make_jgit.sh | 26 ++++++++++++++++++++++++++
4 files changed, 72 insertions(+), 37 deletions(-)
create mode 100644 .gitignore
delete mode 100755 jgit
create mode 100755 jgit.sh
create mode 100755 make_jgit.sh
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0763732
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/jgit
diff --git a/jgit b/jgit
deleted file mode 100755
index be7df7e..0000000
--- a/jgit
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/sh
-
-jgit_home=`dirname $0`
-cp="$jgit_home/org.spearce.jgit/bin"
-cp="$cp:$jgit_home/org.spearce.jgit/lib/jsch-0.1.37.jar"
-unset jgit_home
-java_args=
-
-# Cleanup paths for Cygwin.
-#
-case "`uname`" in
-CYGWIN*)
- cp=`cygpath --windows --mixed --path "$cp"`
- ;;
-Darwin)
- if test -e /System/Library/Frameworks/JavaVM.framework
- then
- java_args='
- -Dcom.apple.mrj.application.apple.menu.about.name=JGit
- -Dcom.apple.mrj.application.growbox.intrudes=false
- -Dapple.laf.useScreenMenuBar=true
- -Xdock:name=JGit
- '
- fi
- ;;
-esac
-
-CLASSPATH="$cp"
-export CLASSPATH
-
-java=java
-if test -n "$JAVA_HOME"
-then
- java="$JAVA_HOME/bin/java"
-fi
-
-exec "$java" $java_args org.spearce.jgit.pgm.Main "$@"
diff --git a/jgit.sh b/jgit.sh
new file mode 100755
index 0000000..84927bc
--- /dev/null
+++ b/jgit.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+if [ "@@use_self@@" = "1" ]
+then
+ this_script=`which "$0" 2>/dev/null`
+ [ $? -gt 0 -a -f "$0" ] && this_script="$0"
+ cp=$this_script
+else
+ jgit_home=`dirname $0`
+ cp="$jgit_home/org.spearce.jgit/bin"
+ cp="$cp:$jgit_home/org.spearce.jgit/lib/jsch-0.1.37.jar"
+ unset jgit_home
+ java_args=
+fi
+
+# Cleanup paths for Cygwin.
+#
+case "`uname`" in
+CYGWIN*)
+ cp=`cygpath --windows --mixed --path "$cp"`
+ ;;
+Darwin)
+ if test -e /System/Library/Frameworks/JavaVM.framework
+ then
+ java_args='
+ -Dcom.apple.mrj.application.apple.menu.about.name=JGit
+ -Dcom.apple.mrj.application.growbox.intrudes=false
+ -Dapple.laf.useScreenMenuBar=true
+ -Xdock:name=JGit
+ '
+ fi
+ ;;
+esac
+
+CLASSPATH="$cp"
+export CLASSPATH
+
+java=java
+if test -n "$JAVA_HOME"
+then
+ java="$JAVA_HOME/bin/java"
+fi
+
+exec "$java" $java_args org.spearce.jgit.pgm.Main "$@"
+exit 1
diff --git a/make_jgit.sh b/make_jgit.sh
new file mode 100755
index 0000000..3889dca
--- /dev/null
+++ b/make_jgit.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+PATH=$JAVA_HOME/bin:$PATH
+O=jgit
+T=".temp$$.$O"
+
+rm -f $O
+rm -rf $T $O+ org.spearce.jgit/bin2
+cp org.spearce.jgit/lib/jsch-0.1.37.jar $T &&
+mkdir org.spearce.jgit/bin2 &&
+(cd org.spearce.jgit/src &&
+ find . -name \*.java -type f |
+ xargs javac \
+ -source 1.5 \
+ -target 1.5 \
+ -g \
+ -d ../bin2 \
+ -cp ../lib/jsch-0.1.37.jar) &&
+jar uf $T -C org.spearce.jgit/bin2 . &&
+jar uf $T -C org.spearce.jgit META-INF &&
+sed s/@@use_self@@/1/ jgit.sh >$O+ &&
+cat $T >>$O+ &&
+chmod 555 $O+ &&
+mv $O+ $O &&
+echo "Created $O." &&
+rm -rf $T $O+ org.spearce.jgit/bin2
--
1.5.6.74.g8a5e
^ permalink raw reply related
* [JGIT PATCH 0/8] Minor push fixups
From: Shawn O. Pearce @ 2008-07-01 3:03 UTC (permalink / raw)
To: Robin Rosenberg, Marek Zawirski; +Cc: git
A (small by recent standards) series to fix some issues with push
in both the dumb and smart transports.
We also can now package jgit as a stand-alone shell+JAR combination;
I install this into my path and have been favoring it over C Git
for a few days now.
--
Shawn O. Pearce (8):
Correct thin pack completion in IndexPack to handle some bundles
Delete reflog when deleting ref during dumb transport push
Refuse to create or delete funny ref names over dumb transports
Shorten progress message text from PackWriter
Correctly name the stderr redirection thread for local transport
Support 'git upload-pack' and 'git receive-pack' over SSH
Pack jgit into a portable single file command line utility
Don't try to pack 0{40} during push of delete and update
.gitignore | 1 +
jgit | 37 ----------------
jgit.sh | 45 ++++++++++++++++++++
make_jgit.sh | 26 +++++++++++
.../src/org/spearce/jgit/lib/PackWriter.java | 4 +-
.../jgit/transport/BasePackPushConnection.java | 6 ++-
.../src/org/spearce/jgit/transport/IndexPack.java | 11 ++++-
.../spearce/jgit/transport/TransportGitSsh.java | 8 +++-
.../org/spearce/jgit/transport/TransportLocal.java | 6 +-
.../spearce/jgit/transport/WalkPushConnection.java | 14 ++++++
.../jgit/transport/WalkRemoteObjectDatabase.java | 13 ++++++
11 files changed, 124 insertions(+), 47 deletions(-)
create mode 100644 .gitignore
delete mode 100755 jgit
create mode 100755 jgit.sh
create mode 100755 make_jgit.sh
^ permalink raw reply
* [JGIT PATCH 5/8] Correctly name the stderr redirection thread for local transport
From: Shawn O. Pearce @ 2008-07-01 3:04 UTC (permalink / raw)
To: Robin Rosenberg, Marek Zawirski; +Cc: git
In-Reply-To: <1214881445-3931-5-git-send-email-spearce@spearce.org>
When we open git-receive-pack for the local transport case we still
create a thread to redirect any error messages to the System.err
console. In this case the thread should be named after the command
we are running.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
.../org/spearce/jgit/transport/TransportLocal.java | 6 +++---
1 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportLocal.java b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportLocal.java
index 761d1b8..b112267 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportLocal.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportLocal.java
@@ -96,7 +96,7 @@ class TransportLocal extends PackTransport {
try {
final Process proc = Runtime.getRuntime().exec(
new String[] { cmd, "." }, null, remoteGitDir);
- new StreamRewritingThread(proc.getErrorStream()).start();
+ new StreamRewritingThread(cmd, proc.getErrorStream()).start();
return proc;
} catch (IOException err) {
throw new TransportException(uri, err.getMessage(), err);
@@ -158,8 +158,8 @@ class TransportLocal extends PackTransport {
class StreamRewritingThread extends Thread {
private final InputStream in;
- StreamRewritingThread(final InputStream in) {
- super("JGit " + getOptionUploadPack() + " Errors");
+ StreamRewritingThread(final String cmd, final InputStream in) {
+ super("JGit " + cmd + " Errors");
this.in = in;
}
--
1.5.6.74.g8a5e
^ permalink raw reply related
* [JGIT PATCH 1/8] Correct thin pack completion in IndexPack to handle some bundles
From: Shawn O. Pearce @ 2008-07-01 3:03 UTC (permalink / raw)
To: Robin Rosenberg, Marek Zawirski; +Cc: git
In-Reply-To: <1214881445-3931-1-git-send-email-spearce@spearce.org>
Recently I saw a bundle with a chain of deltas to base objects as
A->B->C, where A was the delta depending on the base B. In this
pack all of the objects used OBJ_REF_DELTA to link to their base
and C was not in the pack (it was assumed to be in the repository).
Because of the ordering of the ObjectIds for B and C jgit tried to
resolve A's delta base by pulling from the repository, as B was not
found in the pack file. The reason B was not found was because it
was waiting in the queue (to be processed next) and we did not know
what B's ObjectId was.
By skipping objects whose ObjectLoader's aren't found we should be
able to resolve those objects later when their delta base does get
resolved in this pack. However by the end of of this loop we must
have no more objects depending on something by ObjectId, as that is
an indication that the local repository is missing the objects we
must have to complete this thin pack.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
.../src/org/spearce/jgit/transport/IndexPack.java | 11 +++++++++--
1 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java b/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java
index 24a0577..29d99db 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java
@@ -55,6 +55,7 @@ import java.util.zip.Deflater;
import java.util.zip.Inflater;
import org.spearce.jgit.errors.CorruptObjectException;
+import org.spearce.jgit.errors.MissingObjectException;
import org.spearce.jgit.lib.BinaryDelta;
import org.spearce.jgit.lib.Constants;
import org.spearce.jgit.lib.InflaterCache;
@@ -399,9 +400,10 @@ public class IndexPack {
final Deflater def = new Deflater(Deflater.DEFAULT_COMPRESSION, false);
long end = packOut.length() - 20;
- while (!baseById.isEmpty()) {
- final ObjectId baseId = baseById.keySet().iterator().next();
+ for (final ObjectId baseId : new ArrayList<ObjectId>(baseById.keySet())) {
final ObjectLoader ldr = repo.openObject(baseId);
+ if (ldr == null)
+ continue;
final byte[] data = ldr.getBytes();
final int typeCode = ldr.getType();
final PackedObjectInfo oe;
@@ -419,6 +421,11 @@ public class IndexPack {
}
def.end();
+ if (!baseById.isEmpty()) {
+ final ObjectId need = baseById.keySet().iterator().next();
+ throw new MissingObjectException(need, "delta base");
+ }
+
fixHeaderFooter();
}
--
1.5.6.74.g8a5e
^ permalink raw reply related
* [RFC/PATCH 2/2] Migrate git-rebase--i to use git-sequencer
From: Stephan Beyer @ 2008-07-01 2:39 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Stephan Beyer
In-Reply-To: <1214879968-17944-2-git-send-email-s-beyer@gmx.net>
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
git-rebase--interactive.sh | 423 ++++++-----------------------------------
t/t3404-rebase-interactive.sh | 29 ++--
2 files changed, 71 insertions(+), 381 deletions(-)
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index ad16fa2..60f43b2 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -35,28 +35,13 @@ skip skip current patch and continue rebasing process
require_work_tree
DOTEST="$GIT_DIR/.dotest-merge"
-TODO="$DOTEST"/git-rebase-todo
-DONE="$DOTEST"/done
-MSG="$DOTEST"/message
-SQUASH_MSG="$DOTEST"/message-squash
+TODO="$DOTEST/git-rebase-todo"
PRESERVE_MERGES=
STRATEGY=
VERBOSE=
+INTERACTIVE=
ONTO=
-MARK_PREFIX='refs/rebase-marks'
-test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
-test -f "$DOTEST"/verbose && VERBOSE=t
-
-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
-
-mark_prefix=refs/rebase-marks/
-
-warn () {
- echo "$*" >&2
-}
+test -f "$DOTEST"/verbose && VERBOSE=--verbose
output () {
case "$VERBOSE" in
@@ -83,51 +68,11 @@ require_clean_work_tree () {
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"
+warn () {
+ echo "$*" >&2
}
cleanup_before_quit () {
- for ref in $(git for-each-ref --format='%(refname)' "${mark_prefix%/}")
- do
- git update-ref -d "$ref" "$ref" || return 1
- done
rm -rf "$DOTEST"
}
@@ -136,229 +81,13 @@ die_abort () {
die "$1"
}
-has_action () {
- grep '^[^#]' "$1" >/dev/null
-}
-
-redo_merge () {
- rm_sha1=$1
- shift
-
- eval "$(get_author_ident_from_commit $rm_sha1)"
- msg="$(git cat-file commit $rm_sha1 | sed -e '1,/^$/d')"
-
- 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" "$@"
- then
- git rerere
- printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
- die Error redoing merge $rm_sha1
- fi
- unset rm_sha1
-}
-
-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"
- 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
-}
-
-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
-}
-
-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'
-}
-
-peek_next_command () {
- sed -n "1s/ .*$//p" < "$TODO"
-}
-
-mark_to_ref () {
- if expr "$1" : "^:[0-9][0-9]*$" >/dev/null
- then
- echo "$mark_prefix$(printf %d ${1#:})"
- else
- echo "$1"
- fi
-}
-
-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
-
- 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
- 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
- ;;
- mark)
- mark_action_done
-
- mark=$(mark_to_ref :${sha1#:})
- test :${sha1#:} = "$mark" && die "Invalid mark '$sha1'"
-
- git rev-parse --verify "$mark" > /dev/null 2>&1 && \
- warn "mark $sha1 already exist; overwriting it"
-
- git update-ref "$mark" HEAD || die "update-ref failed"
- ;;
- merge|m)
- comment_for_reflog merge
-
- if ! git rev-parse --verify $sha1 > /dev/null
- then
- die "Invalid reference merge '$sha1' in"
- "$command $sha1 $rest"
- fi
-
- new_parents=
- for p in $rest
- do
- new_parents="$new_parents $(mark_to_ref $p)"
- done
- new_parents="${new_parents# }"
- test -n "$new_parents" || \
- die "You forgot to give the parents for the" \
- "merge $sha1. Please fix it in $TODO"
-
- mark_action_done
- redo_merge $sha1 $new_parents
- ;;
- reset|r)
- comment_for_reflog reset
-
- tmp=$(git rev-parse --verify "$(mark_to_ref $sha1)") ||
- die "Invalid parent '$sha1' in $command $sha1 $rest"
-
- mark_action_done
- output git reset --hard $tmp
- ;;
- tag|t)
- comment_for_reflog tag
-
- mark_action_done
- output git tag -f "$sha1"
- ;;
- *)
- warn "Unknown command: $command $sha1 $rest"
- die_with_patch $sha1 "Please fix this in the file $TODO."
- ;;
- esac
- test -s "$TODO" && return
-
- comment_for_reflog finish &&
+update_refs () {
HEADNAME=$(cat "$DOTEST"/head-name) &&
OLDHEAD=$(cat "$DOTEST"/head) &&
- SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
NEWHEAD=$(git rev-parse HEAD) &&
case $HEADNAME in
refs/*)
- message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
+ message="$GIT_REFLOG_ACTION: $HEADNAME)" &&
git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
git symbolic-ref HEAD $HEADNAME
;;
@@ -369,15 +98,30 @@ do_next () {
cleanup_before_quit &&
git gc --auto &&
warn "Successfully rebased and updated $HEADNAME."
+}
- exit
+run_sequencer () {
+ git sequencer --caller='git rebase -i|--abort|--continue|--skip' "$@"
+ case "$?" in
+ 0)
+ update_refs
+ exit 0
+ ;;
+ 2)
+ warn 'git-sequencer needs continuation (by edit).'
+ exit 0
+ ;;
+ 3)
+ die 'git-sequencer needs continuation (by conflict).'
+ ;;
+ *)
+ die_abort 'git-sequencer died.'
+ ;;
+ esac
}
-do_rest () {
- while :
- do
- do_next
- done
+has_action () {
+ grep '^[^#]' "$1" >/dev/null
}
get_value_from_list () {
@@ -418,7 +162,6 @@ create_extended_todo_list () {
(
while read sha1 tag
do
- tag=${tag#refs/tags/}
if test ${last_sha1:-0000} = $sha1
then
saved_tags="$saved_tags:$tag"
@@ -457,7 +200,7 @@ create_extended_todo_list () {
then
for t in $(echo $tmp | tr : ' ')
do
- echo tag $t
+ echo ref $t
done
fi
@@ -477,18 +220,18 @@ create_extended_todo_list () {
fi
done
unset p
- echo merge $commit $new_parents
+ echo "merge $STRATEGY --reuse-commit=$commit $new_parents"
unset new_parents
;;
*)
- echo "pick $commit $subject"
+ echo "pick $commit # $subject"
;;
esac
done
test -n "${last_parent:-}" -a "${last_parent:-}" != $SHORTUPSTREAM && \
- echo reset $last_parent
+ echo "reset $last_parent"
) | \
- perl -e 'print reverse <>' | \
+ @@PERL@@ -e 'print reverse <>' | \
while read cmd args
do
: ${commit_mark_list:=} ${last_commit:=000}
@@ -515,6 +258,7 @@ create_extended_todo_list () {
fi
;;
merge)
+ args="${args#*--reuse-commit=}"
new_args=
for i in ${args#* }
do
@@ -527,7 +271,7 @@ create_extended_todo_list () {
fi
done
last_commit="${args%% *}"
- args="$last_commit ${new_args# }"
+ args="$STRATEGY --reuse-commit=$last_commit ${new_args# }"
;;
esac
echo "$cmd $args"
@@ -545,65 +289,9 @@ is_standalone () {
while test $# != 0
do
case "$1" in
- --continue)
- is_standalone "$@" || usage
- 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 --
- then
- : Nothing to commit -- skip this
- 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."
- fi
-
- require_clean_work_tree
- do_rest
- ;;
- --abort)
+ --continue|--abort|--skip)
is_standalone "$@" || usage
- 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 &&
- cleanup_before_quit
- exit
- ;;
- --skip)
- is_standalone "$@" || usage
- comment_for_reflog skip
-
- git rerere clear
- test -d "$DOTEST" || die "No interactive rebase running"
-
- output git reset --hard && do_rest
+ run_sequencer "$1"
;;
-s)
case "$#,$1" in
@@ -620,7 +308,7 @@ do
# we use merge anyway
;;
-v)
- VERBOSE=t
+ VERBOSE=--verbose
;;
-p)
PRESERVE_MERGES=t
@@ -633,7 +321,7 @@ do
PRESERVE_TAGS=t
;;
-i)
- # yeah, we know
+ INTERACTIVE=t
;;
--onto)
shift
@@ -649,14 +337,12 @@ do
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"
test -z "$ONTO" && ONTO=$UPSTREAM
- if test ! -z "$2"
+ if test -n "$2"
then
output git show-ref --verify --quiet "refs/heads/$2" ||
die "Invalid branchname: $2"
@@ -674,8 +360,7 @@ do
echo $HEAD > "$DOTEST"/head
echo $UPSTREAM > "$DOTEST"/upstream
echo $ONTO > "$DOTEST"/onto
- test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy
- test t = "$VERBOSE" && : > "$DOTEST"/verbose
+ test -n "$VERBOSE" && : > "$DOTEST"/verbose
SHORTUPSTREAM=$(git rev-parse --short=7 $UPSTREAM)
SHORTHEAD=$(git rev-parse --short=7 $HEAD)
@@ -696,7 +381,8 @@ do
create_extended_todo_list
else
git rev-list --no-merges --reverse --pretty=oneline \
- $common_rev_list_opts | sed -n "s/^>/pick /p"
+ $common_rev_list_opts |
+ sed -n -e "s/^>\([0-9a-f]\+\)/pick \1\t#/p"
fi > "$TODO"
cat >> "$TODO" << EOF
@@ -713,26 +399,29 @@ do
# squash = use commit, but meld into previous commit
# mark :mark = mark the current HEAD for later reference
# reset commit = reset HEAD to the commit
-# merge commit-M commit-P ... = redo merge commit-M with the
-# current HEAD and the parents commit-P
-# tag = reset tag to the current HEAD
+# merge [--reuse-commit=commit-M] remote ... =
+# * merge the remotes into HEAD
+# * can use the author and commit message from commit-M
+# ref = update ref to the current HEAD
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
EOF
-
has_action "$TODO" ||
die_abort "Nothing to do"
- cp "$TODO" "$TODO".backup
- git_editor "$TODO" ||
- die "Could not execute editor"
+ if test -n "$INTERACTIVE"
+ then
+ cp "$TODO" "$TODO".backup
+ git_editor "$TODO" ||
+ die_abort "Could not execute editor"
- has_action "$TODO" ||
- die_abort "Nothing to do"
+ has_action "$TODO" ||
+ die_abort "Nothing to do"
+ fi
- output git checkout $ONTO && do_rest
+ run_sequencer $VERBOSE --onto "$ONTO" "$TODO"
;;
esac
shift
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 940eb24..8b0781d 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -89,11 +89,11 @@ for line in $FAKE_LINES; do
echo "reset ${line#reset}"
echo "reset ${line#reset}" >> "$1";;
merge*)
- echo "merge ${line#merge}" | tr / ' '
- echo "merge ${line#merge}" | tr / ' ' >> "$1";;
+ echo "merge --reuse-commit=${line#merge}" | tr / ' '
+ echo "merge --reuse-commit=${line#merge}" | tr / ' ' >> "$1";;
tag*)
- echo "tag ${line#tag}"
- echo "tag ${line#tag}" >> "$1";;
+ echo "ref refs/tags/${line#tag}"
+ echo "ref refs/tags/${line#tag}" >> "$1";;
*)
sed -n "${line}{s/^pick/$action/; p;}" < "$1".tmp
sed -n "${line}{s/^pick/$action/; p;}" < "$1".tmp >> "$1"
@@ -170,19 +170,20 @@ 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/.dotest-merge/patch &&
+ test_cmp expect .git/sequencer/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/.dotest-merge/done | wc -l) &&
- test 0 = $(grep -c "^[^#]" < .git/.dotest-merge/git-rebase-todo)
+ test 4 = $(grep -v "^#" < .git/sequencer/done | wc -l) &&
+ test 0 = $(grep -c "^[^#]" < .git/sequencer/todo) &&
+ test -d .git/.dotest-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/.dotest-merge
+ ! test -d .git/sequencer
'
test_expect_success 'retain authorship' '
@@ -219,13 +220,13 @@ test_expect_success '-p handles "no changes" gracefully' '
test_expect_success 'setting marks works' '
git checkout master &&
FAKE_LINES="mark:0 2 1 mark:42 3 edit 4" git rebase -i HEAD~4 &&
- marks_dir=.git/refs/rebase-marks &&
+ marks_dir=.git/refs/sequencer-marks &&
test -d $marks_dir &&
test $(ls $marks_dir | wc -l) -eq 2 &&
test "$(git rev-parse HEAD~4)" = \
- "$(git rev-parse refs/rebase-marks/0)" &&
+ "$(git rev-parse refs/sequencer-marks/0)" &&
test "$(git rev-parse HEAD~2)" = \
- "$(git rev-parse refs/rebase-marks/42)" &&
+ "$(git rev-parse refs/sequencer-marks/42)" &&
git rebase --abort &&
test 0 = $(ls $marks_dir | wc -l)
'
@@ -233,8 +234,8 @@ test_expect_success 'setting marks works' '
test_expect_success 'reset with nonexistent mark fails' '
export FAKE_LINES="reset:0 1" &&
test_must_fail git rebase -i HEAD~1 &&
- unset FAKE_LINES &&
- git rebase --abort
+ git rebase --abort &&
+ unset FAKE_LINES
'
test_expect_success 'reset to HEAD is a nop' '
@@ -325,7 +326,7 @@ test_expect_success 'interactive --first-parent gives a linear list' '
test "$head" = "$(git rev-parse HEAD)"
'
-test_expect_success 'tag sets tags' '
+test_expect_success 'ref can set tags' '
head=$(git rev-parse HEAD) &&
FAKE_LINES="1 2 3 4 5 tagbb-tag1 6 7 8 9 10 11 12 13 14 15 \
tagbb-tag2 16 tagbb-tag3a tagbb-tag3b 17 18 19 20 21 22" \
--
1.5.6.334.gdaf0
^ permalink raw reply related
* git-rebase-i migration to sequencer
From: Stephan Beyer @ 2008-07-01 2:39 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin
In-Reply-To: <1214879914-17866-5-git-send-email-s-beyer@gmx.net>
Hi,
the patches for rebase-i migration follow.
Why this intermediate mail?
Because the patches will not apply to master/next/pu/whatever.
It needs js/rebase-i-sequencer, that is "dropped for now" from
git.git. So you have to do some extra work to apply.
Well, I could've just added a "Squashed js/rebase-i-sequencer patch"
or something, but
1. I didn't write the patches so I don't want to claim I'm the author,
2. It's an opportunity to try sequencer, as follows:
If you still have the commits (a freshly cloned repo hasn't),
you can do:
git sequencer <<EOF
#pick 367bcc9cbc3d903c076f # optional: "fake-editor: output TODO list if unchanged"
pick 0fa413ebc6e073a52d88
pick -R 88b1f0b8094638b1f953 # just to prevent a silly conflict
pick 966fdf0ab272a64250fb
pick 45d6022d611fd86a13c5
pick 051e0cfb88cd1d2a24bd
# note: the following patches will make t3404 fail until migration to sequencer
pick d9711a80796fd00169ca
pick 10046a6f8adaccac0ea1
pick abe32eafcd8302c387ed
pick d481bcc93f393bbfaaef
pick dbcd70439f697deeb317
pick b1ba88d812434a51889f
pick cc76fdac36ff25eaa77b
pick f8e037d76a224bd87236
pick 777790a3bdfeb91ee4a6
pick 7cdf70106412f44b9116
pick 14a4f1e0a804ccc905d7
EOF
If this results in a lot of "is not a commit" errors, then do
git sequencer --abort
(You may also have a look at git sequencer --status, if you like)
and fetch the missing commits:
git fetch git://repo.or.cz/git/sbeyer.git seq-proto-dev2
and then redo the example above.
When the patches are applied, the following patches will apply, too.
Regards,
Stephan
PS: Sorry, for the nastiness of the patch application ;-)
^ permalink raw reply
* [PATCH 1/2] Make rebase--interactive use OPTIONS_SPEC
From: Stephan Beyer @ 2008-07-01 2:39 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Stephan Beyer
In-Reply-To: <1214879968-17944-1-git-send-email-s-beyer@gmx.net>
Also add some checks that --continue/--abort/--skip
actions are used without --onto, -p, -t, etc.
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
git-rebase--interactive.sh | 72 ++++++++++++++++++++++++++++---------------
1 files changed, 47 insertions(+), 25 deletions(-)
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 7e073bb..ad16fa2 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -10,10 +10,27 @@
# The original idea comes from Eric W. Biederman, in
# http://article.gmane.org/gmane.comp.version-control.git/22407
-USAGE='(--continue | --abort | --skip | [--preserve-merges] [--first-parent]
- [--preserve-tags] [--verbose] [--onto <branch>] <upstream> [<branch>])'
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git-rebase [-i] [options] [--] <upstream> [<branch>]
+git-rebase [-i] (--continue | --abort | --skip)
+--
+ Available options are
+p,preserve-merges try to recreate merges instead of ignoring them
+t,preserve-tags update tags to the new commit object
+m,merge always used (no-op)
+i,interactive always used (no-op)
+onto= rebase onto given branch instead of upstream
+v,verbose display a diffstat of what changed upstream
+ When preserving merges
+f,first-parent show only commits following the first parent of each commit
+s,strategy= use the given merge strategy
+ Actions:
+continue continue rebasing process
+abort abort rebasing process and restore original branch
+skip skip current patch and continue rebasing process
+"
-OPTIONS_SPEC=
. git-sh-setup
require_work_tree
@@ -25,6 +42,8 @@ SQUASH_MSG="$DOTEST"/message-squash
PRESERVE_MERGES=
STRATEGY=
VERBOSE=
+ONTO=
+MARK_PREFIX='refs/rebase-marks'
test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
test -f "$DOTEST"/verbose && VERBOSE=t
@@ -515,10 +534,19 @@ create_extended_todo_list () {
done
}
+is_standalone () {
+ test $# -eq 2 -a "$2" = '--' &&
+ test -z "$ONTO" &&
+ test -z "$PRESERVE_TAGS" &&
+ test -z "$PRESERVE_MERGES" &&
+ test -z "$FIRST_PARENT"
+}
+
while test $# != 0
do
case "$1" in
--continue)
+ is_standalone "$@" || usage
comment_for_reflog continue
test -d "$DOTEST" || die "No interactive rebase running"
@@ -551,6 +579,7 @@ do
do_rest
;;
--abort)
+ is_standalone "$@" || usage
comment_for_reflog abort
git rerere clear
@@ -568,6 +597,7 @@ do
exit
;;
--skip)
+ is_standalone "$@" || usage
comment_for_reflog skip
git rerere clear
@@ -575,7 +605,7 @@ do
output git reset --hard && do_rest
;;
- -s|--strategy)
+ -s)
case "$#,$1" in
*,*=*)
STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
@@ -586,32 +616,33 @@ do
shift ;;
esac
;;
- -m|--merge)
+ -m)
# we use merge anyway
;;
- -C*)
- die "Interactive rebase uses merge, so $1 does not make sense"
- ;;
- -v|--verbose)
+ -v)
VERBOSE=t
;;
- -p|--preserve-merges)
+ -p)
PRESERVE_MERGES=t
;;
- -f|--first-parent)
+ -f)
FIRST_PARENT=t
PRESERVE_MERGES=t
;;
- -t|--preserve-tags)
+ -t)
PRESERVE_TAGS=t
;;
- -i|--interactive)
+ -i)
# yeah, we know
;;
- ''|-h)
- usage
+ --onto)
+ shift
+ ONTO=$(git rev-parse --verify "$1") ||
+ die "Does not point to a valid commit: $1"
;;
- *)
+ --)
+ shift
+ test $# -eq 1 -o $# -eq 2 || usage
test -d "$DOTEST" &&
die "Interactive rebase already started"
@@ -620,15 +651,6 @@ do
comment_for_reflog start
- ONTO=
- case "$1" in
- --onto)
- ONTO=$(git rev-parse --verify "$2") ||
- die "Does not point to a valid commit: $2"
- shift; shift
- ;;
- esac
-
require_clean_work_tree
UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
--
1.5.6.334.gdaf0
^ permalink raw reply related
* [RFC/PATCH 1/4] Add git-sequencer shell prototype
From: Stephan Beyer @ 2008-07-01 2:38 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Stephan Beyer
In-Reply-To: <1214879914-17866-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>
---
Some comments:
- It's declared as plumbing, but the prototype can't really be
named "plumbing", imho, since it uses porcelain like git-commit.
- I hope it is obvious that parts of it are based on
git-rebase-i and git-am code, so if you stumble over code
that looks familiar, it is not necessarily by accident.
.gitignore | 1 +
Makefile | 1 +
command-list.txt | 1 +
git-sequencer.sh | 1902 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 1905 insertions(+), 0 deletions(-)
create mode 100755 git-sequencer.sh
diff --git a/.gitignore b/.gitignore
index 4ff2fec..65a075b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -111,6 +111,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 bf77292..db93956 100644
--- a/Makefile
+++ b/Makefile
@@ -250,6 +250,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..31b0568
--- /dev/null
+++ b/git-sequencer.sh
@@ -0,0 +1,1902 @@
+#!/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
+B,batch run in batch-mode
+onto= checkout the given commit or branch first
+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 () {
+ echo "$*" >&2
+}
+
+cleanup () {
+ for ref in $(git for-each-ref --format='%(refname)' "$MARK_PREFIX")
+ do
+ git update-ref -d "$ref" "$ref" || return
+ done
+ rm -rf "$SEQ_DIR"
+}
+
+# Die if there has been a conflict
+die_to_continue () {
+ warn "$*"
+ test -z "$BATCHMODE" ||
+ die_abort 'Aborting, because of batch mode.'
+ test -z "$WHY" &&
+ echo 'conflict' >"$WHY_FILE" ||
+ echo "$WHY" >"$WHY_FILE"
+ exit 3
+}
+
+die_abort () {
+ restore
+ cleanup
+ die "$1"
+}
+
+quit () {
+ cleanup
+ test -z "$*" ||
+ echo "$*"
+ exit 0
+}
+
+output () {
+ case "$VERBOSE" in
+ 0)
+ "$@" >/dev/null
+ ;;
+ 1)
+ output=$("$@" 2>&1 )
+ status=$?
+ test $status -ne 0 && printf '%s\n' "$output"
+ return $status
+ ;;
+ 2)
+ "$@"
+ ;;
+ esac
+}
+
+require_clean_work_tree () {
+ # test if working tree is dirty
+ git rev-parse --verify HEAD >/dev/null &&
+ git update-index --ignore-submodules --refresh &&
+ git diff-files --quiet --ignore-submodules &&
+ git diff-index --cached --quiet HEAD --ignore-submodules -- ||
+ die 'Working tree 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=
+mark_action_done () {
+ sed -e 1q <"$TODO" >>"$DONE"
+ sed -e 1d <"$TODO" >"$TODO.new"
+ mv -f "$TODO.new" "$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" -lt 1 ||
+ printf 'Sequencing (%d/%d)\r' "$count" "$total"
+ test "$VERBOSE" -lt 2 || echo
+ fi
+ fi
+}
+
+# Generate message, patch and author script files
+make_patch () {
+ parent_sha1=$(git rev-parse --verify "$1"^) ||
+ die "Cannot get patch for $1^"
+ git diff-tree -p "$parent_sha1..$1" >"$PATCH"
+ 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 () {
+ make_patch "$1"
+ git rerere
+ die_to_continue "$2"
+}
+
+restore () {
+ git rerere clear
+
+ HEADNAME=$(cat "$SEQ_DIR/head-name")
+ HEAD=$(cat "$SEQ_DIR/head")
+ case $HEADNAME in
+ refs/*)
+ git symbolic-ref HEAD "$HEADNAME"
+ ;;
+ esac &&
+ output git reset --hard "$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 () {
+ . "$AUTHOR_SCRIPT" ||
+ 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" \
+ "$@"
+}
+
+# Usage: pick_one (cherry-pick|revert) [-*|--edit] sha1
+pick_one () {
+ what="$1"
+ # we just assume that this is either cherry-pick or revert
+ shift
+
+ # check for fast-forward if no options are given
+ if expr "x$1" : 'x[^-]' >/dev/null
+ then
+ test "$(git rev-parse --verify "$1^")" = \
+ "$(git rev-parse --verify HEAD)" &&
+ output git reset --hard "$1" &&
+ return
+ fi
+ test "$1" != '--edit' -a "$what" = 'revert' &&
+ what='revert --no-edit'
+ test -n "$SIGNOFF" &&
+ what="$what -s"
+ $use_output 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 () {
+ echo '# This is a dummy to get the 0.' >"$squash_msg"
+ for cur_sha1 in $(git rev-list --reverse "$sha1..HEAD")
+ do
+ make_squash_message "$cur_sha1" >"$MSG"
+ cp "$MSG" "$squash_msg"
+ done
+}
+
+peek_next_command () {
+ sed -n -e '1s/ .*$//p' <"$TODO"
+}
+
+# If $1 is a mark, make a ref from it; otherwise keep it
+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")"
+}
+
+
+cannot_fallback () {
+ echo "$1"
+ die_to_continue 'Cannot fall back to three-way merge. Please hand-edit.'
+}
+
+
+fallback_3way () {
+ O_OBJECT=$(cd "$GIT_OBJECT_DIRECTORY" && pwd)
+
+ rm -fr "$PATCH-merge-"*
+ mkdir "$PATCH-merge-tmp-dir"
+
+ # First see if the patch records the index info that we can use.
+ git apply --build-fake-ancestor "$PATCH-merge-tmp-index" "$PATCH" &&
+ GIT_INDEX_FILE="$PATCH-merge-tmp-index" \
+ git write-tree >"$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="$PATCH-merge-tmp-index" \
+ git apply --cached "$PATCH"
+ then
+ mv "$PATCH-merge-base+" "$PATCH-merge-base"
+ mv "$PATCH-merge-tmp-index" "$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 "$PATCH-merge-index" &&
+ his_tree=$(GIT_INDEX_FILE="$PATCH-merge-index" git write-tree) &&
+ orig_tree=$(cat "$PATCH-merge-base") &&
+ rm -fr "$PATCH-merge-"* || exit 1
+
+ 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
+ git-merge-recursive "$orig_tree" -- HEAD "$his_tree" || {
+ git rerere
+ die '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"
+
+ GIT_CHERRY_PICK_HELP="
+After resolving the conflicts, mark the corrected paths with
+ git add <paths>
+or
+ git rm <paths>
+and run
+ $(print_caller --continue)
+Note, that your working tree must match the index."
+ 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
+}
+
+strategy_check () {
+ case "$1" in
+ resolve|recursive|octopus|ours|subtree|theirs)
+ return
+ ;;
+ esac
+ todo_warn "Strategy '$1' not known."
+}
+
+
+### Author script functions
+
+clean_author_script () {
+ cat "$ORIG_AUTHOR_SCRIPT" >"$AUTHOR_SCRIPT"
+}
+
+# Take "Name <e-mail>" in stdin and outputs author script
+make_author_script_from_string () {
+ sed -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 () {
+ mark_action_done
+ make_patch HEAD
+ if test "$VERBOSE" -gt 0
+ then
+ warn
+ warn 'You can now edit files and add them to the index.'
+ warn 'Once you are satisfied with your changes, run'
+ warn
+ warn " $(print_caller --continue)"
+ warn
+ warn 'If you only want to change the commit message, run'
+ warn 'git commit --amend before.'
+ fi
+ echo 'pause' >"$WHY_FILE"
+ exit 2
+}
+
+
+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 "file" 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 () {
+ comment_for_reflog 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"
+
+ mark_action_done
+
+ git mailinfo $mailinfo_opts "$msgfile" "$PATCH" \
+ <"$filename" >"$infofile" ||
+ die_abort 'Could not read or parse mail'
+
+ if test -z "$AUTHOR"
+ then
+ sed -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=
+ with_author 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
+ # XXX: This is just a stupid hack:
+ with_author git apply $apply_opts --reject --index "$PATCH"
+ die_to_continue 'Patch failed. See the .rej files.'
+ # 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 'Editor returned error.'
+ has_message "$MSG" ||
+ die_with_patch 'No commit message given.'
+ fi
+
+ tree=$(git write-tree) &&
+ parent=$(git rev-parse --verify HEAD) &&
+ commit=$(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 () {
+ revert=
+ mainline=
+ while test $# -gt 1
+ do
+ case "$1" in
+ -R)
+ revert="$1"
+ ;;
+ --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
+ test -z "$revert" ||
+ todo_error "Cannot use $revert together with --mainline."
+
+ 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")
+
+ comment_for_reflog pick
+
+ mark_action_done
+
+ 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_output=
+ test -n "$edit_msg" ||
+ use_output=output
+
+ pick_one "$op" $edit_msg $mainline $sha1 ||
+ die_with_patch $sha1 "Could not apply $sha1"
+
+ test -n "$EDIT" ||
+ use_output=output
+
+ 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_output git commit --amend $EDIT $signoff --no-verify \
+ --author "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" \
+ --message="$MESSAGE"
+ else
+ # correct author if AUTHOR is set
+ test -n "$AUTHOR" &&
+ $use_output git commit --amend $EDIT --no-verify -C HEAD \
+ --author "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>"
+ # XXX: a git-cherry-pick --author could be nicer here
+ # correct commit message if MESSAGE is set
+ test -n "$MESSAGE" &&
+ $use_output git commit --amend $EDIT $signoff --no-verify \
+ -C HEAD --message="$MESSAGE"
+ 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"
+
+ # work around mark_action_done in insn_pause
+ echo '# pausing' >"$TODO.new"
+ cat "$TODO" >>"$TODO.new"
+ mv -f "$TODO.new" "$TODO"
+ 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
+
+ comment_for_reflog squash
+
+ # 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"
+
+ mark_action_done
+
+ if test -n "$MESSAGE"
+ then
+ printf '%s\n' "$MESSAGE" >"$MSG"
+ else
+ if test -n "$from"
+ then
+ make_squash_message_multiple "$sha1"
+ else
+ make_squash_message "$sha1" >"$MSG"
+ fi
+ fi
+
+ case "$(peek_next_command)" in
+ squash)
+ edit_commit=
+ use_output=output
+ cp "$MSG" "$squash_msg"
+ ;;
+ *)
+ edit_commit=-e
+ use_output=
+ rm -f "$squash_msg" || exit
+ ;;
+ 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
+ output git reset --soft "$sha1"
+ else
+ output 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_output git commit --no-verify \
+ -F "$MSG" $edit_commit || failed=t
+ else
+ cp "$MSG" "$GIT_DIR/MERGE_MSG"
+ warn
+ warn "Could not apply $sha1..."
+ die_with_patch $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_action_done
+
+ 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
+ strategy_check "$1"
+ ;;
+ --)
+ 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 () {
+ comment_for_reflog 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 'Editor returned error.'
+ has_message "$MSG" ||
+ die_with_patch 'No commit message given.'
+ fi
+
+ mark_action_done
+ if ! with_author output git merge $strategy -m junk $new_parents
+ then
+ git rerere
+ cat "$MSG" >"$GIT_DIR/MERGE_MSG"
+ die_to_continue 'Error merging'
+ fi
+ with_author output git commit --amend -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
+ comment_for_reflog reset
+
+ mark_action_done
+ output git reset --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
+ comment_for_reflog ref
+
+ mark_action_done
+ output 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)"
+ case "$c_or_i" in
+ check)
+ check_$insn "$@"
+ ;;
+ do)
+ insn_$insn "$@"
+ ;;
+ esac
+}
+
+# Execute the first line of the current TODO file
+execute_next () {
+ rm -f "$MSG"
+ echo 'HEAD' >"$SEQ_DIR/skiphead"
+ read command rol <"$TODO"
+ case "$command" in
+ '#'*|'')
+ mark_action_done
+ ;;
+ *)
+ test "$VERBOSE" -gt 1 &&
+ echo "Next line: $command $rol"
+
+ # reset general options
+ clean_author_script
+ 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 &&
+ quit
+}
+
+
+# Main loop to check instructions
+todo_check () {
+ todo="$TODO"
+ test -n "$1" && todo="$1"
+
+ 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 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"
+ return $retval
+}
+
+prepare_editable_todo () {
+ echo '# ALREADY DONE:'
+ sed -e 's/^/# /' <"$DONE"
+ echo '# '
+ echo "$markline"
+ cat "$TODO"
+}
+
+get_saved_options () {
+ VERBOSE=$(cat "$SEQ_DIR/verbose")
+ ONTO=$(cat "$SEQ_DIR/onto")
+ WHY=$(cat "$WHY_FILE" 2>/dev/null)
+ return 0
+}
+
+# Realize sequencer invocation
+do_startup () {
+ test -d "$SEQ_DIR" &&
+ die 'sequencer already started'
+
+ require_clean_work_tree
+
+ HEAD=$(git rev-parse --verify HEAD) ||
+ die 'No HEAD?'
+
+ mkdir "$SEQ_DIR" ||
+ die "Could not create temporary $SEQ_DIR"
+
+ # save options
+ echo "$VERBOSE" >"$SEQ_DIR/verbose"
+ 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 $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"
+ output git checkout "$(git rev-parse "$ONTO")" ||
+ die_abort "Could not checkout branch $ONTO"
+ else
+ output 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 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
+
+ has_action "$TODO" || die_abort 'Nothing to do'
+ todo_check || WHY=todo die_to_continue "TODO file contains errors.
+Fix with git sequencer --edit or abort with $(print_caller --abort)."
+
+ execute_rest
+ exit
+}
+
+# Realize --continue.
+do_continue () {
+ test -d "$SEQ_DIR" || die 'No sequencer running'
+ test_caller 'continue'
+
+ todo_check || WHY=todo die_to_continue "TODO file contains errors.
+Fix with git sequencer --edit or abort with $(print_caller --abort)."
+
+ comment_for_reflog continue
+
+ # Sanity check
+ git rev-parse --verify HEAD >/dev/null ||
+ die_to_continue 'Cannot read HEAD'
+ 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?)'
+
+ get_saved_options
+
+ # do we have anything to commit? (staged changes)
+ if ! git diff-index --cached --quiet --ignore-submodules HEAD --
+ then
+ . "$AUTHOR_SCRIPT" ||
+ die_to_continue 'Error evaluating author script. Must not happen!'
+
+ # 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)
+ 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
+ rm -f "$WHY_FILE"
+ execute_rest
+ exit
+}
+
+# Realize --abort.
+do_abort () {
+ test -d "$SEQ_DIR" || die 'No sequencer running'
+ test_caller 'abort'
+
+ comment_for_reflog abort
+ restore
+ quit
+}
+
+# Realize --skip.
+do_skip () {
+ test -d "$SEQ_DIR" || die 'No sequencer running'
+ test_caller 'skip'
+
+ todo_check || WHY=todo die_to_continue "TODO file contains errors.
+Fix with git sequencer --edit or abort with $(print_caller --abort)."
+ get_saved_options
+
+ comment_for_reflog skip
+ git rerere clear
+
+ output git reset --hard "$(cat "$SEQ_DIR/skiphead")" &&
+ rm -f "$WHY_FILE" &&
+ 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"
+
+ # 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
+ 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"
+ ;;
+ [eE]*)
+ prepare_editable_todo >"$TODO.new"
+ git_editor "$TODO.new"
+ ;;
+ [rRxXqQ]*)
+ rm -f "$TODO.new"
+ exit 0
+ ;;
+ [sS]*)
+ test "$WHY" != 'pause' ||
+ echo 'todo' >"$WHY_FILE"
+ 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" || quit "Nothing to do"
+ todo_check || die 'TODO file contains errors. Aborting.'
+ fi
+ cat "$TODO" >"$TODO.old"
+ if grep "^$markline" "$TODO.new" >/dev/null
+ then
+ sed -e "1,/^$markline/d" <"$TODO.new" >"$TODO"
+ else
+ cat "$TODO.new" >"$TODO"
+ fi
+ rm -f "$TODO.new"
+ 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:'
+ sed -e 's/^/ /' <"$DONE"
+ echo
+ fi
+ case "$WHY" in
+ pause)
+ echo 'Intentionally paused.'
+ ;;
+ 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' || {
+ echo 'To skip the current instruction, invoke:'
+ echo " $(print_caller --skip)"
+ }
+ fi
+ exit 0
+}
+
+is_standalone () {
+ test $# -eq 2 &&
+ test "$2" = '--' &&
+ test -z "$BATCHMODE" &&
+ test -z "$onto" &&
+ test "$VERBOSE" -eq 1
+}
+
+
+### Option handling and startup
+
+onto=
+BATCHMODE=
+CALLERSTRING=
+VERBOSE=1
+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"
+ ;;
+ -B)
+ BATCHMODE=t
+ # XXX: we still have abort on editor invokations
+ ;;
+ --onto)
+ shift
+ ONTO="$1"
+ test $(git cat-file -t "$ONTO") = 'commit' ||
+ die "$ONTO is no commit or branch."
+ ;;
+ -q)
+ VERBOSE=0
+ # 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.5.6.1.130.ga8860.dirty
^ permalink raw reply related
* [RFC/PATCH 4/4] Migrate git-am to use git-sequencer
From: Stephan Beyer @ 2008-07-01 2:38 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Stephan Beyer
In-Reply-To: <1214879914-17866-4-git-send-email-s-beyer@gmx.net>
This patch also adds --abort to git-am, which is just a
trivial implication of using git-sequencer.
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
Hm, what to say here?
I have to admit that interactive mode is purely tested.
Well, I've used sequencer-based git-am for a while, but never really
used the interactive mode (except for first manual test cases).
Documentation/git-am.txt | 5 +-
git-am.sh | 552 +++++++++++++---------------------------------
git-rebase.sh | 7 +-
t/t3407-rebase-abort.sh | 10 +-
t/t4150-am.sh | 21 ++-
5 files changed, 178 insertions(+), 417 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 46544a0..b714106 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -13,7 +13,7 @@ SYNOPSIS
[--3way] [--interactive] [--binary]
[--whitespace=<option>] [-C<n>] [-p<n>]
<mbox>|<Maildir>...
-'git-am' [--skip | --resolved]
+'git-am' (--abort | --skip | --resolved)
DESCRIPTION
-----------
@@ -79,6 +79,9 @@ default. You could use `--no-utf8` to override this.
--interactive::
Run interactively.
+--abort::
+ Abort applying and rewind applied patches.
+
--skip::
Skip the current patch. This is only meaningful when
restarting an aborted patch.
diff --git a/git-am.sh b/git-am.sh
index 2c517ed..dd19dd7 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -6,9 +6,11 @@ SUBDIRECTORY_OK=Yes
OPTIONS_KEEPDASHDASH=
OPTIONS_SPEC="\
git-am [options] <mbox>|<Maildir>...
+git-am [options] --abort
git-am [options] --resolved
git-am [options] --skip
--
+abort abort patching and reset done patches
d,dotest= (removed -- do not use)
i,interactive run interactively
b,binary pass --allow-binary-replacement to git-apply
@@ -30,104 +32,101 @@ set_reflog_action am
require_work_tree
cd_to_toplevel
-git var GIT_COMMITTER_IDENT >/dev/null || exit
+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=$(basename $0)
- 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\"."
-
- 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"
+ author_script="$GIT_DIR/sequencer/author-script"
+ # 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 "Commit Body is:"
+ echo "--------------------------"
+ cat "$msg"
+ echo "--------------------------"
+ printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
-cannot_fallback () {
- echo "$1"
- echo "Cannot fall back to three-way merge."
- exit 1
+ read reply
+ case "$reply" in
+ [yY]*)
+ run_sequencer --continue
+ ;;
+ [nN]*)
+ git reset --hard HEAD^
+ run_sequencer --continue
+ ;;
+ [eE]*)
+ git_editor "$msg"
+ git commit --amend --file="$msg" --no-verify >/dev/null
+ ;;
+ [vV]*)
+ LESS=-S ${PAGER:-less} "$patch"
+ ;;
+ [aA]*)
+ echo 'accept' >"$dotest/interactive"
+ run_sequencer --continue
+ ;;
+ *)
+ :
+ ;;
+ esac
+ done
+ test "$action" = accept &&
+ run_sequencer --continue
}
-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...
-
- # 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
- 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 () {
+ git sequencer --caller='git am|--abort|--resolved|--skip' "$@"
+ case "$?" in
+ 0)
+ cleanup
+ exit 0
+ ;;
+ 2)
+ test -n "$interactive" && be_interactive
+ echo 'git-sequencer needs continuation (by edit).'
+ exit 0
+ ;;
+ 3)
+ die 'git-sequencer needs continuation (by conflict).'
+ ;;
+ *)
+ die_abort 'git-sequencer died.'
+ ;;
+ esac
}
prec=4
dotest=".dotest"
+todofile="$dotest/todo"
sign= utf8=t keep= skip= interactive= resolved= binary= rebasing=
-resolvemsg= resume=
-git_apply_opt=
+resolvemsg=
+opts=
while test $# != 0
do
case "$1" in
-i|--interactive)
- interactive=t ;;
+ interactive=-q ;;
+ --abort)
+ abort=t ;;
-b|--binary)
binary=t ;;
-3|--3way)
@@ -152,9 +151,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 ;;
*)
@@ -163,347 +162,92 @@ 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" 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 dotest directory $dotest still exists but mbox given."
- resume=yes
-else
- # Make sure we are not given --skip nor --resolved
- test ",$skip,$resolved," = ,,, ||
- die "Resolve operation not in progress, we are not resuming."
+ test "$#" != 0 &&
+ die "previous dotest directory $dotest still exists but mbox given."
+
+ test -f "$dotest/interactive" &&
+ interactive=-q 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 --abort
+ test -n "$resolved" && run_sequencer --continue
+ test -n "$skip" && run_sequencer --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"
- 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 nor --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
-then
- SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
- s/>.*/>/
- s/^/Signed-off-by: /'
- `
-else
- SIGNOFF=
-fi
+# Start afresh.
+mkdir -p "$dotest" ||
+ die "Could not create $dotest directory."
-last=`cat "$dotest/last"`
-this=`cat "$dotest/next"`
-if test "$skip" = t
+if test -n "$prefix" && test $# != 0
then
- git rerere clear
- this=`expr "$this" + 1`
- resume=
+ 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
-if test "$this" -gt "$last"
-then
- echo Nothing to do.
- rm -fr "$dotest"
- exit
+files=$(git diff-index --cached --name-only HEAD --) || exit
+if [ "$files" ]; then
+ echo "Dirty index: cannot apply patches (dirty: $files)" >&2
+ exit 1
fi
-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
+test -n "$interactive" && echo 'again' >"$dotest/interactive"
- # skip pine's internal folder data
- grep '^Author: Mail System Internal Data$' \
- <"$dotest"/info >/dev/null &&
- go_next && continue
+# 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"
- 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")
+# create todofile
+: > "$todofile" ||
+ die_abort "Cannot create $todofile"
+while test "$this" -le "$last"
+do
+ 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
+ printf 'patch%s "%s" #%s\n' \
+ "$opts" "$dotest/$msgnum" \
+ "$(sed -n '1,/^Subject:/s/Subject://p' "$dotest/$msgnum")" >> "$todofile"
+ test -n "$interactive" && echo 'pause' >>"$todofile"
done
-git gc --auto
-
-rm -fr "$dotest"
+run_sequencer $interactive "$todofile"
diff --git a/git-rebase.sh b/git-rebase.sh
index e2d85ee..b0d4c7d 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=.dotest
- move_to_original_branch
+ git am --abort
fi
- git reset --hard $(cat "$dotest/orig-head")
- rm -r "$dotest"
exit
;;
--onto)
diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh
index 1777ffe..f6bbd51 100755
--- a/t/t3407-rebase-abort.sh
+++ b/t/t3407-rebase-abort.sh
@@ -39,9 +39,11 @@ testrebase() {
git reset --hard pre-rebase &&
test_must_fail git rebase$type master &&
test -d "$dotest" &&
+ #test -d ".git/sequencer" &&
git rebase --abort &&
test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
- test ! -d "$dotest"
+ test ! -d "$dotest" &&
+ test ! -d ".git/sequencer"
'
test_expect_success "rebase$type --abort after --skip" '
@@ -54,7 +56,8 @@ testrebase() {
test $(git rev-parse HEAD) = $(git rev-parse master) &&
git-rebase --abort &&
test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
- test ! -d "$dotest"
+ test ! -d "$dotest" &&
+ test ! -d ".git/sequencer"
'
test_expect_success "rebase$type --abort after --continue" '
@@ -70,7 +73,8 @@ testrebase() {
test $(git rev-parse HEAD) != $(git rev-parse master) &&
git rebase --abort &&
test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
- test ! -d "$dotest"
+ test ! -d "$dotest" &&
+ test ! -d ".git/sequencer"
'
}
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index bc98260..a9ee55e 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -83,6 +83,10 @@ test_expect_success setup '
git commit -m third &&
git format-patch --stdout first >patch2 &&
git checkout -b lorem &&
+ echo new >another &&
+ git add another &&
+ test_tick &&
+ git commit -m "added another file" &&
sed -n -e "11,\$p" msg >file &&
head -n 9 msg >>file &&
test_tick &&
@@ -181,7 +185,7 @@ test_expect_success 'am -3 falls back to 3-way merge' '
'
test_expect_success 'am pauses on conflict' '
- git checkout lorem2^^ &&
+ git checkout lorem2~3 &&
! git am lorem-move.patch &&
test -d .dotest
'
@@ -193,8 +197,17 @@ test_expect_success 'am --skip works' '
test goodbye = "$(cat another)"
'
+test_expect_success 'am --abort works' '
+ git checkout lorem2~3 &&
+ ! git am lorem-move.patch &&
+ test -d .dotest &&
+ git am --abort &&
+ test "$(git rev-parse HEAD)" = "$(git rev-parse lorem2~3)" &&
+ ! test -f another
+'
+
test_expect_success 'am --resolved works' '
- git checkout lorem2^^ &&
+ git checkout lorem2~3 &&
! git am lorem-move.patch &&
test -d .dotest &&
echo resolved >>file &&
@@ -212,14 +225,12 @@ test_expect_success 'am takes patches from a Pine mailbox' '
'
test_expect_success 'am fails on mail without patch' '
- ! git am <failmail &&
- rm -r .dotest/
+ ! git am <failmail
'
test_expect_success 'am fails on empty patch' '
echo "---" >>failmail &&
! git am <failmail &&
- git am --skip &&
! test -d .dotest
'
--
1.5.6.334.gdaf0
^ permalink raw reply related
* [RFC/PATCH 3/4] Add git-sequencer test suite (t3350)
From: Stephan Beyer @ 2008-07-01 2:38 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Stephan Beyer
In-Reply-To: <1214879914-17866-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>
---
Note that the --quiet tests are test_expect_failure, because I
have not really cared about the output behavior of sequencer,
and --quiet cannot be a trivial exec >/dev/null, because sometimes
editors are invoked, etc.
Also note that the fake-editor is quite different from the one
in the rebase tests. According to a "session", the file is
totally rewritten. It's not possible to do something like
FAKE_LINES="3 1 2" so that the third line is the first one, etc.
t/t3350-sequencer.sh | 810 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 810 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..33522eb
--- /dev/null
+++ b/t/t3350-sequencer.sh
@@ -0,0 +1,810 @@
+#!/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
+this=$(cat 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 $(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
+'
+
+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 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 "$(cat file1)" = "2" &&
+ test -z "$(cat file2)" &&
+ 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 "$(cat file1)" = 5 &&
+ test -z "$(cat 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 "$(cat file1)" = 5 &&
+ test -z "$(cat 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 "$(cat file1)" = "2" &&
+ test -z "$(cat file2)" &&
+ test -z "$(cat file3)" &&
+ test -z "$(cat file4)" &&
+ test -z "$(cat file5)" &&
+ test -z "$(cat 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 -q "^<<<<<<<" file1 ; fi } &&
+ echo 3 >file1 &&
+ git add file1 &&
+ next_session nochange &&
+ git sequencer --continue &&
+ session_ok &&
+ ! test -d "$SEQDIR" &&
+ test "$(cat 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 "$(cat 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 "$(cat file7)" &&
+
+ # pick --author="$yet_another" --file="commitmsg" --signoff H
+ expect_author HEAD^ "$yet_another" &&
+ expect_msg HEAD^ expected1 &&
+ test -z "$(cat 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 "$(cat file2)" &&
+ test -z "$(cat file6)" &&
+ git ls-files | grep -q "^file2" &&
+ git ls-files | grep -q "^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 -q "^file4" &&
+ echo 23 >file4 &&
+ git add file4 &&
+ next_session nochange &&
+ git sequencer --continue &&
+ session_ok &&
+ no_marks_set &&
+ test "$(cat file1)" = "2" &&
+ test "$(cat file4)" = "23" &&
+ expect_msg HEAD^ expected2 &&
+ expect_author HEAD^ "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" &&
+
+ # pick I
+ test -z "$(cat file6)" &&
+ git ls-files | grep -q "^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 "$(cat 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 "$(cat file1)" = "2" &&
+ test -z "$(cat 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 $(cat 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.5.6.334.gdaf0
^ permalink raw reply related
* [RFC/PATCH 2/4] Add git-sequencer prototype documentation
From: Stephan Beyer @ 2008-07-01 2:38 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Stephan Beyer
In-Reply-To: <1214879914-17866-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 | 348 +++++++++++++++++++++++++++++++++++++++
1 files changed, 348 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..e0c6410
--- /dev/null
+++ b/Documentation/git-sequencer.txt
@@ -0,0 +1,348 @@
+git-sequencer(1)
+================
+
+NAME
+----
+git-sequencer - Execute a sequence of git instructions
+
+SYNOPSIS
+--------
+[verse]
+'git-sequencer' [--batch] [--onto=<base>] [--verbose|--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.
+
+-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`.
+
+--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 undone rest 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.
+
+-q::
+--quiet::
+ Suppress output.
+ (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>::
+ Picks a commit and pauses 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.
++
+A commit can also be given by a mark, if prefixed with a colon.
++
+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;;
+ Generates 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>;;
+ Allows 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]).
+
+ -*;;
+ Any other dash-prefixed option is passed to
+ linkgit:git-apply[1].
+ This is especially useful for flags like
+ `--reverse`, `-C<n>`, `-p<n>` or `--whitespace=<action>`.
+
+
+pause::
+ Pauses the sequencer process to let you manually make changes.
+ For example, you can re-edit the done commit, fix bugs or typos,
+ or you can 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>.
+
+
+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
+--------
+
+TODO [Here the usage of all commands should become clear.]
+
+
+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-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.5.6.334.gdaf0
^ permalink raw reply related
* git sequencer prototype
From: Stephan Beyer @ 2008-07-01 2:38 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin
Hi,
here is the patchset for the git-sequencer prototype, documentation,
test suite and a first git-am and git-rebase-i migration.
Indeed, monster patches. ;)
I'm using sequencer-based git-am and git-rebase-i and also git-sequencer
itself for around 2-3 weeks now. So, for me, it is reality-proven, but
I'm curious about your opinions/suggestions in usage and source.
The migration patches are a little hard to code-review in the diff-form,
but feel free to apply, test, and then look at the code ;)
Regards,
Stephan
^ permalink raw reply
* [PATCH 14/14] Build in merge
From: Miklos Vajna @ 2008-07-01 2:37 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin
In-Reply-To: <cover.1214879690.git.vmiklos@frugalware.org>
Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
Makefile | 2 +-
builtin-merge.c | 1148 +++++++++++++++++++++++++
builtin.h | 1 +
git-merge.sh => contrib/examples/git-merge.sh | 0
git.c | 1 +
5 files changed, 1151 insertions(+), 1 deletions(-)
create mode 100644 builtin-merge.c
rename git-merge.sh => contrib/examples/git-merge.sh (100%)
diff --git a/Makefile b/Makefile
index bf77292..fbc53e9 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
SCRIPT_SH += git-merge-octopus.sh
SCRIPT_SH += git-merge-one-file.sh
SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
SCRIPT_SH += git-merge-stupid.sh
SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-parse-remote.sh
@@ -515,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
BUILTIN_OBJS += builtin-ls-tree.o
BUILTIN_OBJS += builtin-mailinfo.o
BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
BUILTIN_OBJS += builtin-merge-base.o
BUILTIN_OBJS += builtin-merge-file.o
BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000..bb8d985
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1148 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "path-list.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+
+enum strategy {
+ DEFAULT_TWOHEAD = 1,
+ DEFAULT_OCTOPUS = 2,
+ NO_FAST_FORWARD = 4,
+ NO_TRIVIAL = 8
+};
+
+static const char * const builtin_merge_usage[] = {
+ "git-merge [options] <remote>...",
+ "git-merge [options] <msg> HEAD <remote>",
+ NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static struct path_list use_strategies;
+static const char *branch;
+
+static struct path_list_item strategy_items[] = {
+ { "recur", (void *)NO_TRIVIAL },
+ { "recursive", (void *)(DEFAULT_TWOHEAD | NO_TRIVIAL) },
+ { "octopus", (void *)DEFAULT_OCTOPUS },
+ { "resolve", (void *)0 },
+ { "stupid", (void *)0 },
+ { "ours", (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+ { "subtree", (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+};
+static struct path_list strategies = { strategy_items,
+ ARRAY_SIZE(strategy_items), 0, 0 };
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct strbuf *buf = opt->value;
+
+ if (unset)
+ strbuf_setlen(buf, 0);
+ else {
+ strbuf_addf(buf, "%s\n\n", arg);
+ have_message = 1;
+ }
+ return 0;
+}
+
+static struct path_list_item *unsorted_path_list_lookup(const char *path,
+ struct path_list *list)
+{
+ int i;
+
+ if (!path)
+ return NULL;
+
+ for (i = 0; i < list->nr; i++)
+ if (!strcmp(path, list->items[i].path))
+ return &list->items[i];
+ return NULL;
+}
+
+static inline void path_list_append_strategy(struct path_list_item *item)
+{
+ path_list_append(item->path, &use_strategies)->util = item->util;
+}
+
+static int option_parse_strategy(const struct option *opt,
+ const char *arg, int unset)
+{
+ int i;
+ struct path_list_item *item = unsorted_path_list_lookup(arg, &strategies);
+
+ if (unset)
+ return 0;
+
+ if (item)
+ path_list_append_strategy(item);
+ else {
+ struct strbuf err;
+ strbuf_init(&err, 0);
+ for (i = 0; i < strategies.nr; i++)
+ strbuf_addf(&err, " %s", strategies.items[i].path);
+ fprintf(stderr, "Could not find merge strategy '%s'.\n", arg);
+ fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+ exit(1);
+ }
+ return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+ const char *arg, int unset)
+{
+ show_diffstat = unset;
+ return 0;
+}
+
+static struct option builtin_merge_options[] = {
+ { OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+ "do not show a diffstat at the end of the merge",
+ PARSE_OPT_NOARG, option_parse_n },
+ OPT_BOOLEAN(0, "stat", &show_diffstat,
+ "show a diffstat at the end of the merge"),
+ OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+ OPT_BOOLEAN(0, "log", &option_log,
+ "add list of one-line log to merge commit message"),
+ OPT_BOOLEAN(0, "squash", &squash,
+ "create a single commit instead of doing a merge"),
+ OPT_BOOLEAN(0, "commit", &option_commit,
+ "perform a commit if the merge succeeds (default)"),
+ OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+ "allow fast forward (default)"),
+ OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+ "merge strategy to use", option_parse_strategy),
+ OPT_CALLBACK('m', "message", &merge_msg, "message",
+ "message to be used for the merge commit (if any)",
+ option_parse_message),
+ OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+ unlink(git_path("MERGE_HEAD"));
+ unlink(git_path("MERGE_MSG"));
+}
+
+static void save_state(void)
+{
+ int len;
+ struct child_process cp;
+ struct strbuf buffer = STRBUF_INIT;
+ const char *argv[] = {"stash", "create", NULL};
+
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.out = -1;
+ cp.git_cmd = 1;
+
+ if (start_command(&cp))
+ die("could not run stash.");
+ len = strbuf_read(&buffer, cp.out, 1024);
+ close(cp.out);
+
+ if (finish_command(&cp) || len < 0)
+ die("stash failed");
+ else if (!len)
+ return;
+ strbuf_setlen(&buffer, buffer.len-1);
+ if (get_sha1(buffer.buf, stash))
+ die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+ int i = 0;
+ const char *args[6];
+
+ args[i++] = "read-tree";
+ if (verbose)
+ args[i++] = "-v";
+ args[i++] = "--reset";
+ args[i++] = "-u";
+ args[i++] = sha1_to_hex(sha1);
+ args[i] = NULL;
+
+ if (run_command_v_opt(args, RUN_GIT_CMD))
+ die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+ struct strbuf sb;
+ const char *args[] = { "stash", "apply", NULL, NULL };
+
+ if (is_null_sha1(stash))
+ return;
+
+ reset_hard(head, 1);
+
+ strbuf_init(&sb, 0);
+ args[2] = sha1_to_hex(stash);
+
+ /*
+ * It is OK to ignore error here, for example when there was
+ * nothing to restore.
+ */
+ run_command_v_opt(args, RUN_GIT_CMD);
+
+ strbuf_release(&sb);
+ refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+ printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+ drop_save();
+}
+
+static void squash_message(void)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ struct strbuf out;
+ struct commit_list *j;
+ int fd;
+
+ printf("Squash commit -- not updating HEAD\n");
+ fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could not write to %s", git_path("SQUASH_MSG"));
+
+ init_revisions(&rev, NULL);
+ rev.ignore_merges = 1;
+ rev.commit_format = CMIT_FMT_MEDIUM;
+
+ commit = lookup_commit(head);
+ commit->object.flags |= UNINTERESTING;
+ add_pending_object(&rev, &commit->object, NULL);
+
+ for (j = remoteheads; j; j = j->next)
+ add_pending_object(&rev, &j->item->object, NULL);
+
+ setup_revisions(0, NULL, &rev, NULL);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
+
+ strbuf_init(&out, 0);
+ strbuf_addstr(&out, "Squashed commit of the following:\n");
+ while ((commit = get_revision(&rev)) != NULL) {
+ strbuf_addch(&out, '\n');
+ strbuf_addf(&out, "commit %s\n",
+ sha1_to_hex(commit->object.sha1));
+ pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+ NULL, NULL, rev.date_mode, 0);
+ }
+ write(fd, out.buf, out.len);
+ close(fd);
+ strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+ struct child_process hook;
+ const char *argv[3], *env[2];
+ char index[PATH_MAX];
+
+ argv[0] = git_path("hooks/%s", name);
+ if (access(argv[0], X_OK) < 0)
+ return 0;
+
+ snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+ env[0] = index;
+ env[1] = NULL;
+
+ if (squash)
+ argv[1] = "1";
+ else
+ argv[1] = "0";
+ argv[2] = NULL;
+
+ memset(&hook, 0, sizeof(hook));
+ hook.argv = argv;
+ hook.no_stdin = 1;
+ hook.stdout_to_stderr = 1;
+ hook.env = env;
+
+ return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+ struct strbuf reflog_message;
+
+ strbuf_init(&reflog_message, 0);
+ if (!msg)
+ strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+ else {
+ printf("%s\n", msg);
+ strbuf_addf(&reflog_message, "%s: %s",
+ getenv("GIT_REFLOG_ACTION"), msg);
+ }
+ if (squash) {
+ squash_message();
+ } else {
+ if (!merge_msg.len)
+ printf("No merge message -- not updating HEAD\n");
+ else {
+ const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+ update_ref(reflog_message.buf, "HEAD",
+ new_head, head, 0,
+ DIE_ON_ERR);
+ /*
+ * We ignore errors in 'gc --auto', since the
+ * user should see them.
+ */
+ run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ }
+ }
+ if (new_head && show_diffstat) {
+ struct diff_options opts;
+ diff_setup(&opts);
+ opts.output_format |=
+ DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+ opts.detect_rename = DIFF_DETECT_RENAME;
+ if (diff_use_color_default > 0)
+ DIFF_OPT_SET(&opts, COLOR_DIFF);
+ if (diff_setup_done(&opts) < 0)
+ die("diff_setup_done failed");
+ diff_tree_sha1(head, new_head, "", &opts);
+ diffcore_std(&opts);
+ diff_flush(&opts);
+ }
+
+ /* Run a post-merge hook */
+ run_hook("post-merge");
+
+ strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+ struct object *remote_head;
+ unsigned char branch_head[20], buf_sha[20];
+ struct strbuf buf;
+ const char *ptr;
+ int len = 0;
+
+ memset(branch_head, 0, sizeof(branch_head));
+ remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+ if (!remote_head)
+ die("'%s' does not point to a commit", remote);
+
+ strbuf_init(&buf, 0);
+ strbuf_addstr(&buf, "refs/heads/");
+ strbuf_addstr(&buf, remote);
+ resolve_ref(buf.buf, branch_head, 0, 0);
+
+ if (!hashcmp(remote_head->sha1, branch_head)) {
+ strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+ sha1_to_hex(branch_head), remote);
+ return;
+ }
+ /* See if remote matches <name>~<number>, or <name>^ */
+ ptr = strrchr(remote, '^');
+ if (ptr && ptr[1] == '\0') {
+ for (len = 0, ptr = remote + strlen(remote);
+ remote < ptr && ptr[-1] == '^';
+ ptr--)
+ len++;
+ }
+ else {
+ ptr = strrchr(remote, '~');
+ if (ptr && ptr[1] != '0' && isdigit(ptr[1])) {
+ len = ptr-remote;
+ ptr++;
+ for (ptr++; *ptr; ptr++)
+ if (!isdigit(*ptr)) {
+ len = 0;
+ break;
+ }
+ }
+ }
+ if (len) {
+ struct strbuf truname = STRBUF_INIT;
+ strbuf_addstr(&truname, "refs/heads/");
+ strbuf_addstr(&truname, remote);
+ strbuf_setlen(&truname, len+11);
+ if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
+ strbuf_addf(msg,
+ "%s\t\tbranch '%s' (early part) of .\n",
+ sha1_to_hex(remote_head->sha1), truname.buf);
+ return;
+ }
+ }
+
+ if (!strcmp(remote, "FETCH_HEAD") &&
+ !access(git_path("FETCH_HEAD"), R_OK)) {
+ FILE *fp;
+ struct strbuf line;
+ char *ptr;
+
+ strbuf_init(&line, 0);
+ fp = fopen(git_path("FETCH_HEAD"), "r");
+ if (!fp)
+ die("could not open %s for reading: %s",
+ git_path("FETCH_HEAD"), strerror(errno));
+ strbuf_getline(&line, fp, '\n');
+ fclose(fp);
+ ptr = strstr(line.buf, "\tnot-for-merge\t");
+ if (ptr)
+ strbuf_remove(&line, ptr-line.buf+1, 13);
+ strbuf_addbuf(msg, &line);
+ strbuf_release(&line);
+ return;
+ }
+ strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+ sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+ if (branch && !prefixcmp(k, "branch.") &&
+ !prefixcmp(k + 7, branch) &&
+ !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+ const char **argv;
+ int argc;
+ char *buf;
+
+ buf = xstrdup(v);
+ argc = split_cmdline(buf, &argv);
+ argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+ memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+ argc++;
+ parse_options(argc, argv, builtin_merge_options,
+ builtin_merge_usage, 0);
+ free(buf);
+ }
+
+ if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+ show_diffstat = git_config_bool(k, v);
+ else if (!strcmp(k, "pull.twohead"))
+ return git_config_string(&pull_twohead, k, v);
+ else if (!strcmp(k, "pull.octopus"))
+ return git_config_string(&pull_octopus, k, v);
+ return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+ unsigned char *one)
+{
+ int i, nr_trees = 0;
+ struct tree *trees[MAX_UNPACK_TREES];
+ struct tree_desc t[MAX_UNPACK_TREES];
+ struct unpack_trees_options opts;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 2;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.verbose_update = 1;
+ opts.trivial_merges_only = 1;
+ opts.merge = 1;
+ trees[nr_trees] = parse_tree_indirect(common);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(head);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(one);
+ if (!trees[nr_trees++])
+ return -1;
+ opts.fn = threeway_merge;
+ cache_tree_free(&active_cache_tree);
+ opts.head_idx = 2;
+ for (i = 0; i < nr_trees; i++) {
+ parse_tree(trees[i]);
+ init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+ }
+ if (unpack_trees(nr_trees, t, &opts))
+ return -1;
+ return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+ if (write_cache_as_tree(sha1, 0, NULL))
+ die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(char *strategy, struct commit_list *common,
+ const char *head_arg)
+{
+ const char **args;
+ int i = 0, ret;
+ struct commit_list *j;
+ struct strbuf buf;
+
+ args = xmalloc((4 + commit_list_count(common) +
+ commit_list_count(remoteheads)) * sizeof(char *));
+ strbuf_init(&buf, 0);
+ strbuf_addf(&buf, "merge-%s", strategy);
+ args[i++] = buf.buf;
+ for (j = common; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i++] = "--";
+ args[i++] = head_arg;
+ for (j = remoteheads; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i] = NULL;
+ ret = run_command_v_opt(args, RUN_GIT_CMD);
+ strbuf_release(&buf);
+ i = 1;
+ for (j = common; j; j = j->next)
+ free((void *)args[i++]);
+ i += 2;
+ for (j = remoteheads; j; j = j->next)
+ free((void *)args[i++]);
+ free(args);
+ return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+ struct diff_options *opt, void *data)
+{
+ int *count = data;
+
+ (*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+ const struct index_state *state = &the_index;
+ int i, ret = 0;
+
+ for (i = 0; i < state->cache_nr; i++)
+ if (ce_stage(state->cache[i]))
+ ret++;
+
+ return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+ struct tree *trees[MAX_UNPACK_TREES];
+ struct unpack_trees_options opts;
+ struct tree_desc t[MAX_UNPACK_TREES];
+ int i, fd, nr_trees = 0;
+ struct dir_struct dir;
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ if (read_cache_unmerged())
+ die("you need to resolve your current index first");
+
+ fd = hold_locked_index(lock_file, 1);
+
+ memset(&trees, 0, sizeof(trees));
+ memset(&opts, 0, sizeof(opts));
+ memset(&t, 0, sizeof(t));
+ dir.show_ignored = 1;
+ dir.exclude_per_dir = ".gitignore";
+ opts.dir = &dir;
+
+ opts.head_idx = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.verbose_update = 1;
+ opts.merge = 1;
+ opts.fn = twoway_merge;
+
+ trees[nr_trees] = parse_tree_indirect(head);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(remote);
+ if (!trees[nr_trees++])
+ return -1;
+ for (i = 0; i < nr_trees; i++) {
+ parse_tree(trees[i]);
+ init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+ }
+ if (unpack_trees(nr_trees, t, &opts))
+ return -1;
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("unable to write new index file");
+ return 0;
+}
+
+static void split_merge_strategies(const char *string, struct path_list *list)
+{
+ char *p, *q, *buf;
+
+ if (!string)
+ return;
+
+ list->strdup_paths = 1;
+ buf = xstrdup(string);
+ q = buf;
+ for (;;) {
+ p = strchr(q, ' ');
+ if (!p) {
+ path_list_append(q, list);
+ free(buf);
+ return;
+ } else {
+ *p = '\0';
+ path_list_append(q, list);
+ q = ++p;
+ }
+ }
+}
+
+static void add_strategies(const char *string, enum strategy strategy)
+{
+ struct path_list list;
+ int i;
+
+ memset(&list, 0, sizeof(list));
+ split_merge_strategies(string, &list);
+ if (list.nr) {
+ for (i = 0; i < list.nr; i++) {
+ struct path_list_item *item;
+
+ item = unsorted_path_list_lookup(list.items[i].path,
+ &strategies);
+ if (item)
+ path_list_append_strategy(item);
+ }
+ return;
+ }
+ for (i = 0; i < strategies.nr; i++)
+ if ((enum strategy)strategies.items[i].util & strategy)
+ path_list_append_strategy(&strategies.items[i]);
+}
+
+static int merge_trivial(void)
+{
+ unsigned char result_tree[20], result_commit[20];
+ struct commit_list parent;
+
+ write_tree_trivial(result_tree);
+ printf("Wonderful.\n");
+ parent.item = remoteheads->item;
+ parent.next = NULL;
+ commit_tree(merge_msg.buf, result_tree, &parent, result_commit);
+ finish(result_commit, "In-index merge");
+ drop_save();
+ return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+ unsigned char *result_tree,
+ struct path_list_item *wt_strategy)
+{
+ struct commit_list *parents = NULL, *j;
+ struct strbuf buf = STRBUF_INIT;
+ unsigned char result_commit[20];
+
+ free_commit_list(common);
+ if (allow_fast_forward) {
+ parents = remoteheads;
+ commit_list_insert(lookup_commit(head), &parents);
+ parents = reduce_heads(parents);
+ } else {
+ struct commit_list **pptr = &parents;
+
+ pptr = &commit_list_insert(lookup_commit(head),
+ pptr)->next;
+ for (j = remoteheads; j; j = j->next)
+ pptr = &commit_list_insert(j->item, pptr)->next;
+ }
+ free_commit_list(remoteheads);
+ strbuf_addch(&merge_msg, '\n');
+ commit_tree(merge_msg.buf, result_tree, parents, result_commit);
+ strbuf_addf(&buf, "Merge made by %s.", wt_strategy->path);
+ finish(result_commit, buf.buf);
+ strbuf_release(&buf);
+ drop_save();
+ return 0;
+}
+
+static int suggest_conflicts(void)
+{
+ FILE *fp;
+ int pos;
+
+ fp = fopen(git_path("MERGE_MSG"), "a");
+ if (!fp)
+ die("Could open %s for writing", git_path("MERGE_MSG"));
+ fprintf(fp, "\nConflicts:\n");
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+
+ if (ce_stage(ce)) {
+ fprintf(fp, "\t%s\n", ce->name);
+ while (pos + 1 < active_nr &&
+ !strcmp(ce->name,
+ active_cache[pos + 1]->name))
+ pos++;
+ }
+ }
+ fclose(fp);
+ rerere();
+ printf("Automatic merge failed; "
+ "fix conflicts and then commit the result.\n");
+ return 1;
+}
+
+static inline unsigned nth_strategy_flags(struct path_list *s, int nth)
+{
+ return (unsigned) s->items[nth].util;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+ struct commit *second_token = NULL;
+ if (argc > 1) {
+ unsigned char second_sha1[20];
+
+ if (get_sha1(argv[1], second_sha1))
+ return NULL;
+ second_token = lookup_commit_reference_gently(second_sha1, 0);
+ if (!second_token)
+ die("'%s' is not a commit", argv[1]);
+ if (hashcmp(second_token->object.sha1, head))
+ return NULL;
+ }
+ return second_token;
+}
+
+static int evaluate_result(void)
+{
+ int cnt = 0;
+ struct rev_info rev;
+
+ if (read_cache() < 0)
+ die("failed to read the cache");
+
+ /* Check how many files differ. */
+ init_revisions(&rev, "");
+ setup_revisions(0, NULL, &rev, NULL);
+ rev.diffopt.output_format |=
+ DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = count_diff_files;
+ rev.diffopt.format_callback_data = &cnt;
+ run_diff_files(&rev, 0);
+
+ /*
+ * Check how many unmerged entries are
+ * there.
+ */
+ cnt += count_unmerged_entries();
+
+ return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+ unsigned char result_tree[20];
+ struct strbuf buf;
+ const char *head_arg;
+ int flag, head_invalid = 0, i;
+ int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+ struct commit_list *common = NULL;
+ struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
+ struct commit_list **remotes = &remoteheads;
+
+ setup_work_tree();
+ if (unmerged_cache())
+ die("You are in the middle of a conflicted merge.");
+
+ /*
+ * Check if we are _not_ on a detached HEAD, i.e. if there is a
+ * current branch.
+ */
+ branch = resolve_ref("HEAD", head, 0, &flag);
+ if (branch && flag & REF_ISSYMREF) {
+ const char *ptr = skip_prefix(branch, "refs/heads/");
+ if (ptr)
+ branch = ptr;
+ } else
+ head_invalid = 1;
+
+ git_config(git_merge_config, NULL);
+
+ /* for color.ui */
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
+ argc = parse_options(argc, argv, builtin_merge_options,
+ builtin_merge_usage, 0);
+
+ if (squash) {
+ if (!allow_fast_forward)
+ die("You cannot combine --squash with --no-ff.");
+ option_commit = 0;
+ }
+
+ if (!argc)
+ usage_with_options(builtin_merge_usage,
+ builtin_merge_options);
+
+ /*
+ * This could be traditional "merge <msg> HEAD <commit>..." and
+ * the way we can tell it is to see if the second token is HEAD,
+ * but some people might have misused the interface and used a
+ * committish that is the same as HEAD there instead.
+ * Traditional format never would have "-m" so it is an
+ * additional safety measure to check for it.
+ */
+ strbuf_init(&buf, 0);
+
+ if (!have_message && is_old_style_invocation(argc, argv)) {
+ strbuf_addstr(&merge_msg, argv[0]);
+ head_arg = argv[1];
+ argv += 2;
+ argc -= 2;
+ } else if (head_invalid) {
+ struct object *remote_head;
+ /*
+ * If the merged head is a valid one there is no reason
+ * to forbid "git merge" into a branch yet to be born.
+ * We do the same for "git pull".
+ */
+ if (argc != 1)
+ die("Can merge only exactly one commit into "
+ "empty head");
+ remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+ if (!remote_head)
+ die("%s - not something we can merge", argv[0]);
+ update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+ DIE_ON_ERR);
+ reset_hard(remote_head->sha1, 0);
+ return 0;
+ } else {
+ struct strbuf msg;
+
+ /* We are invoked directly as the first-class UI. */
+ head_arg = "HEAD";
+
+ /*
+ * All the rest are the commits being merged;
+ * prepare the standard merge summary message to
+ * be appended to the given message. If remote
+ * is invalid we will die later in the common
+ * codepath so we discard the error in this
+ * loop.
+ */
+ strbuf_init(&msg, 0);
+ for (i = 0; i < argc; i++)
+ merge_name(argv[i], &msg);
+ fmt_merge_msg(option_log, &msg, &merge_msg);
+ if (merge_msg.len)
+ strbuf_setlen(&merge_msg, merge_msg.len-1);
+ }
+
+ if (head_invalid || !argc)
+ usage_with_options(builtin_merge_usage,
+ builtin_merge_options);
+
+ strbuf_addstr(&buf, "merge");
+ for (i = 0; i < argc; i++)
+ strbuf_addf(&buf, " %s", argv[i]);
+ setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+ strbuf_reset(&buf);
+
+ for (i = 0; i < argc; i++) {
+ struct object *o;
+
+ o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+ if (!o)
+ die("%s - not something we can merge", argv[i]);
+ remotes = &commit_list_insert(lookup_commit(o->sha1),
+ remotes)->next;
+
+ strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+ setenv(buf.buf, argv[i], 1);
+ strbuf_reset(&buf);
+ }
+
+ if (!use_strategies.nr) {
+ if (!remoteheads->next)
+ add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+ else
+ add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+ }
+
+ for (i = 0; i < use_strategies.nr; i++) {
+ if (nth_strategy_flags(&use_strategies, i) & NO_FAST_FORWARD)
+ allow_fast_forward = 0;
+ if (nth_strategy_flags(&use_strategies, i) & NO_TRIVIAL)
+ allow_trivial = 0;
+ }
+
+ if (!remoteheads->next)
+ common = get_merge_bases(lookup_commit(head),
+ remoteheads->item, 1);
+ else {
+ struct commit_list *list = remoteheads;
+ commit_list_insert(lookup_commit(head), &list);
+ common = get_octopus_merge_bases(list);
+ free(list);
+ }
+
+ update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+ DIE_ON_ERR);
+
+ if (!common)
+ ; /* No common ancestors found. We need a real merge. */
+ else if (!remoteheads->next && !common->next &&
+ common->item == remoteheads->item) {
+ /*
+ * If head can reach all the merge then we are up to date.
+ * but first the most common case of merging one remote.
+ */
+ finish_up_to_date("Already up-to-date.");
+ return 0;
+ } else if (allow_fast_forward && !remoteheads->next &&
+ !common->next &&
+ !hashcmp(common->item->object.sha1, head)) {
+ /* Again the most common case of merging one remote. */
+ struct strbuf msg;
+ struct object *o;
+ char hex[41];
+
+ strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+ printf("Updating %s..%s\n",
+ hex,
+ find_unique_abbrev(remoteheads->item->object.sha1,
+ DEFAULT_ABBREV));
+ refresh_cache(REFRESH_QUIET);
+ strbuf_init(&msg, 0);
+ strbuf_addstr(&msg, "Fast forward");
+ if (have_message)
+ strbuf_addstr(&msg,
+ " (no commit created; -m option ignored)");
+ o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+ 0, NULL, OBJ_COMMIT);
+ if (!o)
+ return 1;
+
+ if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+ return 1;
+
+ finish(o->sha1, msg.buf);
+ drop_save();
+ return 0;
+ } else if (!remoteheads->next && common->next)
+ ;
+ /*
+ * We are not doing octopus and not fast forward. Need
+ * a real merge.
+ */
+ else if (!remoteheads->next && !common->next && option_commit) {
+ /*
+ * We are not doing octopus, not fast forward, and have
+ * only one common.
+ */
+ refresh_cache(REFRESH_QUIET);
+ if (allow_trivial) {
+ /* See if it is really trivial. */
+ git_committer_info(IDENT_ERROR_ON_NO_NAME);
+ printf("Trying really trivial in-index merge...\n");
+ if (!read_tree_trivial(common->item->object.sha1,
+ head, remoteheads->item->object.sha1))
+ return merge_trivial();
+ printf("Nope.\n");
+ }
+ } else {
+ /*
+ * An octopus. If we can reach all the remote we are up
+ * to date.
+ */
+ int up_to_date = 1;
+ struct commit_list *j;
+
+ for (j = remoteheads; j; j = j->next) {
+ struct commit_list *common_one;
+
+ /*
+ * Here we *have* to calculate the individual
+ * merge_bases again, otherwise "git merge HEAD^
+ * HEAD^^" would be missed.
+ */
+ common_one = get_merge_bases(lookup_commit(head),
+ j->item, 1);
+ if (hashcmp(common_one->item->object.sha1,
+ j->item->object.sha1)) {
+ up_to_date = 0;
+ break;
+ }
+ }
+ if (up_to_date) {
+ finish_up_to_date("Already up-to-date. Yeeah!");
+ return 0;
+ }
+ }
+
+ /* We are going to make a new commit. */
+ git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+ /*
+ * At this point, we need a real merge. No matter what strategy
+ * we use, it would operate on the index, possibly affecting the
+ * working tree, and when resolved cleanly, have the desired
+ * tree in the index -- this means that the index must be in
+ * sync with the head commit. The strategies are responsible
+ * to ensure this.
+ */
+ if (use_strategies.nr != 1) {
+ /*
+ * Stash away the local changes so that we can try more
+ * than one.
+ */
+ save_state();
+ } else {
+ memcpy(stash, null_sha1, 20);
+ }
+
+ for (i = 0; i < use_strategies.nr; i++) {
+ int ret;
+ if (i) {
+ printf("Rewinding the tree to pristine...\n");
+ restore_state();
+ }
+ if (use_strategies.nr != 1)
+ printf("Trying merge strategy %s...\n",
+ use_strategies.items[i].path);
+ /*
+ * Remember which strategy left the state in the working
+ * tree.
+ */
+ wt_strategy = &use_strategies.items[i];
+
+ ret = try_merge_strategy(use_strategies.items[i].path,
+ common, head_arg);
+ if (!option_commit && !ret) {
+ merge_was_ok = 1;
+ /*
+ * This is necessary here just to avoid writing
+ * the tree, but later we will *not* exit with
+ * status code 1 because merge_was_ok is set.
+ */
+ ret = 1;
+ }
+
+ if (ret) {
+ /*
+ * The backend exits with 1 when conflicts are
+ * left to be resolved, with 2 when it does not
+ * handle the given merge at all.
+ */
+ if (ret == 1) {
+ int cnt = evaluate_result();
+
+ if (best_cnt <= 0 || cnt <= best_cnt) {
+ best_strategy =
+ &use_strategies.items[i];
+ best_cnt = cnt;
+ }
+ }
+ if (merge_was_ok)
+ break;
+ else
+ continue;
+ }
+
+ /* Automerge succeeded. */
+ write_tree_trivial(result_tree);
+ automerge_was_ok = 1;
+ break;
+ }
+
+ /*
+ * If we have a resulting tree, that means the strategy module
+ * auto resolved the merge cleanly.
+ */
+ if (automerge_was_ok)
+ return finish_automerge(common, result_tree, wt_strategy);
+
+ /*
+ * Pick the result from the best strategy and have the user fix
+ * it up.
+ */
+ if (!best_strategy) {
+ restore_state();
+ if (use_strategies.nr > 1)
+ fprintf(stderr,
+ "No merge strategy handled the merge.\n");
+ else
+ fprintf(stderr, "Merge with strategy %s failed.\n",
+ use_strategies.items[0].path);
+ return 2;
+ } else if (best_strategy == wt_strategy)
+ ; /* We already have its result in the working tree. */
+ else {
+ printf("Rewinding the tree to pristine...\n");
+ restore_state();
+ printf("Using the %s to prepare resolving by hand.\n",
+ best_strategy->path);
+ try_merge_strategy(best_strategy->path, common, head_arg);
+ }
+
+ if (squash)
+ finish(NULL, NULL);
+ else {
+ int fd;
+ struct commit_list *j;
+
+ for (j = remoteheads; j; j = j->next)
+ strbuf_addf(&buf, "%s\n",
+ sha1_to_hex(j->item->object.sha1));
+ fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could open %s for writing",
+ git_path("MERGE_HEAD"));
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+ die("Could not write to %s", git_path("MERGE_HEAD"));
+ close(fd);
+ strbuf_addch(&merge_msg, '\n');
+ fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could open %s for writing", git_path("MERGE_MSG"));
+ if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+ merge_msg.len)
+ die("Could not write to %s", git_path("MERGE_MSG"));
+ close(fd);
+ }
+
+ if (merge_was_ok) {
+ fprintf(stderr, "Automatic merge went well; "
+ "stopped before committing as requested\n");
+ return 0;
+ } else
+ return suggest_conflicts();
+}
diff --git a/builtin.h b/builtin.h
index 05ee56f..0e605d4 100644
--- a/builtin.h
+++ b/builtin.h
@@ -64,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
similarity index 100%
rename from git-merge.sh
rename to contrib/examples/git-merge.sh
diff --git a/git.c b/git.c
index 2fbe96b..770aadd 100644
--- a/git.c
+++ b/git.c
@@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "ls-remote", cmd_ls_remote },
{ "mailinfo", cmd_mailinfo },
{ "mailsplit", cmd_mailsplit },
+ { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file },
{ "merge-ours", cmd_merge_ours, RUN_SETUP },
--
1.5.6.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox