From ae8e4c9ce10ea9439a88b79a40aeff4696d40416 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 7 Nov 2011 13:26:22 -0800 Subject: merge: make usage of commit->util more extensible The merge-recursive code uses the commit->util field directly to annotate the commit objects given from the command line, i.e. the remote heads to be merged, with a single string to be used to describe it in its trace messages and conflict markers. Correct this short-signtedness by redefining the field to be a pointer to a structure "struct merge_remote_desc" that later enhancements can add more information. Store the original objects we were told to merge in a field "obj" in this struct, so that we can recover the tag we were told to merge. Signed-off-by: Junio C Hamano --- commit.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'commit.c') diff --git a/commit.c b/commit.c index 73b7e00292..83ff5035a7 100644 --- a/commit.c +++ b/commit.c @@ -894,3 +894,22 @@ int commit_tree(const char *msg, unsigned char *tree, strbuf_release(&buffer); return result; } + +struct commit *get_merge_parent(const char *name) +{ + struct object *obj; + struct commit *commit; + unsigned char sha1[20]; + if (get_sha1(name, sha1)) + return NULL; + obj = parse_object(sha1); + commit = (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); + if (commit && !commit->util) { + struct merge_remote_desc *desc; + desc = xmalloc(sizeof(*desc)); + desc->obj = obj; + desc->name = strdup(name); + commit->util = desc; + } + return commit; +} -- cgit v1.2.3 From 5231c633f2e54723d53a8cd928d9bc98b39dc35f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 7 Nov 2011 16:21:32 -0800 Subject: commit: copy merged signed tags to headers of merge commit Now MERGE_HEAD records the tag objects without peeling, we could record the result of manual conflict resolution via "git commit" without losing the tag information. Introduce a new "mergetag" multi-line header field to the commit object, and use it to store the entire contents of each signed tag merged. A commit header that has a multi-line payload begins with the header tag (e.g. "mergetag" in this case), SP, the first line of payload, LF, and all the remaining lines have a SP inserted at the beginning. In hindsight, it would have been better to make "merge --continue" as the way to continue from such an interrupted merge, not "commit", but this is a backward compatibility baggage we would need to carry around for now. Signed-off-by: Junio C Hamano --- builtin/commit.c | 10 +++--- commit.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- commit.h | 21 +++++++++++-- 3 files changed, 116 insertions(+), 11 deletions(-) (limited to 'commit.c') diff --git a/builtin/commit.c b/builtin/commit.c index c46f2d18e1..4688a738f7 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1425,7 +1425,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix) pptr = &commit_list_insert(c->item, pptr)->next; } else if (whence == FROM_MERGE) { struct strbuf m = STRBUF_INIT; - struct commit *commit; FILE *fp; if (!reflog_msg) @@ -1436,11 +1435,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) die_errno(_("could not open '%s' for reading"), git_path("MERGE_HEAD")); while (strbuf_getline(&m, fp, '\n') != EOF) { - unsigned char sha1[20]; - if (get_sha1_hex(m.buf, sha1) < 0) + struct commit *parent; + + parent = get_merge_parent(m.buf); + if (!parent) die(_("Corrupt MERGE_HEAD file (%s)"), m.buf); - commit = lookup_commit_or_die(sha1, "MERGE_HEAD"); - pptr = &commit_list_insert(commit, pptr)->next; + pptr = &commit_list_insert(parent, pptr)->next; } fclose(fp); strbuf_release(&m); diff --git a/commit.c b/commit.c index 83ff5035a7..a33bc89d8e 100644 --- a/commit.c +++ b/commit.c @@ -840,14 +840,95 @@ struct commit_list *reduce_heads(struct commit_list *heads) return result; } +static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail) +{ + struct merge_remote_desc *desc; + struct commit_extra_header *mergetag; + char *buf; + unsigned long size, len; + enum object_type type; + + desc = merge_remote_util(parent); + if (!desc || !desc->obj) + return; + buf = read_sha1_file(desc->obj->sha1, &type, &size); + if (!buf || type != OBJ_TAG) + goto free_return; + len = parse_signature(buf, size); + if (size == len) + goto free_return; + /* + * We could verify this signature and either omit the tag when + * it does not validate, but the integrator may not have the + * public key of the signer of the tag he is merging, while a + * later auditor may have it while auditing, so let's not run + * verify-signed-buffer here for now... + * + * if (verify_signed_buffer(buf, len, buf + len, size - len, ...)) + * warn("warning: signed tag unverified."); + */ + mergetag = xcalloc(1, sizeof(*mergetag)); + mergetag->key = xstrdup("mergetag"); + mergetag->value = buf; + mergetag->len = size; + + **tail = mergetag; + *tail = &mergetag->next; + return; + +free_return: + free(buf); +} + +void append_merge_tag_headers(struct commit_list *parents, + struct commit_extra_header ***tail) +{ + while (parents) { + struct commit *parent = parents->item; + handle_signed_tag(parent, tail); + parents = parents->next; + } +} + +static void add_extra_header(struct strbuf *buffer, + struct commit_extra_header *extra) +{ + strbuf_addstr(buffer, extra->key); + strbuf_add_lines(buffer, " ", extra->value, extra->len); +} + +void free_commit_extra_headers(struct commit_extra_header *extra) +{ + while (extra) { + struct commit_extra_header *next = extra->next; + free(extra->key); + free(extra->value); + free(extra); + extra = next; + } +} + +int commit_tree(const char *msg, unsigned char *tree, + struct commit_list *parents, unsigned char *ret, + const char *author) +{ + struct commit_extra_header *extra = NULL, **tail = &extra; + int result; + + append_merge_tag_headers(parents, &tail); + result = commit_tree_extended(msg, tree, parents, ret, author, extra); + free_commit_extra_headers(extra); + return result; +} + static const char commit_utf8_warn[] = "Warning: commit message does not conform to UTF-8.\n" "You may want to amend it after fixing the message, or set the config\n" "variable i18n.commitencoding to the encoding your project uses.\n"; -int commit_tree(const char *msg, unsigned char *tree, - struct commit_list *parents, unsigned char *ret, - const char *author) +int commit_tree_extended(const char *msg, unsigned char *tree, + struct commit_list *parents, unsigned char *ret, + const char *author, struct commit_extra_header *extra) { int result; int encoding_is_utf8; @@ -868,8 +949,10 @@ int commit_tree(const char *msg, unsigned char *tree, */ while (parents) { struct commit_list *next = parents->next; + struct commit *parent = parents->item; + strbuf_addf(&buffer, "parent %s\n", - sha1_to_hex(parents->item->object.sha1)); + sha1_to_hex(parent->object.sha1)); free(parents); parents = next; } @@ -881,6 +964,11 @@ int commit_tree(const char *msg, unsigned char *tree, strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME)); if (!encoding_is_utf8) strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding); + + while (extra) { + add_extra_header(&buffer, extra); + extra = extra->next; + } strbuf_addch(&buffer, '\n'); /* And add the comment */ diff --git a/commit.h b/commit.h index 5b57eab578..c1a723ecdb 100644 --- a/commit.h +++ b/commit.h @@ -181,9 +181,26 @@ static inline int single_parent(struct commit *commit) struct commit_list *reduce_heads(struct commit_list *heads); +struct commit_extra_header { + struct commit_extra_header *next; + char *key; + char *value; + size_t len; +}; + +extern void append_merge_tag_headers(struct commit_list *parents, + struct commit_extra_header ***tail); + extern int commit_tree(const char *msg, unsigned char *tree, - struct commit_list *parents, unsigned char *ret, - const char *author); + struct commit_list *parents, unsigned char *ret, + const char *author); + +extern int commit_tree_extended(const char *msg, unsigned char *tree, + struct commit_list *parents, unsigned char *ret, + const char *author, + struct commit_extra_header *); + +extern void free_commit_extra_headers(struct commit_extra_header *extra); struct merge_remote_desc { struct object *obj; /* the named object, could be a tag */ -- cgit v1.2.3 From ed7a42a0752fd71cf04273b8e691966dd2c33ebe Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 8 Nov 2011 15:38:07 -0800 Subject: commit: teach --amend to carry forward extra headers After running "git pull $there for-linus" to merge a signed tag, the integrator may need to amend the resulting merge commit to fix typoes in it. Teach --amend option to read the existing extra headers, and carry them forward. Signed-off-by: Junio C Hamano --- builtin/commit.c | 9 ++++++-- commit.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- commit.h | 3 +++ 3 files changed, 76 insertions(+), 3 deletions(-) (limited to 'commit.c') diff --git a/builtin/commit.c b/builtin/commit.c index 4688a738f7..fca7ea01f3 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1382,6 +1382,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) int allow_fast_forward = 1; struct wt_status s; struct commit *current_head = NULL; + struct commit_extra_header *extra = NULL; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); @@ -1483,12 +1484,16 @@ int cmd_commit(int argc, const char **argv, const char *prefix) exit(1); } - if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1, - author_ident.buf)) { + if (amend) + extra = read_commit_extra_headers(current_head); + + if (commit_tree_extended(sb.buf, active_cache_tree->sha1, parents, sha1, + author_ident.buf, extra)) { rollback_index_files(); die(_("failed to write commit object")); } strbuf_release(&author_ident); + free_commit_extra_headers(extra); ref_lock = lock_any_ref_for_update("HEAD", !current_head diff --git a/commit.c b/commit.c index a33bc89d8e..b78127403b 100644 --- a/commit.c +++ b/commit.c @@ -894,7 +894,72 @@ static void add_extra_header(struct strbuf *buffer, struct commit_extra_header *extra) { strbuf_addstr(buffer, extra->key); - strbuf_add_lines(buffer, " ", extra->value, extra->len); + if (extra->len) + strbuf_add_lines(buffer, " ", extra->value, extra->len); + else + strbuf_addch(buffer, '\n'); +} + +struct commit_extra_header *read_commit_extra_headers(struct commit *commit) +{ + struct commit_extra_header *extra = NULL; + unsigned long size; + enum object_type type; + char *buffer = read_sha1_file(commit->object.sha1, &type, &size); + if (buffer && type == OBJ_COMMIT) + extra = read_commit_extra_header_lines(buffer, size); + free(buffer); + return extra; +} + +static inline int standard_header_field(const char *field, size_t len) +{ + return ((len == 4 && !memcmp(field, "tree ", 5)) || + (len == 6 && !memcmp(field, "parent ", 7)) || + (len == 6 && !memcmp(field, "author ", 7)) || + (len == 9 && !memcmp(field, "committer ", 10)) || + (len == 8 && !memcmp(field, "encoding ", 9))); +} + +struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, size_t size) +{ + struct commit_extra_header *extra = NULL, **tail = &extra, *it = NULL; + const char *line, *next, *eof, *eob; + struct strbuf buf = STRBUF_INIT; + + for (line = buffer, eob = line + size; + line < eob && *line != '\n'; + line = next) { + next = memchr(line, '\n', eob - line); + next = next ? next + 1 : eob; + if (*line == ' ') { + /* continuation */ + if (it) + strbuf_add(&buf, line + 1, next - (line + 1)); + continue; + } + if (it) + it->value = strbuf_detach(&buf, &it->len); + strbuf_reset(&buf); + it = NULL; + + eof = strchr(line, ' '); + if (next <= eof) + eof = next; + + if (standard_header_field(line, eof - line)) + continue; + + it = xcalloc(1, sizeof(*it)); + it->key = xmemdupz(line, eof-line); + *tail = it; + tail = &it->next; + if (eof + 1 < next) + strbuf_add(&buf, eof + 1, next - (eof + 1)); + } + if (it) + it->value = strbuf_detach(&buf, &it->len); + return extra; } void free_commit_extra_headers(struct commit_extra_header *extra) diff --git a/commit.h b/commit.h index c1a723ecdb..3745f12099 100644 --- a/commit.h +++ b/commit.h @@ -200,6 +200,9 @@ extern int commit_tree_extended(const char *msg, unsigned char *tree, const char *author, struct commit_extra_header *); +extern struct commit_extra_header *read_commit_extra_headers(struct commit *); +extern struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len); + extern void free_commit_extra_headers(struct commit_extra_header *extra); struct merge_remote_desc { -- cgit v1.2.3