* why git-svn dcommit recreates master branch?
From: Pawel Sikora @ 2012-01-11 16:27 UTC (permalink / raw)
To: git
[-- Attachment #1: Type: text/plain, Size: 1218 bytes --]
Hi,
i'm wondering why the git-svn-dcommit recreates a master branch in my repo.
is it a bug or feature and master is required to cooperate with subversion?
please look at attached test.sh:
$ ./test.sh
Checked out revision 0.
A file.txt
Adding file.txt
Transmitting file data .
Committed revision 1.
Initialized empty Git repository in /home/users/pawels/bugs/git-svn-master-issue/repo.git/.git/
A file.txt
r1 = 464dbae76d675c8c4bdc715e34ebe46691b8bfa2 (refs/remotes/git-svn)
Checked out HEAD:
file:///home/users/pawels/bugs/git-svn-master-issue/repo.svn r1
branches before dcommit:
* bridge
remotes/git-svn
[bridge ffffc26] bar
1 files changed, 1 insertions(+), 0 deletions(-)
Committing to file:///home/users/pawels/bugs/git-svn-master-issue/repo.svn ...
M file.txt
Committed r2
M file.txt
r2 = d98723bccdcf673790b55d47a4f345c694a68ced (refs/remotes/git-svn)
No changes between current HEAD and refs/remotes/git-svn
Resetting to the latest refs/remotes/git-svn
branches after dcommit:
* bridge
master
remotes/git-svn
could some one put some light on this?
BR,
Paweł.
please CC me on reply.
[-- Attachment #2: test.sh --]
[-- Type: application/x-shellscript, Size: 489 bytes --]
^ permalink raw reply
* [PATCH 1/2] t4034-diff-words: replace regex for diff driver
From: Tay Ray Chuan @ 2012-01-11 17:25 UTC (permalink / raw)
To: Git Mailing List; +Cc: Junio C Hamano
The next patch uses a non-whitespace regex, similar to the regex
currently used by the 'testdriver' diff driver; replace the regex with a
distinct one so that we can continue to conclude its effects.
Signed-off-by: Tay Ray Chuan <rctay89@gmail.com>
---
Kept separate to keep the next patch clean.
---
t/t4034-diff-words.sh | 20 +++++++++++++++++---
1 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh
index 6f1e5a2..9ae0e1a 100755
--- a/t/t4034-diff-words.sh
+++ b/t/t4034-diff-words.sh
@@ -46,6 +46,20 @@ cat >expect.non-whitespace-is-word <<-\EOF
<GREEN>aeff = aeff * ( aaa )<RESET>
EOF
+cat >expect.everything-is-word <<-\EOF
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1,3 +1,7 @@<RESET>
+ <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
+
+ a = b + c<RESET>
+
+ <GREEN>aa = a<RESET>
+
+ <GREEN>aeff = aeff * ( aaa )<RESET>
+EOF
word_diff () {
test_must_fail git diff --no-index "$@" pre post >output &&
@@ -179,7 +193,7 @@ test_expect_success 'word diff with a regular expression' '
'
test_expect_success 'set up a diff driver' '
- git config diff.testdriver.wordRegex "[^[:space:]]" &&
+ git config diff.testdriver.wordRegex ".+" &&
cat <<-\EOF >.gitattributes
pre diff=testdriver
post diff=testdriver
@@ -192,7 +206,7 @@ test_expect_success 'option overrides .gitattributes' '
'
test_expect_success 'use regex supplied by driver' '
- cp expect.non-whitespace-is-word expect &&
+ cp expect.everything-is-word expect &&
word_diff --color-words
'
@@ -224,7 +238,7 @@ test_expect_success 'command-line overrides config: --word-diff-regex' '
'
test_expect_success '.gitattributes override config' '
- cp expect.non-whitespace-is-word expect &&
+ cp expect.everything-is-word expect &&
word_diff --color-words
'
--
1.7.7.584.g16d0ea
^ permalink raw reply related
* [PATCH 2/2] diff --word-diff: use non-whitespace regex by default
From: Tay Ray Chuan @ 2012-01-11 17:25 UTC (permalink / raw)
To: Git Mailing List; +Cc: Junio C Hamano
In-Reply-To: <1326302702-4536-1-git-send-email-rctay89@gmail.com>
Factor out the comprehensive non-whitespace regex in use by PATTERNS and
IPATTERN and use it as the word-diff regex for the default diff driver.
As the default regex is no longer non-empty, update the word-regex
selection logic (non-default driver from pre-image, then post-image,
then the diff.wordRegex config) accordingly.
Signed-off-by: Tay Ray Chuan <rctay89@gmail.com>
---
diff.c | 14 ++++++++------
t/t4034-diff-words.sh | 31 +++++++++----------------------
userdiff.c | 8 +++++---
3 files changed, 22 insertions(+), 31 deletions(-)
diff --git a/diff.c b/diff.c
index 374ecf3..5f71f9f 100644
--- a/diff.c
+++ b/diff.c
@@ -1987,9 +1987,10 @@ static const struct userdiff_funcname *diff_funcname_pattern(struct diff_filespe
return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
}
-static const char *userdiff_word_regex(struct diff_filespec *one)
+static const char *userdiff_word_regex(struct diff_filespec *one, int *is_default)
{
diff_filespec_load_driver(one);
+ *is_default = !strcmp(one->driver->name, "default");
return one->driver->word_regex;
}
@@ -2180,17 +2181,18 @@ static void builtin_diff(const char *name_a,
else if (!prefixcmp(diffopts, "-u"))
xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
if (o->word_diff) {
- int i;
+ int i, is_default;
ecbdata.diff_words =
xcalloc(1, sizeof(struct diff_words_data));
ecbdata.diff_words->type = o->word_diff;
ecbdata.diff_words->opt = o;
+ is_default = 0;
if (!o->word_regex)
- o->word_regex = userdiff_word_regex(one);
- if (!o->word_regex)
- o->word_regex = userdiff_word_regex(two);
- if (!o->word_regex)
+ o->word_regex = userdiff_word_regex(one, &is_default);
+ if (is_default)
+ o->word_regex = userdiff_word_regex(two, &is_default);
+ if (is_default && diff_word_regex_cfg)
o->word_regex = diff_word_regex_cfg;
if (o->word_regex) {
ecbdata.diff_words->word_regex = (regex_t *)
diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh
index 9ae0e1a..e588849 100755
--- a/t/t4034-diff-words.sh
+++ b/t/t4034-diff-words.sh
@@ -84,26 +84,13 @@ test_expect_success setup '
git config diff.color.func magenta
'
-test_expect_success 'set up pre and post with runs of whitespace' '
+test_expect_success 'set up pre and post with runs of non-whitespace' '
cp pre.simple pre &&
- cp post.simple post
+ cp post.simple post &&
+ cp expect.non-whitespace-is-word expect
'
-test_expect_success 'word diff with runs of whitespace' '
- cat >expect <<-\EOF &&
- <BOLD>diff --git a/pre b/post<RESET>
- <BOLD>index 330b04f..5ed8eff 100644<RESET>
- <BOLD>--- a/pre<RESET>
- <BOLD>+++ b/post<RESET>
- <CYAN>@@ -1,3 +1,7 @@<RESET>
- <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
-
- a = b + c<RESET>
-
- <GREEN>aa = a<RESET>
-
- <GREEN>aeff = aeff * ( aaa )<RESET>
- EOF
+test_expect_success 'word diff defaults to runs of non-whitespace' '
word_diff --color-words &&
word_diff --word-diff=color &&
word_diff --color --word-diff=color
@@ -116,8 +103,8 @@ test_expect_success '--word-diff=porcelain' '
--- a/pre
+++ b/post
@@ -1,3 +1,7 @@
- -h(4)
- +h(4),hh[44]
+ h(4)
+ +,hh[44]
~
# significant space
~
@@ -140,7 +127,7 @@ test_expect_success '--word-diff=plain' '
--- a/pre
+++ b/post
@@ -1,3 +1,7 @@
- [-h(4)-]{+h(4),hh[44]+}
+ h(4){+,hh[44]+}
a = b + c
@@ -159,7 +146,7 @@ test_expect_success '--word-diff=plain --color' '
<BOLD>--- a/pre<RESET>
<BOLD>+++ b/post<RESET>
<CYAN>@@ -1,3 +1,7 @@<RESET>
- <RED>[-h(4)-]<RESET><GREEN>{+h(4),hh[44]+}<RESET>
+ h(4)<GREEN>{+,hh[44]+}<RESET>
a = b + c<RESET>
@@ -177,7 +164,7 @@ test_expect_success 'word diff without context' '
<BOLD>--- a/pre<RESET>
<BOLD>+++ b/post<RESET>
<CYAN>@@ -1 +1 @@<RESET>
- <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
+ h(4)<GREEN>,hh[44]<RESET>
<CYAN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET>
<GREEN>aa = a<RESET>
diff --git a/userdiff.c b/userdiff.c
index 76109da..cf38566 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -7,12 +7,14 @@ static struct userdiff_driver *drivers;
static int ndrivers;
static int drivers_alloc;
+#define NON_WHITESPACE \
+ "[^[:space:]]|[\xc0-\xff][\x80-\xbf]+"
#define PATTERNS(name, pattern, word_regex) \
{ name, NULL, -1, { pattern, REG_EXTENDED }, \
- word_regex "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" }
+ word_regex "|" NON_WHITESPACE }
#define IPATTERN(name, pattern, word_regex) \
{ name, NULL, -1, { pattern, REG_EXTENDED | REG_ICASE }, \
- word_regex "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" }
+ word_regex "|" NON_WHITESPACE }
static struct userdiff_driver builtin_drivers[] = {
IPATTERN("fortran",
"!^([C*]|[ \t]*!)\n"
@@ -140,7 +142,7 @@ PATTERNS("csharp",
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
"|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
-{ "default", NULL, -1, { NULL, 0 } },
+{ "default", NULL, -1, { NULL, 0 }, NON_WHITESPACE },
};
#undef PATTERNS
#undef IPATTERN
--
1.7.7.584.g16d0ea
^ permalink raw reply related
* [BUG] multi-commit cherry-pick messes up the order of commits
From: SZEDER Gábor @ 2012-01-11 17:31 UTC (permalink / raw)
To: Christian Couder, git
Hi,
I did some multi-commit cherry-picks lately, and noticed that
sometimes cherry-pick applied the commits in different order than I
specified on the command line. After some debugging, today I could
finally come up with a receipe to reproduce:
git init
echo 1 >a && git add a
git commit -m a
echo 2 >b && git add b
git commit -m b
sleep 2 && echo 3 >c && git add c
git commit -m c
git checkout -b branch HEAD^^
git cherry-pick master master^ # the later commit first
where the 'git cherry-pick' command produces the following output:
[branch ef5b86e0] b
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 b
[branch 6a74f934] c
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 c
Notice that master^, i.e. the commit adding the file 'b', is picked
before master, i.e. the commit adding 'c', although the order on the
command line was the reverse.
This is because
cmd_cherry_pick()
pick_revisions()
walk_revs_populate_todo()
prepare_revs()
calls prepare_revision_walk(), which parses the commits from the
command line in the order they were specified, but inserts them into a
list ordered by date, and commits will be picked in the order they
appear in this list. So if you specify commits in a different order
than their committer date or commits with the same commiter date
(which are often produced by am, rebase, and multi-commit
cherry-pick), then they will be picked in wrong order.
As far as I can tell, this buggy behavior is as old as multi-commit
cherry-pick itself, i.e. 7e2bfd3f (revert: allow cherry-picking more
than one commit, 2010-06-02).
Best,
Gábor
^ permalink raw reply
* [PATCH v3 0/2] The move to sequencer.c
From: Ramkumar Ramachandra @ 2012-01-11 18:15 UTC (permalink / raw)
To: Git List; +Cc: Junio C Hamano, Jonathan Nieder
In-Reply-To: <20120111164758.GD1891@burratino>
Hi,
I figured I'd just post a minimal series performing the move:
1. I can write up scratch code and toy with various ideas I have on
the generalization. It'll help with my thought process.
2. I can stop thinking about how to begin every commit message with a
note about anticipating this move. This'll be a big relief!
Thanks.
Ramkumar Ramachandra (2):
revert: prepare to move replay_action to header
sequencer: factor code out of revert builtin
builtin/revert.c | 952 +-----------------------------------------------------
sequencer.c | 918 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
sequencer.h | 37 +++
3 files changed, 961 insertions(+), 946 deletions(-)
--
1.7.8.2
^ permalink raw reply
* [PATCH 1/2] revert: prepare to move replay_action to header
From: Ramkumar Ramachandra @ 2012-01-11 18:15 UTC (permalink / raw)
To: Git List; +Cc: Junio C Hamano, Jonathan Nieder
In-Reply-To: <1326305757-27525-1-git-send-email-artagnon@gmail.com>
REVERT and CHERRY_PICK and are unsuitable names for an enumerator in a
public interface, because they are generic enough to be likely to
clash with identifiers with other meanings. Rename to REPLAY_REVERT
and REPLAY_PICK as preparation for exposing them.
Helped-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
builtin/revert.c | 40 ++++++++++++++++++++++------------------
1 files changed, 22 insertions(+), 18 deletions(-)
diff --git a/builtin/revert.c b/builtin/revert.c
index 0d8020c..2739405 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -39,7 +39,11 @@ static const char * const cherry_pick_usage[] = {
NULL
};
-enum replay_action { REVERT, CHERRY_PICK };
+enum replay_action {
+ REPLAY_REVERT,
+ REPLAY_PICK
+};
+
enum replay_subcommand {
REPLAY_NONE,
REPLAY_REMOVE_STATE,
@@ -74,14 +78,14 @@ struct replay_opts {
static const char *action_name(const struct replay_opts *opts)
{
- return opts->action == REVERT ? "revert" : "cherry-pick";
+ return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
}
static char *get_encoding(const char *message);
static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts)
{
- return opts->action == REVERT ? revert_usage : cherry_pick_usage;
+ return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage;
}
static int option_parse_x(const struct option *opt,
@@ -160,7 +164,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
OPT_END(),
};
- if (opts->action == CHERRY_PICK) {
+ if (opts->action == REPLAY_PICK) {
struct option cp_extra[] = {
OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"),
OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"),
@@ -374,7 +378,7 @@ static int error_dirty_index(struct replay_opts *opts)
return error_resolve_conflict(action_name(opts));
/* Different translation strings for cherry-pick and revert */
- if (opts->action == CHERRY_PICK)
+ if (opts->action == REPLAY_PICK)
error(_("Your local changes would be overwritten by cherry-pick."));
else
error(_("Your local changes would be overwritten by revert."));
@@ -553,7 +557,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
defmsg = git_pathdup("MERGE_MSG");
- if (opts->action == REVERT) {
+ if (opts->action == REPLAY_REVERT) {
base = commit;
base_label = msg.label;
next = parent;
@@ -594,7 +598,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
}
}
- if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REVERT) {
+ if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
res = do_recursive_merge(base, next, base_label, next_label,
head, &msgbuf, opts);
write_message(&msgbuf, defmsg);
@@ -618,13 +622,13 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
* However, if the merge did not even start, then we don't want to
* write it at all.
*/
- if (opts->action == CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1))
+ if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1))
write_cherry_pick_head(commit, "CHERRY_PICK_HEAD");
- if (opts->action == REVERT && ((opts->no_commit && res == 0) || res == 1))
+ if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1))
write_cherry_pick_head(commit, "REVERT_HEAD");
if (res) {
- error(opts->action == REVERT
+ error(opts->action == REPLAY_REVERT
? _("could not revert %s... %s")
: _("could not apply %s... %s"),
find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
@@ -644,7 +648,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
static void prepare_revs(struct replay_opts *opts)
{
- if (opts->action != REVERT)
+ if (opts->action != REPLAY_REVERT)
opts->revs->reverse ^= 1;
if (prepare_revision_walk(opts->revs))
@@ -701,7 +705,7 @@ static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
{
struct commit_list *cur = NULL;
const char *sha1_abbrev = NULL;
- const char *action_str = opts->action == REVERT ? "revert" : "pick";
+ const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick";
const char *subject;
int subject_len;
@@ -722,10 +726,10 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *
int saved, status, padding;
if (!prefixcmp(bol, "pick")) {
- action = CHERRY_PICK;
+ action = REPLAY_PICK;
bol += strlen("pick");
} else if (!prefixcmp(bol, "revert")) {
- action = REVERT;
+ action = REPLAY_REVERT;
bol += strlen("revert");
} else
return NULL;
@@ -748,7 +752,7 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *
*/
if (action != opts->action) {
const char *action_str;
- action_str = action == REVERT ? "revert" : "cherry-pick";
+ action_str = action == REPLAY_REVERT ? "revert" : "cherry-pick";
error(_("Cannot %s during a %s"), action_str, action_name(opts));
return NULL;
}
@@ -1124,7 +1128,7 @@ static int pick_revisions(struct replay_opts *opts)
if (create_seq_dir() < 0)
return -1;
if (get_sha1("HEAD", sha1)) {
- if (opts->action == REVERT)
+ if (opts->action == REPLAY_REVERT)
return error(_("Can't revert as initial commit"));
return error(_("Can't cherry-pick into empty head"));
}
@@ -1141,7 +1145,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
memset(&opts, 0, sizeof(opts));
if (isatty(0))
opts.edit = 1;
- opts.action = REVERT;
+ opts.action = REPLAY_REVERT;
git_config(git_default_config, NULL);
parse_args(argc, argv, &opts);
res = pick_revisions(&opts);
@@ -1156,7 +1160,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
int res;
memset(&opts, 0, sizeof(opts));
- opts.action = CHERRY_PICK;
+ opts.action = REPLAY_PICK;
git_config(git_default_config, NULL);
parse_args(argc, argv, &opts);
res = pick_revisions(&opts);
--
1.7.8.2
^ permalink raw reply related
* [PATCH 2/2] sequencer: factor code out of revert builtin
From: Ramkumar Ramachandra @ 2012-01-11 18:15 UTC (permalink / raw)
To: Git List; +Cc: Junio C Hamano, Jonathan Nieder
In-Reply-To: <1326305757-27525-1-git-send-email-artagnon@gmail.com>
Expose the cherry-picking machinery through a public
sequencer_pick_revisions() (renamed from pick_revisions() in
builtin/revert.c), so that cherry-picking and reverting are special
cases of a general sequencer operation. The cherry-pick builtin is
now a thin wrapper that does command-line argument parsing before
calling into sequencer_pick_revisions(). In the future, we can write
a new "foo" builtin that calls into the sequencer like:
memset(&opts, 0, sizeof(opts));
opts.action = REPLAY_FOO;
opts.revisions = xmalloc(sizeof(*opts.revs));
parse_args_populate_opts(argc, argv, &opts);
init_revisions(opts.revs);
sequencer_pick_revisions(&opts);
This patch does not intend to make any functional changes. Check
with:
$ git blame -s -C HEAD^..HEAD -- sequencer.c | grep -C3 '^[^^]'
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
builtin/revert.c | 946 +-----------------------------------------------------
sequencer.c | 918 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
sequencer.h | 37 +++
3 files changed, 956 insertions(+), 945 deletions(-)
diff --git a/builtin/revert.c b/builtin/revert.c
index 2739405..4116f2d 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -1,18 +1,9 @@
#include "cache.h"
#include "builtin.h"
-#include "object.h"
-#include "commit.h"
-#include "tag.h"
-#include "run-command.h"
-#include "exec_cmd.h"
-#include "utf8.h"
#include "parse-options.h"
-#include "cache-tree.h"
#include "diff.h"
#include "revision.h"
#include "rerere.h"
-#include "merge-recursive.h"
-#include "refs.h"
#include "dir.h"
#include "sequencer.h"
@@ -39,41 +30,6 @@ static const char * const cherry_pick_usage[] = {
NULL
};
-enum replay_action {
- REPLAY_REVERT,
- REPLAY_PICK
-};
-
-enum replay_subcommand {
- REPLAY_NONE,
- REPLAY_REMOVE_STATE,
- REPLAY_CONTINUE,
- REPLAY_ROLLBACK
-};
-
-struct replay_opts {
- enum replay_action action;
- enum replay_subcommand subcommand;
-
- /* Boolean options */
- int edit;
- int record_origin;
- int no_commit;
- int signoff;
- int allow_ff;
- int allow_rerere_auto;
-
- int mainline;
-
- /* Merge strategy */
- const char *strategy;
- const char **xopts;
- size_t xopts_nr, xopts_alloc;
-
- /* Only used by REPLAY_NONE */
- struct rev_info *revs;
-};
-
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
static const char *action_name(const struct replay_opts *opts)
@@ -81,8 +37,6 @@ static const char *action_name(const struct replay_opts *opts)
return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
}
-static char *get_encoding(const char *message);
-
static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts)
{
return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage;
@@ -241,902 +195,6 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
usage_with_options(usage_str, options);
}
-struct commit_message {
- char *parent_label;
- const char *label;
- const char *subject;
- char *reencoded_message;
- const char *message;
-};
-
-static int get_message(struct commit *commit, struct commit_message *out)
-{
- const char *encoding;
- const char *abbrev, *subject;
- int abbrev_len, subject_len;
- char *q;
-
- if (!commit->buffer)
- return -1;
- encoding = get_encoding(commit->buffer);
- if (!encoding)
- encoding = "UTF-8";
- if (!git_commit_encoding)
- git_commit_encoding = "UTF-8";
-
- out->reencoded_message = NULL;
- out->message = commit->buffer;
- if (strcmp(encoding, git_commit_encoding))
- out->reencoded_message = reencode_string(commit->buffer,
- git_commit_encoding, encoding);
- if (out->reencoded_message)
- out->message = out->reencoded_message;
-
- abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
- abbrev_len = strlen(abbrev);
-
- subject_len = find_commit_subject(out->message, &subject);
-
- out->parent_label = xmalloc(strlen("parent of ") + abbrev_len +
- strlen("... ") + subject_len + 1);
- q = out->parent_label;
- q = mempcpy(q, "parent of ", strlen("parent of "));
- out->label = q;
- q = mempcpy(q, abbrev, abbrev_len);
- q = mempcpy(q, "... ", strlen("... "));
- out->subject = q;
- q = mempcpy(q, subject, subject_len);
- *q = '\0';
- return 0;
-}
-
-static void free_message(struct commit_message *msg)
-{
- free(msg->parent_label);
- free(msg->reencoded_message);
-}
-
-static char *get_encoding(const char *message)
-{
- const char *p = message, *eol;
-
- while (*p && *p != '\n') {
- for (eol = p + 1; *eol && *eol != '\n'; eol++)
- ; /* do nothing */
- if (!prefixcmp(p, "encoding ")) {
- char *result = xmalloc(eol - 8 - p);
- strlcpy(result, p + 9, eol - 8 - p);
- return result;
- }
- p = eol;
- if (*p == '\n')
- p++;
- }
- return NULL;
-}
-
-static void write_cherry_pick_head(struct commit *commit, const char *pseudoref)
-{
- const char *filename;
- int fd;
- struct strbuf buf = STRBUF_INIT;
-
- strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
-
- filename = git_path("%s", pseudoref);
- fd = open(filename, O_WRONLY | O_CREAT, 0666);
- if (fd < 0)
- die_errno(_("Could not open '%s' for writing"), filename);
- if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
- die_errno(_("Could not write to '%s'"), filename);
- strbuf_release(&buf);
-}
-
-static void print_advice(int show_hint)
-{
- char *msg = getenv("GIT_CHERRY_PICK_HELP");
-
- if (msg) {
- fprintf(stderr, "%s\n", msg);
- /*
- * A conflict has occured but the porcelain
- * (typically rebase --interactive) wants to take care
- * of the commit itself so remove CHERRY_PICK_HEAD
- */
- unlink(git_path("CHERRY_PICK_HEAD"));
- return;
- }
-
- if (show_hint) {
- advise("after resolving the conflicts, mark the corrected paths");
- advise("with 'git add <paths>' or 'git rm <paths>'");
- advise("and commit the result with 'git commit'");
- }
-}
-
-static void write_message(struct strbuf *msgbuf, const char *filename)
-{
- static struct lock_file msg_file;
-
- int msg_fd = hold_lock_file_for_update(&msg_file, filename,
- LOCK_DIE_ON_ERROR);
- if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
- die_errno(_("Could not write to %s"), filename);
- strbuf_release(msgbuf);
- if (commit_lock_file(&msg_file) < 0)
- die(_("Error wrapping up %s"), filename);
-}
-
-static struct tree *empty_tree(void)
-{
- return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
-}
-
-static int error_dirty_index(struct replay_opts *opts)
-{
- if (read_cache_unmerged())
- return error_resolve_conflict(action_name(opts));
-
- /* Different translation strings for cherry-pick and revert */
- if (opts->action == REPLAY_PICK)
- error(_("Your local changes would be overwritten by cherry-pick."));
- else
- error(_("Your local changes would be overwritten by revert."));
-
- if (advice_commit_before_merge)
- advise(_("Commit your changes or stash them to proceed."));
- return -1;
-}
-
-static int fast_forward_to(const unsigned char *to, const unsigned char *from)
-{
- struct ref_lock *ref_lock;
-
- read_cache();
- if (checkout_fast_forward(from, to))
- exit(1); /* the callee should have complained already */
- ref_lock = lock_any_ref_for_update("HEAD", from, 0);
- return write_ref_sha1(ref_lock, to, "cherry-pick");
-}
-
-static int do_recursive_merge(struct commit *base, struct commit *next,
- const char *base_label, const char *next_label,
- unsigned char *head, struct strbuf *msgbuf,
- struct replay_opts *opts)
-{
- struct merge_options o;
- struct tree *result, *next_tree, *base_tree, *head_tree;
- int clean, index_fd;
- const char **xopt;
- static struct lock_file index_lock;
-
- index_fd = hold_locked_index(&index_lock, 1);
-
- read_cache();
-
- init_merge_options(&o);
- o.ancestor = base ? base_label : "(empty tree)";
- o.branch1 = "HEAD";
- o.branch2 = next ? next_label : "(empty tree)";
-
- head_tree = parse_tree_indirect(head);
- next_tree = next ? next->tree : empty_tree();
- base_tree = base ? base->tree : empty_tree();
-
- for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
- parse_merge_opt(&o, *xopt);
-
- clean = merge_trees(&o,
- head_tree,
- next_tree, base_tree, &result);
-
- if (active_cache_changed &&
- (write_cache(index_fd, active_cache, active_nr) ||
- commit_locked_index(&index_lock)))
- /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
- die(_("%s: Unable to write new index file"), action_name(opts));
- rollback_lock_file(&index_lock);
-
- if (!clean) {
- int i;
- strbuf_addstr(msgbuf, "\nConflicts:\n\n");
- for (i = 0; i < active_nr;) {
- struct cache_entry *ce = active_cache[i++];
- if (ce_stage(ce)) {
- strbuf_addch(msgbuf, '\t');
- strbuf_addstr(msgbuf, ce->name);
- strbuf_addch(msgbuf, '\n');
- while (i < active_nr && !strcmp(ce->name,
- active_cache[i]->name))
- i++;
- }
- }
- }
-
- return !clean;
-}
-
-/*
- * If we are cherry-pick, and if the merge did not result in
- * hand-editing, we will hit this commit and inherit the original
- * author date and name.
- * If we are revert, or if our cherry-pick results in a hand merge,
- * we had better say that the current user is responsible for that.
- */
-static int run_git_commit(const char *defmsg, struct replay_opts *opts)
-{
- /* 6 is max possible length of our args array including NULL */
- const char *args[6];
- int i = 0;
-
- args[i++] = "commit";
- args[i++] = "-n";
- if (opts->signoff)
- args[i++] = "-s";
- if (!opts->edit) {
- args[i++] = "-F";
- args[i++] = defmsg;
- }
- args[i] = NULL;
-
- return run_command_v_opt(args, RUN_GIT_CMD);
-}
-
-static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
-{
- unsigned char head[20];
- struct commit *base, *next, *parent;
- const char *base_label, *next_label;
- struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
- char *defmsg = NULL;
- struct strbuf msgbuf = STRBUF_INIT;
- int res;
-
- if (opts->no_commit) {
- /*
- * We do not intend to commit immediately. We just want to
- * merge the differences in, so let's compute the tree
- * that represents the "current" state for merge-recursive
- * to work on.
- */
- if (write_cache_as_tree(head, 0, NULL))
- die (_("Your index file is unmerged."));
- } else {
- if (get_sha1("HEAD", head))
- return error(_("You do not have a valid HEAD"));
- if (index_differs_from("HEAD", 0))
- return error_dirty_index(opts);
- }
- discard_cache();
-
- if (!commit->parents) {
- parent = NULL;
- }
- else if (commit->parents->next) {
- /* Reverting or cherry-picking a merge commit */
- int cnt;
- struct commit_list *p;
-
- if (!opts->mainline)
- return error(_("Commit %s is a merge but no -m option was given."),
- sha1_to_hex(commit->object.sha1));
-
- for (cnt = 1, p = commit->parents;
- cnt != opts->mainline && p;
- cnt++)
- p = p->next;
- if (cnt != opts->mainline || !p)
- return error(_("Commit %s does not have parent %d"),
- sha1_to_hex(commit->object.sha1), opts->mainline);
- parent = p->item;
- } else if (0 < opts->mainline)
- return error(_("Mainline was specified but commit %s is not a merge."),
- sha1_to_hex(commit->object.sha1));
- else
- parent = commit->parents->item;
-
- if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head))
- return fast_forward_to(commit->object.sha1, head);
-
- if (parent && parse_commit(parent) < 0)
- /* TRANSLATORS: The first %s will be "revert" or
- "cherry-pick", the second %s a SHA1 */
- return error(_("%s: cannot parse parent commit %s"),
- action_name(opts), sha1_to_hex(parent->object.sha1));
-
- if (get_message(commit, &msg) != 0)
- return error(_("Cannot get commit message for %s"),
- sha1_to_hex(commit->object.sha1));
-
- /*
- * "commit" is an existing commit. We would want to apply
- * the difference it introduces since its first parent "prev"
- * on top of the current HEAD if we are cherry-pick. Or the
- * reverse of it if we are revert.
- */
-
- defmsg = git_pathdup("MERGE_MSG");
-
- if (opts->action == REPLAY_REVERT) {
- base = commit;
- base_label = msg.label;
- next = parent;
- next_label = msg.parent_label;
- strbuf_addstr(&msgbuf, "Revert \"");
- strbuf_addstr(&msgbuf, msg.subject);
- strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
- strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
-
- if (commit->parents && commit->parents->next) {
- strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
- strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));
- }
- strbuf_addstr(&msgbuf, ".\n");
- } else {
- const char *p;
-
- base = parent;
- base_label = msg.parent_label;
- next = commit;
- next_label = msg.label;
-
- /*
- * Append the commit log message to msgbuf; it starts
- * after the tree, parent, author, committer
- * information followed by "\n\n".
- */
- p = strstr(msg.message, "\n\n");
- if (p) {
- p += 2;
- strbuf_addstr(&msgbuf, p);
- }
-
- if (opts->record_origin) {
- strbuf_addstr(&msgbuf, "(cherry picked from commit ");
- strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
- strbuf_addstr(&msgbuf, ")\n");
- }
- }
-
- if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
- res = do_recursive_merge(base, next, base_label, next_label,
- head, &msgbuf, opts);
- write_message(&msgbuf, defmsg);
- } else {
- struct commit_list *common = NULL;
- struct commit_list *remotes = NULL;
-
- write_message(&msgbuf, defmsg);
-
- commit_list_insert(base, &common);
- commit_list_insert(next, &remotes);
- res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts,
- common, sha1_to_hex(head), remotes);
- free_commit_list(common);
- free_commit_list(remotes);
- }
-
- /*
- * If the merge was clean or if it failed due to conflict, we write
- * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
- * However, if the merge did not even start, then we don't want to
- * write it at all.
- */
- if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1))
- write_cherry_pick_head(commit, "CHERRY_PICK_HEAD");
- if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1))
- write_cherry_pick_head(commit, "REVERT_HEAD");
-
- if (res) {
- error(opts->action == REPLAY_REVERT
- ? _("could not revert %s... %s")
- : _("could not apply %s... %s"),
- find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
- msg.subject);
- print_advice(res == 1);
- rerere(opts->allow_rerere_auto);
- } else {
- if (!opts->no_commit)
- res = run_git_commit(defmsg, opts);
- }
-
- free_message(&msg);
- free(defmsg);
-
- return res;
-}
-
-static void prepare_revs(struct replay_opts *opts)
-{
- if (opts->action != REPLAY_REVERT)
- opts->revs->reverse ^= 1;
-
- if (prepare_revision_walk(opts->revs))
- die(_("revision walk setup failed"));
-
- if (!opts->revs->commits)
- die(_("empty commit set passed"));
-}
-
-static void read_and_refresh_cache(struct replay_opts *opts)
-{
- static struct lock_file index_lock;
- int index_fd = hold_locked_index(&index_lock, 0);
- if (read_index_preload(&the_index, NULL) < 0)
- die(_("git %s: failed to read the index"), action_name(opts));
- refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
- if (the_index.cache_changed) {
- if (write_index(&the_index, index_fd) ||
- commit_locked_index(&index_lock))
- die(_("git %s: failed to refresh the index"), action_name(opts));
- }
- rollback_lock_file(&index_lock);
-}
-
-/*
- * Append a commit to the end of the commit_list.
- *
- * next starts by pointing to the variable that holds the head of an
- * empty commit_list, and is updated to point to the "next" field of
- * the last item on the list as new commits are appended.
- *
- * Usage example:
- *
- * struct commit_list *list;
- * struct commit_list **next = &list;
- *
- * next = commit_list_append(c1, next);
- * next = commit_list_append(c2, next);
- * assert(commit_list_count(list) == 2);
- * return list;
- */
-static struct commit_list **commit_list_append(struct commit *commit,
- struct commit_list **next)
-{
- struct commit_list *new = xmalloc(sizeof(struct commit_list));
- new->item = commit;
- *next = new;
- new->next = NULL;
- return &new->next;
-}
-
-static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
- struct replay_opts *opts)
-{
- struct commit_list *cur = NULL;
- const char *sha1_abbrev = NULL;
- const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick";
- const char *subject;
- int subject_len;
-
- for (cur = todo_list; cur; cur = cur->next) {
- sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
- subject_len = find_commit_subject(cur->item->buffer, &subject);
- strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev,
- subject_len, subject);
- }
- return 0;
-}
-
-static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts)
-{
- unsigned char commit_sha1[20];
- enum replay_action action;
- char *end_of_object_name;
- int saved, status, padding;
-
- if (!prefixcmp(bol, "pick")) {
- action = REPLAY_PICK;
- bol += strlen("pick");
- } else if (!prefixcmp(bol, "revert")) {
- action = REPLAY_REVERT;
- bol += strlen("revert");
- } else
- return NULL;
-
- /* Eat up extra spaces/ tabs before object name */
- padding = strspn(bol, " \t");
- if (!padding)
- return NULL;
- bol += padding;
-
- end_of_object_name = bol + strcspn(bol, " \t\n");
- saved = *end_of_object_name;
- *end_of_object_name = '\0';
- status = get_sha1(bol, commit_sha1);
- *end_of_object_name = saved;
-
- /*
- * Verify that the action matches up with the one in
- * opts; we don't support arbitrary instructions
- */
- if (action != opts->action) {
- const char *action_str;
- action_str = action == REPLAY_REVERT ? "revert" : "cherry-pick";
- error(_("Cannot %s during a %s"), action_str, action_name(opts));
- return NULL;
- }
-
- if (status < 0)
- return NULL;
-
- return lookup_commit_reference(commit_sha1);
-}
-
-static int parse_insn_buffer(char *buf, struct commit_list **todo_list,
- struct replay_opts *opts)
-{
- struct commit_list **next = todo_list;
- struct commit *commit;
- char *p = buf;
- int i;
-
- for (i = 1; *p; i++) {
- char *eol = strchrnul(p, '\n');
- commit = parse_insn_line(p, eol, opts);
- if (!commit)
- return error(_("Could not parse line %d."), i);
- next = commit_list_append(commit, next);
- p = *eol ? eol + 1 : eol;
- }
- if (!*todo_list)
- return error(_("No commits parsed."));
- return 0;
-}
-
-static void read_populate_todo(struct commit_list **todo_list,
- struct replay_opts *opts)
-{
- const char *todo_file = git_path(SEQ_TODO_FILE);
- struct strbuf buf = STRBUF_INIT;
- int fd, res;
-
- fd = open(todo_file, O_RDONLY);
- if (fd < 0)
- die_errno(_("Could not open %s"), todo_file);
- if (strbuf_read(&buf, fd, 0) < 0) {
- close(fd);
- strbuf_release(&buf);
- die(_("Could not read %s."), todo_file);
- }
- close(fd);
-
- res = parse_insn_buffer(buf.buf, todo_list, opts);
- strbuf_release(&buf);
- if (res)
- die(_("Unusable instruction sheet: %s"), todo_file);
-}
-
-static int populate_opts_cb(const char *key, const char *value, void *data)
-{
- struct replay_opts *opts = data;
- int error_flag = 1;
-
- if (!value)
- error_flag = 0;
- else if (!strcmp(key, "options.no-commit"))
- opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
- else if (!strcmp(key, "options.edit"))
- opts->edit = git_config_bool_or_int(key, value, &error_flag);
- else if (!strcmp(key, "options.signoff"))
- opts->signoff = git_config_bool_or_int(key, value, &error_flag);
- else if (!strcmp(key, "options.record-origin"))
- opts->record_origin = git_config_bool_or_int(key, value, &error_flag);
- else if (!strcmp(key, "options.allow-ff"))
- opts->allow_ff = git_config_bool_or_int(key, value, &error_flag);
- else if (!strcmp(key, "options.mainline"))
- opts->mainline = git_config_int(key, value);
- else if (!strcmp(key, "options.strategy"))
- git_config_string(&opts->strategy, key, value);
- else if (!strcmp(key, "options.strategy-option")) {
- ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
- opts->xopts[opts->xopts_nr++] = xstrdup(value);
- } else
- return error(_("Invalid key: %s"), key);
-
- if (!error_flag)
- return error(_("Invalid value for %s: %s"), key, value);
-
- return 0;
-}
-
-static void read_populate_opts(struct replay_opts **opts_ptr)
-{
- const char *opts_file = git_path(SEQ_OPTS_FILE);
-
- if (!file_exists(opts_file))
- return;
- if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0)
- die(_("Malformed options sheet: %s"), opts_file);
-}
-
-static void walk_revs_populate_todo(struct commit_list **todo_list,
- struct replay_opts *opts)
-{
- struct commit *commit;
- struct commit_list **next;
-
- prepare_revs(opts);
-
- next = todo_list;
- while ((commit = get_revision(opts->revs)))
- next = commit_list_append(commit, next);
-}
-
-static int create_seq_dir(void)
-{
- const char *seq_dir = git_path(SEQ_DIR);
-
- if (file_exists(seq_dir)) {
- error(_("a cherry-pick or revert is already in progress"));
- advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
- return -1;
- }
- else if (mkdir(seq_dir, 0777) < 0)
- die_errno(_("Could not create sequencer directory %s"), seq_dir);
- return 0;
-}
-
-static void save_head(const char *head)
-{
- const char *head_file = git_path(SEQ_HEAD_FILE);
- static struct lock_file head_lock;
- struct strbuf buf = STRBUF_INIT;
- int fd;
-
- fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);
- strbuf_addf(&buf, "%s\n", head);
- if (write_in_full(fd, buf.buf, buf.len) < 0)
- die_errno(_("Could not write to %s"), head_file);
- if (commit_lock_file(&head_lock) < 0)
- die(_("Error wrapping up %s."), head_file);
-}
-
-static int reset_for_rollback(const unsigned char *sha1)
-{
- const char *argv[4]; /* reset --merge <arg> + NULL */
- argv[0] = "reset";
- argv[1] = "--merge";
- argv[2] = sha1_to_hex(sha1);
- argv[3] = NULL;
- return run_command_v_opt(argv, RUN_GIT_CMD);
-}
-
-static int rollback_single_pick(void)
-{
- unsigned char head_sha1[20];
-
- if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
- !file_exists(git_path("REVERT_HEAD")))
- return error(_("no cherry-pick or revert in progress"));
- if (read_ref_full("HEAD", head_sha1, 0, NULL))
- return error(_("cannot resolve HEAD"));
- if (is_null_sha1(head_sha1))
- return error(_("cannot abort from a branch yet to be born"));
- return reset_for_rollback(head_sha1);
-}
-
-static int sequencer_rollback(struct replay_opts *opts)
-{
- const char *filename;
- FILE *f;
- unsigned char sha1[20];
- struct strbuf buf = STRBUF_INIT;
-
- filename = git_path(SEQ_HEAD_FILE);
- f = fopen(filename, "r");
- if (!f && errno == ENOENT) {
- /*
- * There is no multiple-cherry-pick in progress.
- * If CHERRY_PICK_HEAD or REVERT_HEAD indicates
- * a single-cherry-pick in progress, abort that.
- */
- return rollback_single_pick();
- }
- if (!f)
- return error(_("cannot open %s: %s"), filename,
- strerror(errno));
- if (strbuf_getline(&buf, f, '\n')) {
- error(_("cannot read %s: %s"), filename, ferror(f) ?
- strerror(errno) : _("unexpected end of file"));
- fclose(f);
- goto fail;
- }
- fclose(f);
- if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') {
- error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"),
- filename);
- goto fail;
- }
- if (reset_for_rollback(sha1))
- goto fail;
- remove_sequencer_state();
- strbuf_release(&buf);
- return 0;
-fail:
- strbuf_release(&buf);
- return -1;
-}
-
-static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
-{
- const char *todo_file = git_path(SEQ_TODO_FILE);
- static struct lock_file todo_lock;
- struct strbuf buf = STRBUF_INIT;
- int fd;
-
- fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR);
- if (format_todo(&buf, todo_list, opts) < 0)
- die(_("Could not format %s."), todo_file);
- if (write_in_full(fd, buf.buf, buf.len) < 0) {
- strbuf_release(&buf);
- die_errno(_("Could not write to %s"), todo_file);
- }
- if (commit_lock_file(&todo_lock) < 0) {
- strbuf_release(&buf);
- die(_("Error wrapping up %s."), todo_file);
- }
- strbuf_release(&buf);
-}
-
-static void save_opts(struct replay_opts *opts)
-{
- const char *opts_file = git_path(SEQ_OPTS_FILE);
-
- if (opts->no_commit)
- git_config_set_in_file(opts_file, "options.no-commit", "true");
- if (opts->edit)
- git_config_set_in_file(opts_file, "options.edit", "true");
- if (opts->signoff)
- git_config_set_in_file(opts_file, "options.signoff", "true");
- if (opts->record_origin)
- git_config_set_in_file(opts_file, "options.record-origin", "true");
- if (opts->allow_ff)
- git_config_set_in_file(opts_file, "options.allow-ff", "true");
- if (opts->mainline) {
- struct strbuf buf = STRBUF_INIT;
- strbuf_addf(&buf, "%d", opts->mainline);
- git_config_set_in_file(opts_file, "options.mainline", buf.buf);
- strbuf_release(&buf);
- }
- if (opts->strategy)
- git_config_set_in_file(opts_file, "options.strategy", opts->strategy);
- if (opts->xopts) {
- int i;
- for (i = 0; i < opts->xopts_nr; i++)
- git_config_set_multivar_in_file(opts_file,
- "options.strategy-option",
- opts->xopts[i], "^$", 0);
- }
-}
-
-static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
-{
- struct commit_list *cur;
- int res;
-
- setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
- if (opts->allow_ff)
- assert(!(opts->signoff || opts->no_commit ||
- opts->record_origin || opts->edit));
- read_and_refresh_cache(opts);
-
- for (cur = todo_list; cur; cur = cur->next) {
- save_todo(cur, opts);
- res = do_pick_commit(cur->item, opts);
- if (res)
- return res;
- }
-
- /*
- * Sequence of picks finished successfully; cleanup by
- * removing the .git/sequencer directory
- */
- remove_sequencer_state();
- return 0;
-}
-
-static int continue_single_pick(void)
-{
- const char *argv[] = { "commit", NULL };
-
- if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
- !file_exists(git_path("REVERT_HEAD")))
- return error(_("no cherry-pick or revert in progress"));
- return run_command_v_opt(argv, RUN_GIT_CMD);
-}
-
-static int sequencer_continue(struct replay_opts *opts)
-{
- struct commit_list *todo_list = NULL;
-
- if (!file_exists(git_path(SEQ_TODO_FILE)))
- return continue_single_pick();
- read_populate_opts(&opts);
- read_populate_todo(&todo_list, opts);
-
- /* Verify that the conflict has been resolved */
- if (file_exists(git_path("CHERRY_PICK_HEAD")) ||
- file_exists(git_path("REVERT_HEAD"))) {
- int ret = continue_single_pick();
- if (ret)
- return ret;
- }
- if (index_differs_from("HEAD", 0))
- return error_dirty_index(opts);
- todo_list = todo_list->next;
- return pick_commits(todo_list, opts);
-}
-
-static int single_pick(struct commit *cmit, struct replay_opts *opts)
-{
- setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
- return do_pick_commit(cmit, opts);
-}
-
-static int pick_revisions(struct replay_opts *opts)
-{
- struct commit_list *todo_list = NULL;
- unsigned char sha1[20];
-
- if (opts->subcommand == REPLAY_NONE)
- assert(opts->revs);
-
- read_and_refresh_cache(opts);
-
- /*
- * Decide what to do depending on the arguments; a fresh
- * cherry-pick should be handled differently from an existing
- * one that is being continued
- */
- if (opts->subcommand == REPLAY_REMOVE_STATE) {
- remove_sequencer_state();
- return 0;
- }
- if (opts->subcommand == REPLAY_ROLLBACK)
- return sequencer_rollback(opts);
- if (opts->subcommand == REPLAY_CONTINUE)
- return sequencer_continue(opts);
-
- /*
- * If we were called as "git cherry-pick <commit>", just
- * cherry-pick/revert it, set CHERRY_PICK_HEAD /
- * REVERT_HEAD, and don't touch the sequencer state.
- * This means it is possible to cherry-pick in the middle
- * of a cherry-pick sequence.
- */
- if (opts->revs->cmdline.nr == 1 &&
- opts->revs->cmdline.rev->whence == REV_CMD_REV &&
- opts->revs->no_walk &&
- !opts->revs->cmdline.rev->flags) {
- struct commit *cmit;
- if (prepare_revision_walk(opts->revs))
- die(_("revision walk setup failed"));
- cmit = get_revision(opts->revs);
- if (!cmit || get_revision(opts->revs))
- die("BUG: expected exactly one commit from walk");
- return single_pick(cmit, opts);
- }
-
- /*
- * Start a new cherry-pick/ revert sequence; but
- * first, make sure that an existing one isn't in
- * progress
- */
-
- walk_revs_populate_todo(&todo_list, opts);
- if (create_seq_dir() < 0)
- return -1;
- if (get_sha1("HEAD", sha1)) {
- if (opts->action == REPLAY_REVERT)
- return error(_("Can't revert as initial commit"));
- return error(_("Can't cherry-pick into empty head"));
- }
- save_head(sha1_to_hex(sha1));
- save_opts(opts);
- return pick_commits(todo_list, opts);
-}
-
int cmd_revert(int argc, const char **argv, const char *prefix)
{
struct replay_opts opts;
@@ -1148,7 +206,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
opts.action = REPLAY_REVERT;
git_config(git_default_config, NULL);
parse_args(argc, argv, &opts);
- res = pick_revisions(&opts);
+ res = sequencer_pick_revisions(&opts);
if (res < 0)
die(_("revert failed"));
return res;
@@ -1163,7 +221,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
opts.action = REPLAY_PICK;
git_config(git_default_config, NULL);
parse_args(argc, argv, &opts);
- res = pick_revisions(&opts);
+ res = sequencer_pick_revisions(&opts);
if (res < 0)
die(_("cherry-pick failed"));
return res;
diff --git a/sequencer.c b/sequencer.c
index d1f28a6..5477119 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1,7 +1,20 @@
#include "cache.h"
#include "sequencer.h"
-#include "strbuf.h"
#include "dir.h"
+#include "object.h"
+#include "commit.h"
+#include "tag.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+#include "utf8.h"
+#include "cache-tree.h"
+#include "diff.h"
+#include "revision.h"
+#include "rerere.h"
+#include "merge-recursive.h"
+#include "refs.h"
+
+#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
void remove_sequencer_state(void)
{
@@ -11,3 +24,906 @@ void remove_sequencer_state(void)
remove_dir_recursively(&seq_dir, 0);
strbuf_release(&seq_dir);
}
+
+static const char *action_name(const struct replay_opts *opts)
+{
+ return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
+}
+
+static char *get_encoding(const char *message);
+
+struct commit_message {
+ char *parent_label;
+ const char *label;
+ const char *subject;
+ char *reencoded_message;
+ const char *message;
+};
+
+static int get_message(struct commit *commit, struct commit_message *out)
+{
+ const char *encoding;
+ const char *abbrev, *subject;
+ int abbrev_len, subject_len;
+ char *q;
+
+ if (!commit->buffer)
+ return -1;
+ encoding = get_encoding(commit->buffer);
+ if (!encoding)
+ encoding = "UTF-8";
+ if (!git_commit_encoding)
+ git_commit_encoding = "UTF-8";
+
+ out->reencoded_message = NULL;
+ out->message = commit->buffer;
+ if (strcmp(encoding, git_commit_encoding))
+ out->reencoded_message = reencode_string(commit->buffer,
+ git_commit_encoding, encoding);
+ if (out->reencoded_message)
+ out->message = out->reencoded_message;
+
+ abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
+ abbrev_len = strlen(abbrev);
+
+ subject_len = find_commit_subject(out->message, &subject);
+
+ out->parent_label = xmalloc(strlen("parent of ") + abbrev_len +
+ strlen("... ") + subject_len + 1);
+ q = out->parent_label;
+ q = mempcpy(q, "parent of ", strlen("parent of "));
+ out->label = q;
+ q = mempcpy(q, abbrev, abbrev_len);
+ q = mempcpy(q, "... ", strlen("... "));
+ out->subject = q;
+ q = mempcpy(q, subject, subject_len);
+ *q = '\0';
+ return 0;
+}
+
+static void free_message(struct commit_message *msg)
+{
+ free(msg->parent_label);
+ free(msg->reencoded_message);
+}
+
+static char *get_encoding(const char *message)
+{
+ const char *p = message, *eol;
+
+ while (*p && *p != '\n') {
+ for (eol = p + 1; *eol && *eol != '\n'; eol++)
+ ; /* do nothing */
+ if (!prefixcmp(p, "encoding ")) {
+ char *result = xmalloc(eol - 8 - p);
+ strlcpy(result, p + 9, eol - 8 - p);
+ return result;
+ }
+ p = eol;
+ if (*p == '\n')
+ p++;
+ }
+ return NULL;
+}
+
+static void write_cherry_pick_head(struct commit *commit, const char *pseudoref)
+{
+ const char *filename;
+ int fd;
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
+
+ filename = git_path("%s", pseudoref);
+ fd = open(filename, O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno(_("Could not open '%s' for writing"), filename);
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
+ die_errno(_("Could not write to '%s'"), filename);
+ strbuf_release(&buf);
+}
+
+static void print_advice(int show_hint)
+{
+ char *msg = getenv("GIT_CHERRY_PICK_HELP");
+
+ if (msg) {
+ fprintf(stderr, "%s\n", msg);
+ /*
+ * A conflict has occured but the porcelain
+ * (typically rebase --interactive) wants to take care
+ * of the commit itself so remove CHERRY_PICK_HEAD
+ */
+ unlink(git_path("CHERRY_PICK_HEAD"));
+ return;
+ }
+
+ if (show_hint) {
+ advise("after resolving the conflicts, mark the corrected paths");
+ advise("with 'git add <paths>' or 'git rm <paths>'");
+ advise("and commit the result with 'git commit'");
+ }
+}
+
+static void write_message(struct strbuf *msgbuf, const char *filename)
+{
+ static struct lock_file msg_file;
+
+ int msg_fd = hold_lock_file_for_update(&msg_file, filename,
+ LOCK_DIE_ON_ERROR);
+ if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
+ die_errno(_("Could not write to %s"), filename);
+ strbuf_release(msgbuf);
+ if (commit_lock_file(&msg_file) < 0)
+ die(_("Error wrapping up %s"), filename);
+}
+
+static struct tree *empty_tree(void)
+{
+ return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
+}
+
+static int error_dirty_index(struct replay_opts *opts)
+{
+ if (read_cache_unmerged())
+ return error_resolve_conflict(action_name(opts));
+
+ /* Different translation strings for cherry-pick and revert */
+ if (opts->action == REPLAY_PICK)
+ error(_("Your local changes would be overwritten by cherry-pick."));
+ else
+ error(_("Your local changes would be overwritten by revert."));
+
+ if (advice_commit_before_merge)
+ advise(_("Commit your changes or stash them to proceed."));
+ return -1;
+}
+
+static int fast_forward_to(const unsigned char *to, const unsigned char *from)
+{
+ struct ref_lock *ref_lock;
+
+ read_cache();
+ if (checkout_fast_forward(from, to))
+ exit(1); /* the callee should have complained already */
+ ref_lock = lock_any_ref_for_update("HEAD", from, 0);
+ return write_ref_sha1(ref_lock, to, "cherry-pick");
+}
+
+static int do_recursive_merge(struct commit *base, struct commit *next,
+ const char *base_label, const char *next_label,
+ unsigned char *head, struct strbuf *msgbuf,
+ struct replay_opts *opts)
+{
+ struct merge_options o;
+ struct tree *result, *next_tree, *base_tree, *head_tree;
+ int clean, index_fd;
+ const char **xopt;
+ static struct lock_file index_lock;
+
+ index_fd = hold_locked_index(&index_lock, 1);
+
+ read_cache();
+
+ init_merge_options(&o);
+ o.ancestor = base ? base_label : "(empty tree)";
+ o.branch1 = "HEAD";
+ o.branch2 = next ? next_label : "(empty tree)";
+
+ head_tree = parse_tree_indirect(head);
+ next_tree = next ? next->tree : empty_tree();
+ base_tree = base ? base->tree : empty_tree();
+
+ for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
+ parse_merge_opt(&o, *xopt);
+
+ clean = merge_trees(&o,
+ head_tree,
+ next_tree, base_tree, &result);
+
+ if (active_cache_changed &&
+ (write_cache(index_fd, active_cache, active_nr) ||
+ commit_locked_index(&index_lock)))
+ /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
+ die(_("%s: Unable to write new index file"), action_name(opts));
+ rollback_lock_file(&index_lock);
+
+ if (!clean) {
+ int i;
+ strbuf_addstr(msgbuf, "\nConflicts:\n\n");
+ for (i = 0; i < active_nr;) {
+ struct cache_entry *ce = active_cache[i++];
+ if (ce_stage(ce)) {
+ strbuf_addch(msgbuf, '\t');
+ strbuf_addstr(msgbuf, ce->name);
+ strbuf_addch(msgbuf, '\n');
+ while (i < active_nr && !strcmp(ce->name,
+ active_cache[i]->name))
+ i++;
+ }
+ }
+ }
+
+ return !clean;
+}
+
+/*
+ * If we are cherry-pick, and if the merge did not result in
+ * hand-editing, we will hit this commit and inherit the original
+ * author date and name.
+ * If we are revert, or if our cherry-pick results in a hand merge,
+ * we had better say that the current user is responsible for that.
+ */
+static int run_git_commit(const char *defmsg, struct replay_opts *opts)
+{
+ /* 6 is max possible length of our args array including NULL */
+ const char *args[6];
+ int i = 0;
+
+ args[i++] = "commit";
+ args[i++] = "-n";
+ if (opts->signoff)
+ args[i++] = "-s";
+ if (!opts->edit) {
+ args[i++] = "-F";
+ args[i++] = defmsg;
+ }
+ args[i] = NULL;
+
+ return run_command_v_opt(args, RUN_GIT_CMD);
+}
+
+static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
+{
+ unsigned char head[20];
+ struct commit *base, *next, *parent;
+ const char *base_label, *next_label;
+ struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
+ char *defmsg = NULL;
+ struct strbuf msgbuf = STRBUF_INIT;
+ int res;
+
+ if (opts->no_commit) {
+ /*
+ * We do not intend to commit immediately. We just want to
+ * merge the differences in, so let's compute the tree
+ * that represents the "current" state for merge-recursive
+ * to work on.
+ */
+ if (write_cache_as_tree(head, 0, NULL))
+ die (_("Your index file is unmerged."));
+ } else {
+ if (get_sha1("HEAD", head))
+ return error(_("You do not have a valid HEAD"));
+ if (index_differs_from("HEAD", 0))
+ return error_dirty_index(opts);
+ }
+ discard_cache();
+
+ if (!commit->parents) {
+ parent = NULL;
+ }
+ else if (commit->parents->next) {
+ /* Reverting or cherry-picking a merge commit */
+ int cnt;
+ struct commit_list *p;
+
+ if (!opts->mainline)
+ return error(_("Commit %s is a merge but no -m option was given."),
+ sha1_to_hex(commit->object.sha1));
+
+ for (cnt = 1, p = commit->parents;
+ cnt != opts->mainline && p;
+ cnt++)
+ p = p->next;
+ if (cnt != opts->mainline || !p)
+ return error(_("Commit %s does not have parent %d"),
+ sha1_to_hex(commit->object.sha1), opts->mainline);
+ parent = p->item;
+ } else if (0 < opts->mainline)
+ return error(_("Mainline was specified but commit %s is not a merge."),
+ sha1_to_hex(commit->object.sha1));
+ else
+ parent = commit->parents->item;
+
+ if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head))
+ return fast_forward_to(commit->object.sha1, head);
+
+ if (parent && parse_commit(parent) < 0)
+ /* TRANSLATORS: The first %s will be "revert" or
+ "cherry-pick", the second %s a SHA1 */
+ return error(_("%s: cannot parse parent commit %s"),
+ action_name(opts), sha1_to_hex(parent->object.sha1));
+
+ if (get_message(commit, &msg) != 0)
+ return error(_("Cannot get commit message for %s"),
+ sha1_to_hex(commit->object.sha1));
+
+ /*
+ * "commit" is an existing commit. We would want to apply
+ * the difference it introduces since its first parent "prev"
+ * on top of the current HEAD if we are cherry-pick. Or the
+ * reverse of it if we are revert.
+ */
+
+ defmsg = git_pathdup("MERGE_MSG");
+
+ if (opts->action == REPLAY_REVERT) {
+ base = commit;
+ base_label = msg.label;
+ next = parent;
+ next_label = msg.parent_label;
+ strbuf_addstr(&msgbuf, "Revert \"");
+ strbuf_addstr(&msgbuf, msg.subject);
+ strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
+ strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
+
+ if (commit->parents && commit->parents->next) {
+ strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
+ strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));
+ }
+ strbuf_addstr(&msgbuf, ".\n");
+ } else {
+ const char *p;
+
+ base = parent;
+ base_label = msg.parent_label;
+ next = commit;
+ next_label = msg.label;
+
+ /*
+ * Append the commit log message to msgbuf; it starts
+ * after the tree, parent, author, committer
+ * information followed by "\n\n".
+ */
+ p = strstr(msg.message, "\n\n");
+ if (p) {
+ p += 2;
+ strbuf_addstr(&msgbuf, p);
+ }
+
+ if (opts->record_origin) {
+ strbuf_addstr(&msgbuf, "(cherry picked from commit ");
+ strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
+ strbuf_addstr(&msgbuf, ")\n");
+ }
+ }
+
+ if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
+ res = do_recursive_merge(base, next, base_label, next_label,
+ head, &msgbuf, opts);
+ write_message(&msgbuf, defmsg);
+ } else {
+ struct commit_list *common = NULL;
+ struct commit_list *remotes = NULL;
+
+ write_message(&msgbuf, defmsg);
+
+ commit_list_insert(base, &common);
+ commit_list_insert(next, &remotes);
+ res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts,
+ common, sha1_to_hex(head), remotes);
+ free_commit_list(common);
+ free_commit_list(remotes);
+ }
+
+ /*
+ * If the merge was clean or if it failed due to conflict, we write
+ * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
+ * However, if the merge did not even start, then we don't want to
+ * write it at all.
+ */
+ if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1))
+ write_cherry_pick_head(commit, "CHERRY_PICK_HEAD");
+ if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1))
+ write_cherry_pick_head(commit, "REVERT_HEAD");
+
+ if (res) {
+ error(opts->action == REPLAY_REVERT
+ ? _("could not revert %s... %s")
+ : _("could not apply %s... %s"),
+ find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
+ msg.subject);
+ print_advice(res == 1);
+ rerere(opts->allow_rerere_auto);
+ } else {
+ if (!opts->no_commit)
+ res = run_git_commit(defmsg, opts);
+ }
+
+ free_message(&msg);
+ free(defmsg);
+
+ return res;
+}
+
+static void prepare_revs(struct replay_opts *opts)
+{
+ if (opts->action != REPLAY_REVERT)
+ opts->revs->reverse ^= 1;
+
+ if (prepare_revision_walk(opts->revs))
+ die(_("revision walk setup failed"));
+
+ if (!opts->revs->commits)
+ die(_("empty commit set passed"));
+}
+
+static void read_and_refresh_cache(struct replay_opts *opts)
+{
+ static struct lock_file index_lock;
+ int index_fd = hold_locked_index(&index_lock, 0);
+ if (read_index_preload(&the_index, NULL) < 0)
+ die(_("git %s: failed to read the index"), action_name(opts));
+ refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
+ if (the_index.cache_changed) {
+ if (write_index(&the_index, index_fd) ||
+ commit_locked_index(&index_lock))
+ die(_("git %s: failed to refresh the index"), action_name(opts));
+ }
+ rollback_lock_file(&index_lock);
+}
+
+/*
+ * Append a commit to the end of the commit_list.
+ *
+ * next starts by pointing to the variable that holds the head of an
+ * empty commit_list, and is updated to point to the "next" field of
+ * the last item on the list as new commits are appended.
+ *
+ * Usage example:
+ *
+ * struct commit_list *list;
+ * struct commit_list **next = &list;
+ *
+ * next = commit_list_append(c1, next);
+ * next = commit_list_append(c2, next);
+ * assert(commit_list_count(list) == 2);
+ * return list;
+ */
+static struct commit_list **commit_list_append(struct commit *commit,
+ struct commit_list **next)
+{
+ struct commit_list *new = xmalloc(sizeof(struct commit_list));
+ new->item = commit;
+ *next = new;
+ new->next = NULL;
+ return &new->next;
+}
+
+static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
+ struct replay_opts *opts)
+{
+ struct commit_list *cur = NULL;
+ const char *sha1_abbrev = NULL;
+ const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick";
+ const char *subject;
+ int subject_len;
+
+ for (cur = todo_list; cur; cur = cur->next) {
+ sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
+ subject_len = find_commit_subject(cur->item->buffer, &subject);
+ strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev,
+ subject_len, subject);
+ }
+ return 0;
+}
+
+static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts)
+{
+ unsigned char commit_sha1[20];
+ enum replay_action action;
+ char *end_of_object_name;
+ int saved, status, padding;
+
+ if (!prefixcmp(bol, "pick")) {
+ action = REPLAY_PICK;
+ bol += strlen("pick");
+ } else if (!prefixcmp(bol, "revert")) {
+ action = REPLAY_REVERT;
+ bol += strlen("revert");
+ } else
+ return NULL;
+
+ /* Eat up extra spaces/ tabs before object name */
+ padding = strspn(bol, " \t");
+ if (!padding)
+ return NULL;
+ bol += padding;
+
+ end_of_object_name = bol + strcspn(bol, " \t\n");
+ saved = *end_of_object_name;
+ *end_of_object_name = '\0';
+ status = get_sha1(bol, commit_sha1);
+ *end_of_object_name = saved;
+
+ /*
+ * Verify that the action matches up with the one in
+ * opts; we don't support arbitrary instructions
+ */
+ if (action != opts->action) {
+ const char *action_str;
+ action_str = action == REPLAY_REVERT ? "revert" : "cherry-pick";
+ error(_("Cannot %s during a %s"), action_str, action_name(opts));
+ return NULL;
+ }
+
+ if (status < 0)
+ return NULL;
+
+ return lookup_commit_reference(commit_sha1);
+}
+
+static int parse_insn_buffer(char *buf, struct commit_list **todo_list,
+ struct replay_opts *opts)
+{
+ struct commit_list **next = todo_list;
+ struct commit *commit;
+ char *p = buf;
+ int i;
+
+ for (i = 1; *p; i++) {
+ char *eol = strchrnul(p, '\n');
+ commit = parse_insn_line(p, eol, opts);
+ if (!commit)
+ return error(_("Could not parse line %d."), i);
+ next = commit_list_append(commit, next);
+ p = *eol ? eol + 1 : eol;
+ }
+ if (!*todo_list)
+ return error(_("No commits parsed."));
+ return 0;
+}
+
+static void read_populate_todo(struct commit_list **todo_list,
+ struct replay_opts *opts)
+{
+ const char *todo_file = git_path(SEQ_TODO_FILE);
+ struct strbuf buf = STRBUF_INIT;
+ int fd, res;
+
+ fd = open(todo_file, O_RDONLY);
+ if (fd < 0)
+ die_errno(_("Could not open %s"), todo_file);
+ if (strbuf_read(&buf, fd, 0) < 0) {
+ close(fd);
+ strbuf_release(&buf);
+ die(_("Could not read %s."), todo_file);
+ }
+ close(fd);
+
+ res = parse_insn_buffer(buf.buf, todo_list, opts);
+ strbuf_release(&buf);
+ if (res)
+ die(_("Unusable instruction sheet: %s"), todo_file);
+}
+
+static int populate_opts_cb(const char *key, const char *value, void *data)
+{
+ struct replay_opts *opts = data;
+ int error_flag = 1;
+
+ if (!value)
+ error_flag = 0;
+ else if (!strcmp(key, "options.no-commit"))
+ opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
+ else if (!strcmp(key, "options.edit"))
+ opts->edit = git_config_bool_or_int(key, value, &error_flag);
+ else if (!strcmp(key, "options.signoff"))
+ opts->signoff = git_config_bool_or_int(key, value, &error_flag);
+ else if (!strcmp(key, "options.record-origin"))
+ opts->record_origin = git_config_bool_or_int(key, value, &error_flag);
+ else if (!strcmp(key, "options.allow-ff"))
+ opts->allow_ff = git_config_bool_or_int(key, value, &error_flag);
+ else if (!strcmp(key, "options.mainline"))
+ opts->mainline = git_config_int(key, value);
+ else if (!strcmp(key, "options.strategy"))
+ git_config_string(&opts->strategy, key, value);
+ else if (!strcmp(key, "options.strategy-option")) {
+ ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
+ opts->xopts[opts->xopts_nr++] = xstrdup(value);
+ } else
+ return error(_("Invalid key: %s"), key);
+
+ if (!error_flag)
+ return error(_("Invalid value for %s: %s"), key, value);
+
+ return 0;
+}
+
+static void read_populate_opts(struct replay_opts **opts_ptr)
+{
+ const char *opts_file = git_path(SEQ_OPTS_FILE);
+
+ if (!file_exists(opts_file))
+ return;
+ if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0)
+ die(_("Malformed options sheet: %s"), opts_file);
+}
+
+static void walk_revs_populate_todo(struct commit_list **todo_list,
+ struct replay_opts *opts)
+{
+ struct commit *commit;
+ struct commit_list **next;
+
+ prepare_revs(opts);
+
+ next = todo_list;
+ while ((commit = get_revision(opts->revs)))
+ next = commit_list_append(commit, next);
+}
+
+static int create_seq_dir(void)
+{
+ const char *seq_dir = git_path(SEQ_DIR);
+
+ if (file_exists(seq_dir)) {
+ error(_("a cherry-pick or revert is already in progress"));
+ advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
+ return -1;
+ }
+ else if (mkdir(seq_dir, 0777) < 0)
+ die_errno(_("Could not create sequencer directory %s"), seq_dir);
+ return 0;
+}
+
+static void save_head(const char *head)
+{
+ const char *head_file = git_path(SEQ_HEAD_FILE);
+ static struct lock_file head_lock;
+ struct strbuf buf = STRBUF_INIT;
+ int fd;
+
+ fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);
+ strbuf_addf(&buf, "%s\n", head);
+ if (write_in_full(fd, buf.buf, buf.len) < 0)
+ die_errno(_("Could not write to %s"), head_file);
+ if (commit_lock_file(&head_lock) < 0)
+ die(_("Error wrapping up %s."), head_file);
+}
+
+static int reset_for_rollback(const unsigned char *sha1)
+{
+ const char *argv[4]; /* reset --merge <arg> + NULL */
+ argv[0] = "reset";
+ argv[1] = "--merge";
+ argv[2] = sha1_to_hex(sha1);
+ argv[3] = NULL;
+ return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int rollback_single_pick(void)
+{
+ unsigned char head_sha1[20];
+
+ if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
+ !file_exists(git_path("REVERT_HEAD")))
+ return error(_("no cherry-pick or revert in progress"));
+ if (read_ref_full("HEAD", head_sha1, 0, NULL))
+ return error(_("cannot resolve HEAD"));
+ if (is_null_sha1(head_sha1))
+ return error(_("cannot abort from a branch yet to be born"));
+ return reset_for_rollback(head_sha1);
+}
+
+static int sequencer_rollback(struct replay_opts *opts)
+{
+ const char *filename;
+ FILE *f;
+ unsigned char sha1[20];
+ struct strbuf buf = STRBUF_INIT;
+
+ filename = git_path(SEQ_HEAD_FILE);
+ f = fopen(filename, "r");
+ if (!f && errno == ENOENT) {
+ /*
+ * There is no multiple-cherry-pick in progress.
+ * If CHERRY_PICK_HEAD or REVERT_HEAD indicates
+ * a single-cherry-pick in progress, abort that.
+ */
+ return rollback_single_pick();
+ }
+ if (!f)
+ return error(_("cannot open %s: %s"), filename,
+ strerror(errno));
+ if (strbuf_getline(&buf, f, '\n')) {
+ error(_("cannot read %s: %s"), filename, ferror(f) ?
+ strerror(errno) : _("unexpected end of file"));
+ fclose(f);
+ goto fail;
+ }
+ fclose(f);
+ if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') {
+ error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"),
+ filename);
+ goto fail;
+ }
+ if (reset_for_rollback(sha1))
+ goto fail;
+ remove_sequencer_state();
+ strbuf_release(&buf);
+ return 0;
+fail:
+ strbuf_release(&buf);
+ return -1;
+}
+
+static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
+{
+ const char *todo_file = git_path(SEQ_TODO_FILE);
+ static struct lock_file todo_lock;
+ struct strbuf buf = STRBUF_INIT;
+ int fd;
+
+ fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR);
+ if (format_todo(&buf, todo_list, opts) < 0)
+ die(_("Could not format %s."), todo_file);
+ if (write_in_full(fd, buf.buf, buf.len) < 0) {
+ strbuf_release(&buf);
+ die_errno(_("Could not write to %s"), todo_file);
+ }
+ if (commit_lock_file(&todo_lock) < 0) {
+ strbuf_release(&buf);
+ die(_("Error wrapping up %s."), todo_file);
+ }
+ strbuf_release(&buf);
+}
+
+static void save_opts(struct replay_opts *opts)
+{
+ const char *opts_file = git_path(SEQ_OPTS_FILE);
+
+ if (opts->no_commit)
+ git_config_set_in_file(opts_file, "options.no-commit", "true");
+ if (opts->edit)
+ git_config_set_in_file(opts_file, "options.edit", "true");
+ if (opts->signoff)
+ git_config_set_in_file(opts_file, "options.signoff", "true");
+ if (opts->record_origin)
+ git_config_set_in_file(opts_file, "options.record-origin", "true");
+ if (opts->allow_ff)
+ git_config_set_in_file(opts_file, "options.allow-ff", "true");
+ if (opts->mainline) {
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addf(&buf, "%d", opts->mainline);
+ git_config_set_in_file(opts_file, "options.mainline", buf.buf);
+ strbuf_release(&buf);
+ }
+ if (opts->strategy)
+ git_config_set_in_file(opts_file, "options.strategy", opts->strategy);
+ if (opts->xopts) {
+ int i;
+ for (i = 0; i < opts->xopts_nr; i++)
+ git_config_set_multivar_in_file(opts_file,
+ "options.strategy-option",
+ opts->xopts[i], "^$", 0);
+ }
+}
+
+static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
+{
+ struct commit_list *cur;
+ int res;
+
+ setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+ if (opts->allow_ff)
+ assert(!(opts->signoff || opts->no_commit ||
+ opts->record_origin || opts->edit));
+ read_and_refresh_cache(opts);
+
+ for (cur = todo_list; cur; cur = cur->next) {
+ save_todo(cur, opts);
+ res = do_pick_commit(cur->item, opts);
+ if (res)
+ return res;
+ }
+
+ /*
+ * Sequence of picks finished successfully; cleanup by
+ * removing the .git/sequencer directory
+ */
+ remove_sequencer_state();
+ return 0;
+}
+
+static int continue_single_pick(void)
+{
+ const char *argv[] = { "commit", NULL };
+
+ if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
+ !file_exists(git_path("REVERT_HEAD")))
+ return error(_("no cherry-pick or revert in progress"));
+ return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int sequencer_continue(struct replay_opts *opts)
+{
+ struct commit_list *todo_list = NULL;
+
+ if (!file_exists(git_path(SEQ_TODO_FILE)))
+ return continue_single_pick();
+ read_populate_opts(&opts);
+ read_populate_todo(&todo_list, opts);
+
+ /* Verify that the conflict has been resolved */
+ if (file_exists(git_path("CHERRY_PICK_HEAD")) ||
+ file_exists(git_path("REVERT_HEAD"))) {
+ int ret = continue_single_pick();
+ if (ret)
+ return ret;
+ }
+ if (index_differs_from("HEAD", 0))
+ return error_dirty_index(opts);
+ todo_list = todo_list->next;
+ return pick_commits(todo_list, opts);
+}
+
+static int single_pick(struct commit *cmit, struct replay_opts *opts)
+{
+ setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+ return do_pick_commit(cmit, opts);
+}
+
+int sequencer_pick_revisions(struct replay_opts *opts)
+{
+ struct commit_list *todo_list = NULL;
+ unsigned char sha1[20];
+
+ if (opts->subcommand == REPLAY_NONE)
+ assert(opts->revs);
+
+ read_and_refresh_cache(opts);
+
+ /*
+ * Decide what to do depending on the arguments; a fresh
+ * cherry-pick should be handled differently from an existing
+ * one that is being continued
+ */
+ if (opts->subcommand == REPLAY_REMOVE_STATE) {
+ remove_sequencer_state();
+ return 0;
+ }
+ if (opts->subcommand == REPLAY_ROLLBACK)
+ return sequencer_rollback(opts);
+ if (opts->subcommand == REPLAY_CONTINUE)
+ return sequencer_continue(opts);
+
+ /*
+ * If we were called as "git cherry-pick <commit>", just
+ * cherry-pick/revert it, set CHERRY_PICK_HEAD /
+ * REVERT_HEAD, and don't touch the sequencer state.
+ * This means it is possible to cherry-pick in the middle
+ * of a cherry-pick sequence.
+ */
+ if (opts->revs->cmdline.nr == 1 &&
+ opts->revs->cmdline.rev->whence == REV_CMD_REV &&
+ opts->revs->no_walk &&
+ !opts->revs->cmdline.rev->flags) {
+ struct commit *cmit;
+ if (prepare_revision_walk(opts->revs))
+ die(_("revision walk setup failed"));
+ cmit = get_revision(opts->revs);
+ if (!cmit || get_revision(opts->revs))
+ die("BUG: expected exactly one commit from walk");
+ return single_pick(cmit, opts);
+ }
+
+ /*
+ * Start a new cherry-pick/ revert sequence; but
+ * first, make sure that an existing one isn't in
+ * progress
+ */
+
+ walk_revs_populate_todo(&todo_list, opts);
+ if (create_seq_dir() < 0)
+ return -1;
+ if (get_sha1("HEAD", sha1)) {
+ if (opts->action == REPLAY_REVERT)
+ return error(_("Can't revert as initial commit"));
+ return error(_("Can't cherry-pick into empty head"));
+ }
+ save_head(sha1_to_hex(sha1));
+ save_opts(opts);
+ return pick_commits(todo_list, opts);
+}
diff --git a/sequencer.h b/sequencer.h
index 2d4528f..bb4b138 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -6,7 +6,44 @@
#define SEQ_TODO_FILE "sequencer/todo"
#define SEQ_OPTS_FILE "sequencer/opts"
+enum replay_action {
+ REPLAY_REVERT,
+ REPLAY_PICK
+};
+
+enum replay_subcommand {
+ REPLAY_NONE,
+ REPLAY_REMOVE_STATE,
+ REPLAY_CONTINUE,
+ REPLAY_ROLLBACK
+};
+
+struct replay_opts {
+ enum replay_action action;
+ enum replay_subcommand subcommand;
+
+ /* Boolean options */
+ int edit;
+ int record_origin;
+ int no_commit;
+ int signoff;
+ int allow_ff;
+ int allow_rerere_auto;
+
+ int mainline;
+
+ /* Merge strategy */
+ const char *strategy;
+ const char **xopts;
+ size_t xopts_nr, xopts_alloc;
+
+ /* Only used by REPLAY_NONE */
+ struct rev_info *revs;
+};
+
/* Removes SEQ_DIR. */
extern void remove_sequencer_state(void);
+int sequencer_pick_revisions(struct replay_opts *opts);
+
#endif
--
1.7.8.2
^ permalink raw reply related
* Re: [PATCH v3 0/2] The move to sequencer.c
From: Jonathan Nieder @ 2012-01-11 18:40 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Ramkumar Ramachandra, Git List, Christian Couder
In-Reply-To: <1326305757-27525-1-git-send-email-artagnon@gmail.com>
Ramkumar Ramachandra wrote:
> Ramkumar Ramachandra (2):
> revert: prepare to move replay_action to header
> sequencer: factor code out of revert builtin
Ah. "git diff HEAD^:builtin/revert.c HEAD:sequencer.c" gives a sane
diff, and the remaining stuff in builtin/revert.c feels pleasant.
Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>
Here's a patchlet for squashing into patch 2/2.
---
builtin/revert.c | 2 --
1 files changed, 0 insertions(+), 2 deletions(-)
diff --git a/builtin/revert.c b/builtin/revert.c
index 4116f2d3..e6840f23 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -30,8 +30,6 @@ static const char * const cherry_pick_usage[] = {
NULL
};
-#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
-
static const char *action_name(const struct replay_opts *opts)
{
return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
--
^ permalink raw reply related
* Re: Regulator updates for 3.3
From: Paul Gortmaker @ 2012-01-11 18:40 UTC (permalink / raw)
To: Linus Torvalds
Cc: Mark Brown, Liam Girdwood, linux-kernel, Junio C Hamano,
Git Mailing List
In-Reply-To: <CA+55aFxvQF=Bm4ae6euB_UO8otMCuN9Lv37Zn3TpE-L7JH3Kzw@mail.gmail.com>
[Re: Regulator updates for 3.3] On 10/01/2012 (Tue 14:54) Linus Torvalds wrote:
[...]
> So right now "git merge" (and "git pull") make it too easy to make
> those meaningless merge commits. If instead of seven pointless merges
It looks like the editor-by-default solution is a go, but there still
might be value in increasing the visibility of the pointless merges
via. the patch below.
Paul.
> you had (say) had two merges that had messages about *why* they
> weren't pointless, I'd be perfectly happy.
>
> Addid junio and git to the cc just to bring up this issue of bad UI
> once again. I realize it could break old scripts to start up an editor
> window, but still..
>
> Linus
>From 1a548fa97b78cebcded15d2b00ee3d826f731abd Mon Sep 17 00:00:00 2001
From: Paul Gortmaker <paul.gortmaker@windriver.com>
Date: Wed, 11 Jan 2012 10:33:45 -0500
Subject: [PATCH] merge: Make merge strategy message follow the diffstat
One of the common problems I've seen with people who are
somewhat new to git is that they don't realize that a pull
is a fetch+merge. They simply decide they want all the
latest stuff and issue a git pull without really thinking
if they are on a branch with local commits or on master,
where a fast forward can take place.
But the one line message that tells you whether you got a fast
forward or a real merge commit is usually pushed off the
screen by all the diffstat information. So these users won't
even know that their pull has created a merge, and chances
are they will never change their workflow.
By moving the message after the diffstat, there is a better
chance that people will be aware they've done a pointless
merge commit.
Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com>
diff --git a/builtin/merge.c b/builtin/merge.c
index 3a45172..9471588 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -370,12 +370,12 @@ static void finish(struct commit *head_commit,
{
struct strbuf reflog_message = STRBUF_INIT;
const unsigned char *head = head_commit->object.sha1;
+ int automsg = 0;
- if (!msg)
+ if (!msg) {
+ automsg = 1;
strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
- else {
- if (verbosity >= 0)
- printf("%s\n", msg);
+ } else {
strbuf_addf(&reflog_message, "%s: %s",
getenv("GIT_REFLOG_ACTION"), msg);
}
@@ -409,6 +409,9 @@ static void finish(struct commit *head_commit,
diff_flush(&opts);
}
+ if (!automsg && verbosity >= 0)
+ printf("%s\n", msg);
+
/* Run a post-merge hook */
run_hook(NULL, "post-merge", squash ? "1" : "0", NULL);
--
1.7.4.4
^ permalink raw reply related
* Re: [PATCH v3 06/10] clone: delay cloning until after remote HEAD checking
From: Junio C Hamano @ 2012-01-11 19:36 UTC (permalink / raw)
To: Nguyễn Thái Ngọc Duy; +Cc: git, Jeff King
In-Reply-To: <1326189427-20800-7-git-send-email-pclouds@gmail.com>
Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:
> This gives us an opportunity to abort the command during remote HEAD
> check without wasting much bandwidth.
>
> Cloning with remote-helper remains before the check because the remote
> helper updates mapped_refs, which is necessary for remote ref checks.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> I'm not familiar with remote-helper to see if there's any better way
> to do this..
> ...
> + refs = transport_get_remote_refs(transport);
> + mapped_refs = refs ? wanted_peer_refs(refs, refspec) : NULL;
> +
> + /*
> + * mapped_refs may be updated if transport-helper is used so
> + * we need fetch it early because remote_head code below
> + * relies on it.
> + *
> + * for normal clones, transport_get_remote_refs() should
> + * return reliable ref set, we can delay cloning until after
> + * remote HEAD check.
> + */
> + if (!is_local && remote->foreign_vcs && refs)
> + transport_fetch_refs(transport, mapped_refs);
> +
I like the goal of this change, but wouldn't remote->url indicate it is a
remote that needs a helper invocation by having double-colon in it,
without having an explicit value in foreign_vcs field?
Would it be cleaner if transport_get() learned to mark the transport as
needing special treatment (i.e. we won't know the final ref mapping until
we fetch the data from the other side) and check is made on that mark left
in the transport, instead of foreign_vcs in remote?
> diff --git a/transport.c b/transport.c
> index a99b7c9..aae9889 100644
> --- a/transport.c
> +++ b/transport.c
> @@ -895,8 +895,10 @@ struct transport *transport_get(struct remote *remote, const char *url)
>
> while (is_urlschemechar(p == url, *p))
> p++;
> - if (!prefixcmp(p, "::"))
> + if (!prefixcmp(p, "::")) {
> helper = xstrndup(url, p - url);
> + remote->foreign_vcs = helper;
> + }
> }
>
> if (helper) {
Ahhh, Ok. You are reusing the existing "foreign_vcs" field exactly for
that purpose. Earlier the field was strictly for configured .vcs value,
but now you use it also for the helper embedded within the URL, which
sounds like the right change to me.
> @@ -938,6 +940,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
> char *handler = xmalloc(len + 1);
> handler[len] = 0;
> strncpy(handler, url, len);
> + remote->foreign_vcs = helper;
> transport_helper_init(ret, handler);
> }
This I am not sure. What value does "helper" variable have at this point
in the flow? Wouldn't it be a NULL? Or did you mean "handler"?
^ permalink raw reply
* Re: [PATCH] archive: re-allow HEAD:Documentation on a remote invocation
From: Jeff King @ 2012-01-11 19:39 UTC (permalink / raw)
To: Carlos Martín Nieto; +Cc: git, Albert Astals Cid
In-Reply-To: <1326283958-30271-1-git-send-email-cmn@elego.de>
On Wed, Jan 11, 2012 at 01:12:38PM +0100, Carlos Martín Nieto wrote:
> The tightening done in (ee27ca4a: archive: don't let remote clients
> get unreachable commits, 2011-11-17) went too far and disallowed
> HEAD:Documentation as it would try to find "HEAD:Documentation" as a
> ref.
>
> Only DWIM the "HEAD" part to see if it exists as a ref. Once we're
> sure that we've been given a valid ref, we follow the normal code
> path. This still disallows attempts to access commits which are not
> branch tips.
I'd rather not do this kind of ad-hoc parsing of sha1s in the archive
code, and instead let the regular resolution process tell us more about
what it did, so we can make a policy decision at the upper level.
Patches to follow:
[1/2]: get_sha1_with_context: report features used in resolution
[2/2]: archive: loosen restrictions on remote object lookup
> AFAICT this should still be safe. Using HEAD^:Documentation or
> <sha1>:Documentation still complains that HEAD^ and <sha1> aren't
> refs.
My patches enable things like HEAD^, but disallow a raw sha1. The only
way to safely allow a raw sha1 is to check its connectivity from the
tips, which can be somewhat expensive (you have to traverse every tree
of every commit in the worst case).
-Peff
^ permalink raw reply
* [PATCH 1/2] get_sha1_with_context: report features used in resolution
From: Jeff King @ 2012-01-11 19:42 UTC (permalink / raw)
To: Carlos Martín Nieto; +Cc: git, Albert Astals Cid
In-Reply-To: <20120111193916.GA12333@sigill.intra.peff.net>
Most callers generally treat get_sha1 as a black box, giving
it a string from the user and expecting to get a sha1 in
return. The get_sha1_with_context function gives callers
more information about what happened while resolving the
object name so they can make better decisions about how to
use the result. We currently use this only to provide
information about the path entry used to find a blob.
We don't currently provide any information about the
resolution rules that were used to reach the final object.
Some callers may want these in order to enforce a policy
that a particular subset of the lookup rules are used (e.g.,
when serving remote requests).
This patch adds a set of bit-fields that document the use of
particular features during an object lookup.
Signed-off-by: Jeff King <peff@peff.net>
---
The diffstat looks a little scary, but it is mostly just the internal
get_sha1 functions learning to pass the object_context around.
cache.h | 7 +++++
sha1_name.c | 73 +++++++++++++++++++++++++++++++++++++---------------------
2 files changed, 53 insertions(+), 27 deletions(-)
diff --git a/cache.h b/cache.h
index 10afd71..ac25a37 100644
--- a/cache.h
+++ b/cache.h
@@ -809,6 +809,13 @@ struct object_context {
unsigned char tree[20];
char path[PATH_MAX];
unsigned mode;
+ unsigned used_ref:1,
+ used_reflog:1,
+ used_index:1,
+ used_nth_checkout:1,
+ used_describe_name:1,
+ used_oneline:1,
+ used_raw_hex:1;
};
extern int get_sha1(const char *str, unsigned char *sha1);
diff --git a/sha1_name.c b/sha1_name.c
index 03ffc2c..ad7c52a 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -7,7 +7,8 @@
#include "refs.h"
#include "remote.h"
-static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
+static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *,
+ struct object_context *);
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
{
@@ -158,7 +159,7 @@ static int find_unique_short_object(int len, char *canonical,
}
static int get_short_sha1(const char *name, int len, unsigned char *sha1,
- int quietly)
+ int quietly, struct object_context *oc)
{
int i, status;
char canonical[40];
@@ -190,6 +191,8 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1,
status = find_unique_short_object(i, canonical, res, sha1);
if (!quietly && (status == SHORT_NAME_AMBIGUOUS))
return error("short SHA1 %.*s is ambiguous.", len, canonical);
+ if (!status)
+ oc->used_raw_hex = 1;
return status;
}
@@ -204,7 +207,8 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len)
return hex;
while (len < 40) {
unsigned char sha1_ret[20];
- status = get_short_sha1(hex, len, sha1_ret, 1);
+ struct object_context unused;
+ status = get_short_sha1(hex, len, sha1_ret, 1, &unused);
if (exists
? !status
: status == SHORT_NAME_NOT_FOUND) {
@@ -255,17 +259,21 @@ static inline int upstream_mark(const char *string, int len)
return 0;
}
-static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+static int get_sha1_1(const char *name, int len, unsigned char *sha1,
+ struct object_context *oc);
-static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
+static int get_sha1_basic(const char *str, int len, unsigned char *sha1,
+ struct object_context *oc)
{
static const char *warn_msg = "refname '%.*s' is ambiguous.";
char *real_ref = NULL;
int refs_found = 0;
int at, reflog_len;
- if (len == 40 && !get_sha1_hex(str, sha1))
+ if (len == 40 && !get_sha1_hex(str, sha1)) {
+ oc->used_raw_hex = 1;
return 0;
+ }
/* basic@{time or number or -number} format to query ref-log */
reflog_len = at = 0;
@@ -292,7 +300,8 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
ret = interpret_branch_name(str+at, &buf);
if (ret > 0) {
/* substitute this branch name and restart */
- return get_sha1_1(buf.buf, buf.len, sha1);
+ oc->used_nth_checkout = 1;
+ return get_sha1_1(buf.buf, buf.len, sha1, oc);
} else if (ret == 0) {
return -1;
}
@@ -305,6 +314,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
if (!refs_found)
return -1;
+ oc->used_ref = 1;
if (warn_ambiguous_refs && refs_found > 1)
warning(warn_msg, len, str);
@@ -352,6 +362,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
len, str, co_cnt);
}
}
+ oc->used_reflog = 1;
}
free(real_ref);
@@ -359,10 +370,11 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
}
static int get_parent(const char *name, int len,
- unsigned char *result, int idx)
+ unsigned char *result, int idx,
+ struct object_context *oc)
{
unsigned char sha1[20];
- int ret = get_sha1_1(name, len, sha1);
+ int ret = get_sha1_1(name, len, sha1, oc);
struct commit *commit;
struct commit_list *p;
@@ -389,13 +401,14 @@ static int get_parent(const char *name, int len,
}
static int get_nth_ancestor(const char *name, int len,
- unsigned char *result, int generation)
+ unsigned char *result, int generation,
+ struct object_context *oc)
{
unsigned char sha1[20];
struct commit *commit;
int ret;
- ret = get_sha1_1(name, len, sha1);
+ ret = get_sha1_1(name, len, sha1, oc);
if (ret)
return ret;
commit = lookup_commit_reference(sha1);
@@ -436,7 +449,8 @@ struct object *peel_to_type(const char *name, int namelen,
}
}
-static int peel_onion(const char *name, int len, unsigned char *sha1)
+static int peel_onion(const char *name, int len, unsigned char *sha1,
+ struct object_context *oc)
{
unsigned char outer[20];
const char *sp;
@@ -476,7 +490,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
else
return -1;
- if (get_sha1_1(name, sp - name - 2, outer))
+ if (get_sha1_1(name, sp - name - 2, outer, oc))
return -1;
o = parse_object(outer);
@@ -515,14 +529,15 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
prefix = xstrndup(sp + 1, name + len - 1 - (sp + 1));
commit_list_insert((struct commit *)o, &list);
- ret = get_sha1_oneline(prefix, sha1, list);
+ ret = get_sha1_oneline(prefix, sha1, list, oc);
free(prefix);
return ret;
}
return 0;
}
-static int get_describe_name(const char *name, int len, unsigned char *sha1)
+static int get_describe_name(const char *name, int len, unsigned char *sha1,
+ struct object_context *oc)
{
const char *cp;
@@ -535,14 +550,15 @@ static int get_describe_name(const char *name, int len, unsigned char *sha1)
if (ch == 'g' && cp[-1] == '-') {
cp++;
len -= cp - name;
- return get_short_sha1(cp, len, sha1, 1);
+ return get_short_sha1(cp, len, sha1, 1, oc);
}
}
}
return -1;
}
-static int get_sha1_1(const char *name, int len, unsigned char *sha1)
+static int get_sha1_1(const char *name, int len, unsigned char *sha1,
+ struct object_context *oc)
{
int ret, has_suffix;
const char *cp;
@@ -569,25 +585,25 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
if (!num && len1 == len - 1)
num = 1;
if (has_suffix == '^')
- return get_parent(name, len1, sha1, num);
+ return get_parent(name, len1, sha1, num, oc);
/* else if (has_suffix == '~') -- goes without saying */
- return get_nth_ancestor(name, len1, sha1, num);
+ return get_nth_ancestor(name, len1, sha1, num, oc);
}
- ret = peel_onion(name, len, sha1);
+ ret = peel_onion(name, len, sha1, oc);
if (!ret)
return 0;
- ret = get_sha1_basic(name, len, sha1);
+ ret = get_sha1_basic(name, len, sha1, oc);
if (!ret)
return 0;
/* It could be describe output that is "SOMETHING-gXXXX" */
- ret = get_describe_name(name, len, sha1);
+ ret = get_describe_name(name, len, sha1, oc);
if (!ret)
return 0;
- return get_short_sha1(name, len, sha1, 0);
+ return get_short_sha1(name, len, sha1, 0, oc);
}
/*
@@ -619,7 +635,8 @@ static int handle_one_ref(const char *path,
}
static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
- struct commit_list *list)
+ struct commit_list *list,
+ struct object_context *oc)
{
struct commit_list *backup = NULL, *l;
int found = 0;
@@ -672,6 +689,7 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
for (l = backup; l; l = l->next)
clear_commit_marks(l->item, ONELINE_SEEN);
free_commit_list(backup);
+ oc->used_oneline = found;
return found ? 0 : -1;
}
@@ -1029,7 +1047,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
memset(oc, 0, sizeof(*oc));
oc->mode = S_IFINVALID;
- ret = get_sha1_1(name, namelen, sha1);
+ ret = get_sha1_1(name, namelen, sha1, oc);
if (!ret)
return ret;
/* sha1:path --> object name of path in ent sha1
@@ -1046,7 +1064,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
if (!only_to_die && namelen > 2 && name[1] == '/') {
struct commit_list *list = NULL;
for_each_ref(handle_one_ref, &list);
- return get_sha1_oneline(name + 2, sha1, list);
+ return get_sha1_oneline(name + 2, sha1, list, oc);
}
if (namelen < 3 ||
name[2] != ':' ||
@@ -1081,6 +1099,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
if (ce_stage(ce) == stage) {
hashcpy(sha1, ce->sha1);
oc->mode = ce->ce_mode;
+ oc->used_index = 1;
free(new_path);
return 0;
}
@@ -1107,7 +1126,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
strncpy(object_name, name, cp-name);
object_name[cp-name] = '\0';
}
- if (!get_sha1_1(name, cp-name, tree_sha1)) {
+ if (!get_sha1_1(name, cp-name, tree_sha1, oc)) {
const char *filename = cp+1;
char *new_filename = NULL;
--
1.7.9.rc0.33.gd3c17
^ permalink raw reply related
* [PATCH 2/2] archive: loosen restrictions on remote object lookup
From: Jeff King @ 2012-01-11 19:42 UTC (permalink / raw)
To: Carlos Martín Nieto; +Cc: git, Albert Astals Cid
In-Reply-To: <20120111193916.GA12333@sigill.intra.peff.net>
Initially, "git upload-archive" would feed the tree
specification from the remote side directly into get_sha1,
giving the remote user the full power of the object name
resolver. This was convenient, but it also meant that remote
users could fetch disconnected trees by their sha1s, which
violates the long-standing behavior of upload-pack not to
make such objects available.
Later, commit ee27ca4 tightened this to use dwim_ref instead
of get_sha1 for the remote case, allowing only the use of
actual refs. Unfortunately, this broke some existing use
cases, like fetching sub-trees with "$ref:subdir".
This patch loosens the restrictions to re-enable those use
cases. It does this by using get_sha1_with_context for the
object lookup, and checking that only allowable features
were used.
Signed-off-by: Jeff King <peff@peff.net>
---
archive.c | 34 ++++++++++++++-------
t/t5002-archive-resolution.sh | 66 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 89 insertions(+), 11 deletions(-)
create mode 100755 t/t5002-archive-resolution.sh
diff --git a/archive.c b/archive.c
index 164bbd0..a031bde 100644
--- a/archive.c
+++ b/archive.c
@@ -246,6 +246,25 @@ static void parse_pathspec_arg(const char **pathspec,
}
}
+static int check_object_context(int remote, const struct object_context *oc)
+{
+ /* For local requests, allow anything */
+ if (!remote)
+ return 1;
+ /*
+ * Otherwise, require that we accessed the object through a ref,
+ * but not have used any of the advanced features like looking in
+ * the reflog.
+ */
+ return oc->used_ref &&
+ !oc->used_reflog &&
+ !oc->used_index &&
+ !oc->used_nth_checkout &&
+ !oc->used_describe_name &&
+ !oc->used_oneline &&
+ !oc->used_raw_hex;
+}
+
static void parse_treeish_arg(const char **argv,
struct archiver_args *ar_args, const char *prefix,
int remote)
@@ -256,18 +275,11 @@ static void parse_treeish_arg(const char **argv,
struct tree *tree;
const struct commit *commit;
unsigned char sha1[20];
+ struct object_context oc;
- /* Remotes are only allowed to fetch actual refs */
- if (remote) {
- char *ref = NULL;
- if (!dwim_ref(name, strlen(name), sha1, &ref))
- die("no such ref: %s", name);
- free(ref);
- }
- else {
- if (get_sha1(name, sha1))
- die("Not a valid object name");
- }
+ if (get_sha1_with_context(name, sha1, &oc) ||
+ !check_object_context(remote, &oc))
+ die("Not a valid object name");
commit = lookup_commit_reference_gently(sha1, 1);
if (commit) {
diff --git a/t/t5002-archive-resolution.sh b/t/t5002-archive-resolution.sh
new file mode 100755
index 0000000..bf2b55c
--- /dev/null
+++ b/t/t5002-archive-resolution.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+test_description='test object resolution methods for local and remote archive'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo a >a &&
+ git add . &&
+ git commit -m one &&
+ sha1_one=`git rev-parse HEAD` &&
+ mkdir subdir &&
+ echo b >subdir/b &&
+ git add . &&
+ git commit -m two &&
+ git checkout -b other &&
+ git checkout master
+'
+
+while read desc where what expect; do
+ cmd="git archive --format=tar -o result.tar"
+ test "$where" = "remote" && cmd="$cmd --remote=."
+ cmd="$cmd $what"
+
+ if test "$expect" = "deny"; then
+ test_expect_success "archive $desc ($where, should deny)" "
+ test_must_fail $cmd
+ "
+ else
+ test_expect_success "archive $desc ($where, should work)" '
+ '"$cmd"' &&
+ for i in '"$expect"'; do
+ echo "$i:`basename $i`"
+ done >expect &&
+ rm -rf result &&
+ mkdir result &&
+ (cd result &&
+ tar xf ../result.tar &&
+ for i in `find * -type f`; do
+ echo "$i:`cat $i`"
+ done >../actual
+ ) &&
+ test_cmp expect actual
+ '
+ fi
+done <<EOF
+ref local master a subdir/b
+ref remote master a subdir/b
+parent local master^ a
+parent remote master^ a
+tree local master^{tree} a subdir/b
+tree remote master^{tree} a subdir/b
+subtree local master:subdir b
+subtree remote master:subdir b
+sha1 local $sha1_one a
+sha1 remote $sha1_one deny
+reflog local master@{1} a
+reflog remote master@{1} deny
+oneline local :/one a
+oneline remote :/one deny
+oneline-ref local master^{/one} a
+oneline-ref remote master^{/one} deny
+nth-checkout local @{-1} a subdir/b
+nth-checkout remote @{-1} deny
+EOF
+
+test_done
--
1.7.9.rc0.33.gd3c17
^ permalink raw reply related
* Re: leaky cherry-pick
From: Jeff King @ 2012-01-11 19:56 UTC (permalink / raw)
To: Ramkumar Ramachandra
Cc: Junio C Hamano, Pete Wyckoff, git,
Nguyễn Thái Ngọc
In-Reply-To: <CALkWK0m+okqJk05BMQAEMww6FNLxaLVhAM92WmUDeA_J-drOdg@mail.gmail.com>
On Wed, Jan 11, 2012 at 02:30:16PM +0530, Ramkumar Ramachandra wrote:
> > I somehow have a feeling that you did not read the conclusion in Peff's
> > message correctly. The code only keeps data from one active path of
> > per-directory .gitattributes files to the leaf of a working tree and
> > releases unneeded data (IOW, it "pops" the attr_stack elements) when it
> > goes on to look at the next path, so my understanding is that there is
> > nothing to "try implementing" here.
>
> My bad- I thought the current implementation doesn't release the
> unneeded data. So, does the entire 7 KB of leaked data come from one
> active path?
Hmm. Actually, I think there is a leak. Because the strategy I described
leaves the memory reachable from the global attr_stack variable, which
means valgrind will only show it if "--show-reachable" is turned on.
Maybe this?
diff --git a/attr.c b/attr.c
index 76b079f..1656db4 100644
--- a/attr.c
+++ b/attr.c
@@ -301,6 +301,7 @@ static void free_attr_elem(struct attr_stack *e)
}
free(a);
}
+ free(e->attrs);
free(e);
}
-Peff
^ permalink raw reply related
* Re: [PATCH v3 07/10] clone: --branch=<branch> always means refs/heads/<branch>
From: Junio C Hamano @ 2012-01-11 20:01 UTC (permalink / raw)
To: Nguyễn Thái Ngọc Duy; +Cc: git, Jeff King
In-Reply-To: <1326189427-20800-8-git-send-email-pclouds@gmail.com>
Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:
> It does not make sense to look outside refs/heads for HEAD's target
> (src_ref_prefix can be set to "refs/" if --mirror is used) because ref
> code only allows symref in form refs/heads/...
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> builtin/clone.c | 4 ++--
> 1 files changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 05224d7..960242f 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -463,7 +463,7 @@ static void update_remote_refs(const struct ref *refs,
> static void update_head(const struct ref *our, const struct ref *remote,
> const char *msg)
> {
> - if (our) {
> + if (our && !prefixcmp(our->name, "refs/heads/")) {
> /* Local default branch link */
> create_symref("HEAD", our->name, NULL);
> if (!option_bare) {
I think this makes sense. In the following three casees:
- When cloning without --branch, if we could not find a branch that match
HEAD at the remote;
- When cloning with --branch, if we did not see such a branch at the
remote; or
- When cloning from an empty repository
we come here with "our" set to NULL. Additionally, if the remote HEAD
points outside refs/heads/ and the transport could detect that case
(e.g. a helper that reads from ls-remote output), we can see our->name
outside refs/heads/. Is there any other case where our is not NULL and
our->name does not start with refs/heads/?
The "else if (remote)" clause (not shown, outside the context) that
follows still has comments that says it is a case for "Source had detached
HEAD pointing somewhere", and needs to be adjusted for this change, no? It
is "(1) we know the HEAD points at a non-branch, (2) HEAD may point at a
branch but we do not know which one, or (3) we asked for a specific branch
but it did not exist there" (cloning from an empty will have NULL in
remote and the comment would not apply to that case).
> @@ -760,7 +760,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
>
> if (option_branch) {
> struct strbuf head = STRBUF_INIT;
> - strbuf_addstr(&head, src_ref_prefix);
> + strbuf_addstr(&head, "refs/heads/");
> strbuf_addstr(&head, option_branch);
> our_head_points_at =
> find_ref_by_name(mapped_refs, head.buf);
^ permalink raw reply
* Re: [PATCH 2/2] diff --word-diff: use non-whitespace regex by default
From: Thomas Rast @ 2012-01-11 20:05 UTC (permalink / raw)
To: Tay Ray Chuan; +Cc: Git Mailing List, Junio C Hamano
In-Reply-To: <1326302702-4536-2-git-send-email-rctay89@gmail.com>
Tay Ray Chuan <rctay89@gmail.com> writes:
> Factor out the comprehensive non-whitespace regex in use by PATTERNS and
> IPATTERN and use it as the word-diff regex for the default diff driver.
Why?
I seem to recall that the motivation for keeping the original code as-is
instead of just emulating its behavior with a default regex was that it
is faster. So disabling the default mode should at least have an
advantage?
</devils-advocate>
--
Thomas Rast
trast@{inf,student}.ethz.ch
^ permalink raw reply
* [PATCH 1/3] mailinfo documentation: accurately describe non -k case
From: Thomas Rast @ 2012-01-11 20:13 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano
Since its very first description of -k, the documentation for
git-mailinfo claimed that (in the case without -k) after cleaning up
bracketed strings [blah], it would insert [PATCH].
It doesn't; on the contrary, one of the important jobs of mailinfo is
to remove those strings.
Since we're already there, rewrite the paragraph to give a complete
enumeration of all the transformations. Specifically, it was missing
the whitespace normalization (run of isspace(c) -> ' ') and the
removal of leading ':'.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
Documentation/git-mailinfo.txt | 25 ++++++++++++++++++-------
1 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt
index 51dc325..97e7a8e 100644
--- a/Documentation/git-mailinfo.txt
+++ b/Documentation/git-mailinfo.txt
@@ -25,13 +25,24 @@ command directly. See linkgit:git-am[1] instead.
OPTIONS
-------
-k::
- Usually the program 'cleans up' the Subject: header line
- to extract the title line for the commit log message,
- among which (1) remove 'Re:' or 're:', (2) leading
- whitespaces, (3) '[' up to ']', typically '[PATCH]', and
- then prepends "[PATCH] ". This flag forbids this
- munging, and is most useful when used to read back
- 'git format-patch -k' output.
+ Usually the program removes email cruft from the Subject:
+ header line to extract the title line for the commit log
+ message. This option prevents this munging, and is most
+ useful when used to read back 'git format-patch -k' output.
++
+Specifically, the following are removed until none of them remain:
++
+--
+* Leading and trailing whitespace.
+
+* Leading `Re:`, `re:`, and `:`.
+
+* Leading bracketed strings (between `[` and `]`, usually
+ `[PATCH]`).
+--
++
+Finally, runs of whitespace are normalized to a single ASCII space
+character.
-b::
When -k is not in effect, all leading strings bracketed with '['
--
1.7.9.rc0.168.g3847c
^ permalink raw reply related
* [PATCH 3/3] mailinfo: with -b, keep space after [foo]
From: Thomas Rast @ 2012-01-11 20:13 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano
In-Reply-To: <e915a551c9bbf12f4d8fd929e9ed24f3223790ee.1326312730.git.trast@student.ethz.ch>
The logic for the -b mode, where [PATCH] is dropped but [foo] is not,
silently ate all spaces after the ].
Fix this by keeping the next isspace() character, if there is any.
Being more thorough is pointless, as the later cleanup_space() call
will normalize any sequence of whitespace to a single ' '.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
builtin/mailinfo.c | 11 ++++++++++-
t/t4150-am.sh | 2 +-
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c
index bfb32b7..eaf9e15 100644
--- a/builtin/mailinfo.c
+++ b/builtin/mailinfo.c
@@ -250,8 +250,17 @@ static void cleanup_subject(struct strbuf *subject)
(7 <= remove &&
memmem(subject->buf + at, remove, "PATCH", 5)))
strbuf_remove(subject, at, remove);
- else
+ else {
at += remove;
+ /*
+ * If the input had a space after the ], keep
+ * it. We don't bother with finding the end of
+ * the space, since we later normalize it
+ * anyway.
+ */
+ if (isspace(subject->buf[at]))
+ at += 1;
+ }
continue;
}
break;
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 7e7c83c..8807b60 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -262,7 +262,7 @@ test_expect_success 'am --keep really keeps the subject' '
grep "Re: Re: Re: \[PATCH 1/5 v2\] \[foo\] third" actual
'
-test_expect_failure 'am --keep-non-patch really keeps the non-patch part' '
+test_expect_success 'am --keep-non-patch really keeps the non-patch part' '
rm -fr .git/rebase-apply &&
git reset --hard &&
git checkout HEAD^ &&
--
1.7.9.rc0.168.g3847c
^ permalink raw reply related
* [PATCH 2/3] am: learn passing -b to mailinfo
From: Thomas Rast @ 2012-01-11 20:13 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano
In-Reply-To: <e915a551c9bbf12f4d8fd929e9ed24f3223790ee.1326312730.git.trast@student.ethz.ch>
git-am could pass -k to mailinfo, but not -b. Introduce an option
that does so. We change the meaning of the 'keep' state file, but are
careful not to cause a problem unless you downgrade in the middle of
an 'am' run.
This uncovers a bug in mailinfo -b, hence the failing test.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
Someone on IRC asked about git-am support for passing through the -b
flag to mailinfo. And so it began...
Documentation/git-am.txt | 3 +++
git-am.sh | 11 +++++++----
t/t4150-am.sh | 14 ++++++++++++--
3 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 887466d..ee6cca2 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -40,6 +40,9 @@ OPTIONS
--keep::
Pass `-k` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+--keep-non-patch::
+ Pass `-b` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+
--keep-cr::
--no-keep-cr::
With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1])
diff --git a/git-am.sh b/git-am.sh
index 1c13b13..8dfad7a 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -15,6 +15,7 @@ q,quiet be quiet
s,signoff add a Signed-off-by line to the commit message
u,utf8 recode into utf8 (default)
k,keep pass -k flag to git-mailinfo
+keep-non-patch pass -b flag to git-mailinfo
keep-cr pass --keep-cr flag to git-mailsplit for mbox format
no-keep-cr do not pass --keep-cr flag to git-mailsplit independent of am.keepcr
c,scissors strip everything before a scissors line
@@ -386,7 +387,9 @@ do
--no-utf8)
utf8= ;;
-k|--keep)
- keep=t ;;
+ keep=-k ;;
+ --keep-non-patch)
+ keep=-b ;;
-c|--scissors)
scissors=t ;;
--no-scissors)
@@ -398,7 +401,7 @@ do
--abort)
abort=t ;;
--rebasing)
- rebasing=t threeway=t keep=t scissors=f no_inbody_headers=t ;;
+ rebasing=t threeway=t keep=-k scissors=f no_inbody_headers=t ;;
-d|--dotest)
die "$(gettext "-d option is no longer supported. Do not use.")"
;;
@@ -571,8 +574,8 @@ then
else
utf8=-n
fi
-if test "$(cat "$dotest/keep")" = t
-then
+keep=$(cat "$dotest/keep")
+if test "$keep" = t
keep=-k
fi
case "$(cat "$dotest/scissors")" in
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index d7d9ccc..7e7c83c 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -237,7 +237,7 @@ test_expect_success 'am stays in branch' '
test_expect_success 'am --signoff does not add Signed-off-by: line if already there' '
git format-patch --stdout HEAD^ >patch3 &&
- sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2," patch3 >patch4 &&
+ sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," patch3 >patch4 &&
rm -fr .git/rebase-apply &&
git reset --hard &&
git checkout HEAD^ &&
@@ -259,7 +259,17 @@ test_expect_success 'am --keep really keeps the subject' '
git am --keep patch4 &&
! test -d .git/rebase-apply &&
git cat-file commit HEAD >actual &&
- grep "Re: Re: Re: \[PATCH 1/5 v2\] third" actual
+ grep "Re: Re: Re: \[PATCH 1/5 v2\] \[foo\] third" actual
+'
+
+test_expect_failure 'am --keep-non-patch really keeps the non-patch part' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
+ git checkout HEAD^ &&
+ git am --keep-non-patch patch4 &&
+ ! test -d .git/rebase-apply &&
+ git cat-file commit HEAD >actual &&
+ grep "^\[foo\] third" actual
'
test_expect_success 'am -3 falls back to 3-way merge' '
--
1.7.9.rc0.168.g3847c
^ permalink raw reply related
* Re: [PATCH 3/3] diff-index: pass pathspec down to unpack-trees machinery
From: Junio C Hamano @ 2012-01-11 20:40 UTC (permalink / raw)
To: Nguyen Thai Ngoc Duy; +Cc: Jonathan Nieder, git, Linus Torvalds
In-Reply-To: <CACsJy8D7EnOebAxBYF8ua7htu-81nKY=ghUMgg=JOe4Fc1uigQ@mail.gmail.com>
Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
> This seems to fix this.
>
> diff --git a/unpack-trees.c b/unpack-trees.c
> index 7c9ecf6..5cf58b6 100644
> --- a/unpack-trees.c
> +++ b/unpack-trees.c
> @@ -1042,6 +1042,7 @@ int unpack_trees(unsigned len, struct tree_desc
> *t, struct unpack_trees_options
> info.data = o;
> info.show_all_errors = o->show_all_errors;
> info.pathspec = o->pathspec;
> + info.pathspec->recursive = 1;
>
> if (o->prefix) {
> /*
>
> Still scratching my head why this flag is zero by default, which would
> affect all other places.
Ahh, thanks for diagnosing.
> ... Or perhaps the right fix would be this
> instead
>
> diff --git a/tree-walk.c b/tree-walk.c
> index f82dba6..0345938 100644
> --- a/tree-walk.c
> +++ b/tree-walk.c
> @@ -634,7 +634,7 @@ enum interesting tree_entry_interesting(const
> struct name_entry *entry,
> * Match all directories. We'll try to
> * match files later on.
> */
> - if (ps->recursive && S_ISDIR(entry->mode))
> + if (S_ISDIR(entry->mode))
> return entry_interesting;
> }
>
> @@ -662,7 +662,7 @@ match_wildcards:
> * Match all directories. We'll try to match files
> * later on.
> */
> - if (ps->recursive && S_ISDIR(entry->mode))
> + if (S_ISDIR(entry->mode))
> return entry_interesting;
> }
> return never_interesting; /* No matches */
Doesn't that break "git diff-tree A B" without the "-r" option?
^ permalink raw reply
* Re: [PATCH RFC] commit: allow to commit even if there are intent-to-add entries
From: Junio C Hamano @ 2012-01-11 21:08 UTC (permalink / raw)
To: Jonathan Nieder
Cc: Nguyễn Thái Ngọc Duy, git, Jeff King,
Will Palmer
In-Reply-To: <20120111110222.GA32173@burratino>
Jonathan Nieder <jrnieder@gmail.com> writes:
>> When running "commit" and "status" with files marked with "intent to add",
>> I think there are three possible interpretations of what the user
>> wants to do.
> [ (1) thanks for stopping me, I had forgotten about that file;
> (2) I changed my mind, please leave that file out; or (3) please
> dwim and add the file ]
>
> I think (3) was a trick --- no one that does not use the "-a" option
> would want that. :)
I really wish it were the case, but I doubt it.
People from other VCS background seem to still think that "commit" without
paths should commit everything; after getting told that "what you add to
the index is what you will commit", I can easily see this commint: but but
but I told Git that I _want_ to add with -N! Why aren't you committing it?
> At the time, I did not understand what (2) meant. Now I see why ---
> in interpretation (2), the user did not change her mind at all.
You are correct. "I still cannot make up my mind" is what is happening in
that situation.
The user explicitly said "I cannot decide about this path right now" when
she said "add -N". And we haven't heard from the user what should happen
to the path. Now we have to make a commit so somebody needs to decide.
> She
> said "I will be adding this file at some point, so please keep track
> of it along with the others for the sake of commands like 'git diff'
> and 'git add -u', but that does not mean "I will be adding this file
> at some point _before the next commit_".
Correct. She only said "I cannot decide right now" when she said "add -N"
and hasn't gave us any more hint as to what should happen now we have to
make a commit.
It is _wrong_ for us to unilaterally decide for the user that she does not
want the path in the commit. The last we heard from her was that she does
not know what should happen to the path.
> (2) makes intent-to-add entries just like any other tracked index
> entry with some un-added content.
You are comparing files edited in the working tree without the user
telling anything about them to Git (both tracked and untracked) and files
the user explicitly told Git that the user hasn't made up her mind
about. Why is it a good thing to make the latter behave "just like any
other"?
^ permalink raw reply
* Re: git svn clone terminating prematurely (I think)
From: Steven Line @ 2012-01-11 21:52 UTC (permalink / raw)
To: Ramkumar Ramachandra; +Cc: git
In-Reply-To: <CALkWK0nyc6NVE7Qpvbc0dXb1UHGM_=uYbCS+a53HZxiBRG9HvQ@mail.gmail.com>
Hi Ram, thank you for the reply. Se below . . .
>
>> I need some help getting my subversion repository cloned over to git.
>> Our svn repository has about 12,000 commits, when I run
>> git svn clone -s -A authors.txt
>> svn+ssh://csvn <at> source.res.ourdomain.com/home/svn/sem sem
>> It runs for about 2h 15m then completes with no error messages. I have
>> also cloned starting at revision 6300, about the middle of the svn
>> repository, and I get the same results as below.
>
>> $ git branch -a # shows only about half the branches that should have
>> been cloned
>
> Interesting. From the git-svn-id of the most recent commit, can you
> tell if there's anything especially fishy about the revision where
> git-svn stops? Your Subversion repository is probably broken in some
> way, but git-svn should not use that as an excuse for appearing to
> finish successfully while failing in reality.
Well by your question it seems you expect this clone to fail at the
same svn revision number
each time. I spent all day today trying to figure out what that
revision was. However it doesn't
seem to be failing at the same svn revision each time.
There are about 12813 revisions in our svn repository so I started
attempting clones from successively later
and later revisions both to figure which revision they repeatedly
failed on (it was never the same one) and to see
if I could coax a successful clone to occur. Originally I started at
revision 1, then incremented to 6300, then 8000, then 10000, then
12,500, then finally 12,700. Finally the attempt starting at svn
revision 12,700 succeeded and the resulting git repository
seems to work. All the attempts prior to 12,700 (starting at 1, 6300,
8000, 10000, and 12,500) failed while
importing different revisions, none of them showed error messages.
One thing that I am suspect of is that I'm not able to log directly
into the machine running git, it's on a remote server in
Atlanta while I'm in Colorado. I'm running the 'git svn clone' using
nohup, but in every case of a corrupt git repository
the connection between me and the server dies before the git completes
(the connection dies, but I log in again and run
ps -ef and I see the multiple git processes still running. They run
for up to several hours longer, then terminate). I should be
ok since I'm using nohup, but coincidentally the only clone that
succeeded was the one where the network connection
never disconnected during the clone
Not sure if this helps but here are some numbers:
Both the svn repository and git machine are Solaris 10
Svn revision 1.6.12 CollabNet
Git version 1.7.6.1
Thank you.
--
Steven Line
303-910-1212
sline00@gmail.com
^ permalink raw reply
* Re: git svn clone terminating prematurely (I think)
From: Jonathan Nieder @ 2012-01-11 22:48 UTC (permalink / raw)
To: Ramkumar Ramachandra; +Cc: Steven Line, git
In-Reply-To: <CALkWK0nyc6NVE7Qpvbc0dXb1UHGM_=uYbCS+a53HZxiBRG9HvQ@mail.gmail.com>
Ramkumar Ramachandra wrote:
> Interesting. From the git-svn-id of the most recent commit, can you
> tell if there's anything especially fishy about the revision where
> git-svn stops? Your Subversion repository is probably broken in some
> way,
I wouldn't necessarily assume that. My first hunch would have been
that this is some variation on the SIGPIPE bug[1]. The usual
workaround for that[2] is to get git-svn to continue by running "git
svn fetch" again. Nobody's fixed it because nobody's stared at the
SVN perl bindings for long enough to find the cause.
Modern git versions produce a message in that case, though:
error: git-svn died of signal 13
So you are probably running into something else. I only mention this
for context.
Does svnsync work for making a local copy of whatever subset of the
repository is relevant? Alternatively (this is basically the same),
is the network connection stable enough for e.g. svnrdump[3] to get a
dump?
Sorry for the trouble, and hope that helps,
Jonathan
[1] http://thread.gmane.org/gmane.comp.version-control.git/134936/focus=134940
[2] Maybe it deserves a note in the manpage.
[3] http://repo.or.cz/w/svnrdump.git
^ permalink raw reply
* [PATCH 0/5] git-p4: view spec review fixes
From: Pete Wyckoff @ 2012-01-11 23:31 UTC (permalink / raw)
To: git; +Cc: Gary Gibbons
I received some helpful review comments offline regarding the
series recently merged into master (8cbfc11
(Merge branch 'pw/p4-view-updates', 2012-01-06)).
It would be nice to see these go into 1.7.9 as well.
Even though they belong there logically, they are
minor fixes, and not critical.
I added a bunch of new tests documenting further problems,
but will not fix those here as the changes will be more
invasive.
Thanks,
Pete Wyckoff (5):
git-p4: only a single ... wildcard is supported
git-p4: fix verbose comment typo
git-p4: clarify comment
git-p4: adjust test to adhere to stricter useClientSpec
git-p4: add tests demonstrating spec overlay ambiguities
Documentation/git-p4.txt | 5 +
contrib/fast-import/git-p4 | 9 +-
t/t9806-git-p4-options.sh | 4 +-
t/t9809-git-p4-client-view.sh | 395 ++++++++++++++++++++++++++++++++++++++++-
4 files changed, 406 insertions(+), 7 deletions(-)
--
1.7.8.1.409.g3e338.dirty
^ permalink raw reply
* [PATCH 1/5] git-p4: only a single ... wildcard is supported
From: Pete Wyckoff @ 2012-01-11 23:31 UTC (permalink / raw)
To: git; +Cc: Gary Gibbons
In-Reply-To: <1326324670-15967-1-git-send-email-pw@padd.com>
Catch the case where a ... exists at the end, and also elsehwere.
Reported-by: Gary Gibbons <ggibbons@perforce.com>
Signed-off-by: Pete Wyckoff <pw@padd.com>
---
contrib/fast-import/git-p4 | 4 ++--
t/t9809-git-p4-client-view.sh | 8 +++++++-
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
index 3e1aa27..20208bf 100755
--- a/contrib/fast-import/git-p4
+++ b/contrib/fast-import/git-p4
@@ -1207,8 +1207,8 @@ class View(object):
die("Can't handle * wildcards in view: %s" % self.path)
triple_dot_index = self.path.find("...")
if triple_dot_index >= 0:
- if not self.path.endswith("..."):
- die("Can handle ... wildcard only at end of path: %s" %
+ if triple_dot_index != len(self.path) - 3:
+ die("Can handle only single ... wildcard, at end: %s" %
self.path)
self.ends_triple_dot = True
diff --git a/t/t9809-git-p4-client-view.sh b/t/t9809-git-p4-client-view.sh
index c9471d5..54204af 100755
--- a/t/t9809-git-p4-client-view.sh
+++ b/t/t9809-git-p4-client-view.sh
@@ -101,12 +101,18 @@ test_expect_success 'unsupported view wildcard *' '
test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
'
-test_expect_success 'wildcard ... only supported at end of spec' '
+test_expect_success 'wildcard ... only supported at end of spec 1' '
client_view "//depot/.../file11 //client/.../file11" &&
test_when_finished cleanup_git &&
test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
'
+test_expect_success 'wildcard ... only supported at end of spec 2' '
+ client_view "//depot/.../a/... //client/.../a/..." &&
+ test_when_finished cleanup_git &&
+ test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+'
+
test_expect_success 'basic map' '
client_view "//depot/dir1/... //client/cli1/..." &&
files="cli1/file11 cli1/file12" &&
--
1.7.8.1.409.g3e338.dirty
^ 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