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 <gitster@pobox.com>

Junio C Hamano committed Nov 7, 2011 at 16:21 UTC 5231c633f2e54723d53a8cd928d9bc98b39dc35f
3 files changed +116 -11
builtin/commit.c
+5 -5
@@ -1425,7 +1425,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
1425 pptr = &commit_list_insert(c->item, pptr)->next;
1426 } else if (whence == FROM_MERGE) {
1427 struct strbuf m = STRBUF_INIT;
1428 - struct commit *commit;
1428 FILE *fp;
1429
1430 if (!reflog_msg)
@@ -1436,11 +1435,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
1435 die_errno(_("could not open '%s' for reading"),
1436 git_path("MERGE_HEAD"));
1437 while (strbuf_getline(&m, fp, '\n') != EOF) {
1439 - unsigned char sha1[20];
1440 - if (get_sha1_hex(m.buf, sha1) < 0)
1438 + struct commit *parent;
1439 +
1440 + parent = get_merge_parent(m.buf);
1441 + if (!parent)
1442 die(_("Corrupt MERGE_HEAD file (%s)"), m.buf);
1442 - commit = lookup_commit_or_die(sha1, "MERGE_HEAD");
1443 - pptr = &commit_list_insert(commit, pptr)->next;
1443 + pptr = &commit_list_insert(parent, pptr)->next;
1444 }
1445 fclose(fp);
1446 strbuf_release(&m);
commit.c
+92 -4
@@ -840,14 +840,95 @@ struct commit_list *reduce_heads(struct commit_list *heads)
840 return result;
841 }
842
843 +static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)
844 +{
845 + struct merge_remote_desc *desc;
846 + struct commit_extra_header *mergetag;
847 + char *buf;
848 + unsigned long size, len;
849 + enum object_type type;
850 +
851 + desc = merge_remote_util(parent);
852 + if (!desc || !desc->obj)
853 + return;
854 + buf = read_sha1_file(desc->obj->sha1, &type, &size);
855 + if (!buf || type != OBJ_TAG)
856 + goto free_return;
857 + len = parse_signature(buf, size);
858 + if (size == len)
859 + goto free_return;
860 + /*
861 + * We could verify this signature and either omit the tag when
862 + * it does not validate, but the integrator may not have the
863 + * public key of the signer of the tag he is merging, while a
864 + * later auditor may have it while auditing, so let's not run
865 + * verify-signed-buffer here for now...
866 + *
867 + * if (verify_signed_buffer(buf, len, buf + len, size - len, ...))
868 + * warn("warning: signed tag unverified.");
869 + */
870 + mergetag = xcalloc(1, sizeof(*mergetag));
871 + mergetag->key = xstrdup("mergetag");
872 + mergetag->value = buf;
873 + mergetag->len = size;
874 +
875 + **tail = mergetag;
876 + *tail = &mergetag->next;
877 + return;
878 +
879 +free_return:
880 + free(buf);
881 +}
882 +
883 +void append_merge_tag_headers(struct commit_list *parents,
884 + struct commit_extra_header ***tail)
885 +{
886 + while (parents) {
887 + struct commit *parent = parents->item;
888 + handle_signed_tag(parent, tail);
889 + parents = parents->next;
890 + }
891 +}
892 +
893 +static void add_extra_header(struct strbuf *buffer,
894 + struct commit_extra_header *extra)
895 +{
896 + strbuf_addstr(buffer, extra->key);
897 + strbuf_add_lines(buffer, " ", extra->value, extra->len);
898 +}
899 +
900 +void free_commit_extra_headers(struct commit_extra_header *extra)
901 +{
902 + while (extra) {
903 + struct commit_extra_header *next = extra->next;
904 + free(extra->key);
905 + free(extra->value);
906 + free(extra);
907 + extra = next;
908 + }
909 +}
910 +
911 +int commit_tree(const char *msg, unsigned char *tree,
912 + struct commit_list *parents, unsigned char *ret,
913 + const char *author)
914 +{
915 + struct commit_extra_header *extra = NULL, **tail = &extra;
916 + int result;
917 +
918 + append_merge_tag_headers(parents, &tail);
919 + result = commit_tree_extended(msg, tree, parents, ret, author, extra);
920 + free_commit_extra_headers(extra);
921 + return result;
922 +}
923 +
924 static const char commit_utf8_warn[] =
925 "Warning: commit message does not conform to UTF-8.\n"
926 "You may want to amend it after fixing the message, or set the config\n"
927 "variable i18n.commitencoding to the encoding your project uses.\n";
928
848 -int commit_tree(const char *msg, unsigned char *tree,
849 - struct commit_list *parents, unsigned char *ret,
850 - const char *author)
929 +int commit_tree_extended(const char *msg, unsigned char *tree,
930 + struct commit_list *parents, unsigned char *ret,
931 + const char *author, struct commit_extra_header *extra)
932 {
933 int result;
934 int encoding_is_utf8;
@@ -868,8 +949,10 @@ int commit_tree(const char *msg, unsigned char *tree,
949 */
950 while (parents) {
951 struct commit_list *next = parents->next;
952 + struct commit *parent = parents->item;
953 +
954 strbuf_addf(&buffer, "parent %s\n",
872 - sha1_to_hex(parents->item->object.sha1));
955 + sha1_to_hex(parent->object.sha1));
956 free(parents);
957 parents = next;
958 }
@@ -881,6 +964,11 @@ int commit_tree(const char *msg, unsigned char *tree,
964 strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
965 if (!encoding_is_utf8)
966 strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
967 +
968 + while (extra) {
969 + add_extra_header(&buffer, extra);
970 + extra = extra->next;
971 + }
972 strbuf_addch(&buffer, '\n');
973
974 /* And add the comment */
commit.h
+19 -2
@@ -181,9 +181,26 @@ static inline int single_parent(struct commit *commit)
181
182 struct commit_list *reduce_heads(struct commit_list *heads);
183
184 +struct commit_extra_header {
185 + struct commit_extra_header *next;
186 + char *key;
187 + char *value;
188 + size_t len;
189 +};
190 +
191 +extern void append_merge_tag_headers(struct commit_list *parents,
192 + struct commit_extra_header ***tail);
193 +
194 extern int commit_tree(const char *msg, unsigned char *tree,
185 - struct commit_list *parents, unsigned char *ret,
186 - const char *author);
195 + struct commit_list *parents, unsigned char *ret,
196 + const char *author);
197 +
198 +extern int commit_tree_extended(const char *msg, unsigned char *tree,
199 + struct commit_list *parents, unsigned char *ret,
200 + const char *author,
201 + struct commit_extra_header *);
202 +
203 +extern void free_commit_extra_headers(struct commit_extra_header *extra);
204
205 struct merge_remote_desc {
206 struct object *obj; /* the named object, could be a tag */