merge: handle FETCH_HEAD internally

The collect_parents() function now is responsible for

 1. parsing the commits given on the command line into a list of
    commits to be merged;

 2. filtering these parents into independent ones; and

 3. optionally calling fmt_merge_msg() via prepare_merge_message()
    to prepare an auto-generated merge log message, using fake
    contents that FETCH_HEAD would have had if these commits were
    fetched from the current repository with "git pull . $args..."

Make "git merge FETCH_HEAD" to be the same as the traditional

    git merge "$(git fmt-merge-msg <.git/FETCH_HEAD)" $commits

invocation of the command in "git pull", where $commits are the ones
that appear in FETCH_HEAD that are not marked as not-for-merge, by
making it do a bit more, specifically:

 - noticing "FETCH_HEAD" is the only "commit" on the command line
   and picking the commits that are not marked as not-for-merge as
   the list of commits to be merged (substitute for step #1 above);

 - letting the resulting list fed to step #2 above;

 - doing the step #3 above, using the contents of the FETCH_HEAD
   instead of fake contents crafted from the list of commits parsed
   in the step #1 above.

Note that this changes the semantics.  "git merge FETCH_HEAD" has
always behaved as if the first commit in the FETCH_HEAD file were
directly specified on the command line, creating a two-way merge
whose auto-generated merge log said "merge commit xyz".  With this
change, if the previous fetch was to grab multiple branches (e.g.
"git fetch $there topic-a topic-b"), the new world order is to
create an octopus, behaving as if "git pull $there topic-a topic-b"
were run.  This is a deliberate change to make that happen, and
can be seen in the changes to t3033 tests.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Junio C Hamano 2015-04-25 18:47:21 -07:00
Родитель 770380156d
Коммит 74e8bc59cb
3 изменённых файлов: 81 добавлений и 39 удалений

Просмотреть файл

@ -104,6 +104,10 @@ commit or stash your changes before running 'git merge'.
If no commit is given from the command line, merge the remote-tracking If no commit is given from the command line, merge the remote-tracking
branches that the current branch is configured to use as its upstream. branches that the current branch is configured to use as its upstream.
See also the configuration section of this manual page. See also the configuration section of this manual page.
+
When `FETCH_HEAD` (and no other commit) is specified, the branches
recorded in the `.git/FETCH_HEAD` file by the previous invocation
of `git fetch` for merging are merged to the current branch.
PRE-MERGE CHECKS PRE-MERGE CHECKS

Просмотреть файл

@ -505,28 +505,6 @@ static void merge_name(const char *remote, struct strbuf *msg)
strbuf_release(&truname); strbuf_release(&truname);
} }
if (!strcmp(remote, "FETCH_HEAD") &&
!access(git_path("FETCH_HEAD"), R_OK)) {
const char *filename;
FILE *fp;
struct strbuf line = STRBUF_INIT;
char *ptr;
filename = git_path("FETCH_HEAD");
fp = fopen(filename, "r");
if (!fp)
die_errno(_("could not open '%s' for reading"),
filename);
strbuf_getline(&line, fp, '\n');
fclose(fp);
ptr = strstr(line.buf, "\tnot-for-merge\t");
if (ptr)
strbuf_remove(&line, ptr-line.buf+1, 13);
strbuf_addbuf(msg, &line);
strbuf_release(&line);
goto cleanup;
}
if (remote_head->util) { if (remote_head->util) {
struct merge_remote_desc *desc; struct merge_remote_desc *desc;
desc = merge_remote_util(remote_head); desc = merge_remote_util(remote_head);
@ -1090,6 +1068,60 @@ static void prepare_merge_message(struct strbuf *merge_names, struct strbuf *mer
strbuf_setlen(merge_msg, merge_msg->len - 1); strbuf_setlen(merge_msg, merge_msg->len - 1);
} }
static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge_names)
{
const char *filename;
int fd, pos, npos;
struct strbuf fetch_head_file = STRBUF_INIT;
if (!merge_names)
merge_names = &fetch_head_file;
filename = git_path("FETCH_HEAD");
fd = open(filename, O_RDONLY);
if (fd < 0)
die_errno(_("could not open '%s' for reading"), filename);
if (strbuf_read(merge_names, fd, 0) < 0)
die_errno(_("could not read '%s'"), filename);
if (close(fd) < 0)
die_errno(_("could not close '%s'"), filename);
for (pos = 0; pos < merge_names->len; pos = npos) {
unsigned char sha1[20];
char *ptr;
struct commit *commit;
ptr = strchr(merge_names->buf + pos, '\n');
if (ptr)
npos = ptr - merge_names->buf + 1;
else
npos = merge_names->len;
if (npos - pos < 40 + 2 ||
get_sha1_hex(merge_names->buf + pos, sha1))
commit = NULL; /* bad */
else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2))
continue; /* not-for-merge */
else {
char saved = merge_names->buf[pos + 40];
merge_names->buf[pos + 40] = '\0';
commit = get_merge_parent(merge_names->buf + pos);
merge_names->buf[pos + 40] = saved;
}
if (!commit) {
if (ptr)
*ptr = '\0';
die("not something we can merge in %s: %s",
filename, merge_names->buf + pos);
}
remotes = &commit_list_insert(commit, remotes)->next;
}
if (merge_names == &fetch_head_file)
strbuf_release(&fetch_head_file);
}
static struct commit_list *collect_parents(struct commit *head_commit, static struct commit_list *collect_parents(struct commit *head_commit,
int *head_subsumed, int *head_subsumed,
int argc, const char **argv, int argc, const char **argv,
@ -1105,21 +1137,27 @@ static struct commit_list *collect_parents(struct commit *head_commit,
if (head_commit) if (head_commit)
remotes = &commit_list_insert(head_commit, remotes)->next; remotes = &commit_list_insert(head_commit, remotes)->next;
for (i = 0; i < argc; i++) {
struct commit *commit = get_merge_parent(argv[i]); if (argc == 1 && !strcmp(argv[0], "FETCH_HEAD")) {
if (!commit) handle_fetch_head(remotes, autogen);
help_unknown_ref(argv[i], "merge", remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
"not something we can merge"); } else {
remotes = &commit_list_insert(commit, remotes)->next; for (i = 0; i < argc; i++) {
struct commit *commit = get_merge_parent(argv[i]);
if (!commit)
help_unknown_ref(argv[i], "merge",
"not something we can merge");
remotes = &commit_list_insert(commit, remotes)->next;
}
remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
if (autogen) {
struct commit_list *p;
for (p = remoteheads; p; p = p->next)
merge_name(merge_remote_util(p->item)->name, autogen);
}
} }
remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
if (autogen) { if (autogen) {
struct commit_list *p;
for (p = remoteheads; p; p = p->next)
merge_name(merge_remote_util(p->item)->name, autogen);
prepare_merge_message(autogen, merge_msg); prepare_merge_message(autogen, merge_msg);
strbuf_release(autogen); strbuf_release(autogen);
} }

Просмотреть файл

@ -77,7 +77,7 @@ test_expect_success 'merge octopus, non-fast-forward' '
# The same set with FETCH_HEAD # The same set with FETCH_HEAD
test_expect_failure 'merge FETCH_HEAD octopus into void' ' test_expect_success 'merge FETCH_HEAD octopus into void' '
t3033_reset && t3033_reset &&
git checkout --orphan test && git checkout --orphan test &&
git rm -fr . && git rm -fr . &&
@ -88,7 +88,7 @@ test_expect_failure 'merge FETCH_HEAD octopus into void' '
test_must_fail git rev-parse HEAD test_must_fail git rev-parse HEAD
' '
test_expect_failure 'merge FETCH_HEAD octopus fast-forward (ff)' ' test_expect_success 'merge FETCH_HEAD octopus fast-forward (ff)' '
t3033_reset && t3033_reset &&
git reset --hard one && git reset --hard one &&
git fetch . left right && git fetch . left right &&
@ -100,7 +100,7 @@ test_expect_failure 'merge FETCH_HEAD octopus fast-forward (ff)' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_failure 'merge FETCH_HEAD octopus non-fast-forward (ff)' ' test_expect_success 'merge FETCH_HEAD octopus non-fast-forward (ff)' '
t3033_reset && t3033_reset &&
git reset --hard one && git reset --hard one &&
git fetch . left right && git fetch . left right &&
@ -112,7 +112,7 @@ test_expect_failure 'merge FETCH_HEAD octopus non-fast-forward (ff)' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_failure 'merge FETCH_HEAD octopus fast-forward (does not ff)' ' test_expect_success 'merge FETCH_HEAD octopus fast-forward (does not ff)' '
t3033_reset && t3033_reset &&
git fetch . left right && git fetch . left right &&
git merge FETCH_HEAD && git merge FETCH_HEAD &&
@ -123,7 +123,7 @@ test_expect_failure 'merge FETCH_HEAD octopus fast-forward (does not ff)' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_failure 'merge FETCH_HEAD octopus non-fast-forward' ' test_expect_success 'merge FETCH_HEAD octopus non-fast-forward' '
t3033_reset && t3033_reset &&
git fetch . left right && git fetch . left right &&
git merge --no-ff FETCH_HEAD && git merge --no-ff FETCH_HEAD &&