#include "commit.h"
#include "pkt-line.h"
#include "utf8.h"
-#include "interpolate.h"
#include "diff.h"
#include "revision.h"
+#include "notes.h"
+#include "gpg-interface.h"
+#include "mergesort.h"
-int save_commit_buffer = 1;
-
-struct sort_node
-{
- /*
- * the number of children of the associated commit
- * that also occur in the list being sorted.
- */
- unsigned int indegree;
-
- /*
- * reference to original list item that we will re-use
- * on output.
- */
- struct commit_list * list_item;
+static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
-};
+int save_commit_buffer = 1;
const char *commit_type = "commit";
-static struct cmt_fmt_map {
- const char *n;
- size_t cmp_len;
- enum cmit_fmt v;
-} cmt_fmts[] = {
- { "raw", 1, CMIT_FMT_RAW },
- { "medium", 1, CMIT_FMT_MEDIUM },
- { "short", 1, CMIT_FMT_SHORT },
- { "email", 1, CMIT_FMT_EMAIL },
- { "full", 5, CMIT_FMT_FULL },
- { "fuller", 5, CMIT_FMT_FULLER },
- { "oneline", 1, CMIT_FMT_ONELINE },
- { "format:", 7, CMIT_FMT_USERFORMAT},
-};
-
-static char *user_format;
-
-enum cmit_fmt get_commit_format(const char *arg)
-{
- int i;
-
- if (!arg || !*arg)
- return CMIT_FMT_DEFAULT;
- if (*arg == '=')
- arg++;
- if (!prefixcmp(arg, "format:")) {
- if (user_format)
- free(user_format);
- user_format = xstrdup(arg + 7);
- return CMIT_FMT_USERFORMAT;
- }
- for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
- if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
- !strncmp(arg, cmt_fmts[i].n, strlen(arg)))
- return cmt_fmts[i].v;
- }
-
- die("invalid --pretty format: %s", arg);
-}
-
static struct commit *check_commit(struct object *obj,
const unsigned char *sha1,
int quiet)
return lookup_commit_reference_gently(sha1, 0);
}
+struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_name)
+{
+ struct commit *c = lookup_commit_reference(sha1);
+ if (!c)
+ die(_("could not parse %s"), ref_name);
+ if (hashcmp(sha1, c->object.sha1)) {
+ warning(_("%s %s is not a commit!"),
+ ref_name, sha1_to_hex(sha1));
+ }
+ return c;
+}
+
struct commit *lookup_commit(const unsigned char *sha1)
{
struct object *obj = lookup_object(sha1);
return check_commit(obj, sha1, 0);
}
-static unsigned long parse_commit_date(const char *buf)
+struct commit *lookup_commit_reference_by_name(const char *name)
{
- unsigned long date;
+ unsigned char sha1[20];
+ struct commit *commit;
+ if (get_sha1_committish(name, sha1))
+ return NULL;
+ commit = lookup_commit_reference(sha1);
+ if (!commit || parse_commit(commit))
+ return NULL;
+ return commit;
+}
+
+static unsigned long parse_commit_date(const char *buf, const char *tail)
+{
+ const char *dateptr;
+
+ if (buf + 6 >= tail)
+ return 0;
if (memcmp(buf, "author", 6))
return 0;
- while (*buf++ != '\n')
+ while (buf < tail && *buf++ != '\n')
/* nada */;
+ if (buf + 9 >= tail)
+ return 0;
if (memcmp(buf, "committer", 9))
return 0;
- while (*buf++ != '>')
+ while (buf < tail && *buf++ != '>')
+ /* nada */;
+ if (buf >= tail)
+ return 0;
+ dateptr = buf;
+ while (buf < tail && *buf++ != '\n')
/* nada */;
- date = strtoul(buf, NULL, 10);
- if (date == ULONG_MAX)
- date = 0;
- return date;
+ if (buf >= tail)
+ return 0;
+ /* dateptr < buf && buf[-1] == '\n', so strtoul will stop at buf-1 */
+ return strtoul(dateptr, NULL, 10);
}
static struct commit_graft **commit_graft;
int i;
struct commit_graft *graft = NULL;
- if (buf[len-1] == '\n')
- buf[--len] = 0;
+ while (len && isspace(buf[len-1]))
+ buf[--len] = '\0';
if (buf[0] == '#' || buf[0] == '\0')
return NULL;
- if ((len + 1) % 41) {
- bad_graft_data:
- error("bad graft data: %s", buf);
- free(graft);
- return NULL;
- }
+ if ((len + 1) % 41)
+ goto bad_graft_data;
i = (len + 1) / 41 - 1;
graft = xmalloc(sizeof(*graft) + 20 * i);
graft->nr_parent = i;
goto bad_graft_data;
}
return graft;
+
+bad_graft_data:
+ error("bad graft data: %s", buf);
+ free(graft);
+ return NULL;
}
-int read_graft_file(const char *graft_file)
+static int read_graft_file(const char *graft_file)
{
FILE *fp = fopen(graft_file, "r");
char buf[1024];
commit_graft_prepared = 1;
}
-static struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
+struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
{
int pos;
prepare_commit_graft();
return commit_graft[pos];
}
-int write_shallow_commits(int fd, int use_pack_protocol)
+int for_each_commit_graft(each_commit_graft_fn fn, void *cb_data)
{
- int i, count = 0;
- for (i = 0; i < commit_graft_nr; i++)
- if (commit_graft[i]->nr_parent < 0) {
- const char *hex =
- sha1_to_hex(commit_graft[i]->sha1);
- count++;
- if (use_pack_protocol)
- packet_write(fd, "shallow %s", hex);
- else {
- if (write_in_full(fd, hex, 40) != 40)
- break;
- if (write_in_full(fd, "\n", 1) != 1)
- break;
- }
- }
- return count;
+ int i, ret;
+ for (i = ret = 0; i < commit_graft_nr && !ret; i++)
+ ret = fn(commit_graft[i], cb_data);
+ return ret;
}
int unregister_shallow(const unsigned char *sha1)
if (pos < 0)
return -1;
if (pos + 1 < commit_graft_nr)
- memcpy(commit_graft + pos, commit_graft + pos + 1,
+ memmove(commit_graft + pos, commit_graft + pos + 1,
sizeof(struct commit_graft *)
* (commit_graft_nr - pos - 1));
commit_graft_nr--;
return 0;
}
-int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
+int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size)
{
- char *tail = buffer;
- char *bufptr = buffer;
+ const char *tail = buffer;
+ const char *bufptr = buffer;
unsigned char parent[20];
struct commit_list **pptr;
struct commit_graft *graft;
- unsigned n_refs = 0;
if (item->object.parsed)
return 0;
item->object.parsed = 1;
tail += size;
- if (tail <= bufptr + 5 || memcmp(bufptr, "tree ", 5))
+ if (tail <= bufptr + 46 || memcmp(bufptr, "tree ", 5) || bufptr[45] != '\n')
return error("bogus commit object %s", sha1_to_hex(item->object.sha1));
- if (tail <= bufptr + 45 || get_sha1_hex(bufptr + 5, parent) < 0)
+ if (get_sha1_hex(bufptr + 5, parent) < 0)
return error("bad tree pointer in commit %s",
sha1_to_hex(item->object.sha1));
item->tree = lookup_tree(parent);
- if (item->tree)
- n_refs++;
bufptr += 46; /* "tree " + "hex sha1" + "\n" */
pptr = &item->parents;
bufptr[47] != '\n')
return error("bad parents in commit %s", sha1_to_hex(item->object.sha1));
bufptr += 48;
- if (graft)
+ /*
+ * The clone is shallow if nr_parent < 0, and we must
+ * not traverse its real parents even when we unhide them.
+ */
+ if (graft && (graft->nr_parent < 0 || grafts_replace_parents))
continue;
new_parent = lookup_commit(parent);
- if (new_parent) {
+ if (new_parent)
pptr = &commit_list_insert(new_parent, pptr)->next;
- n_refs++;
- }
}
if (graft) {
int i;
if (!new_parent)
continue;
pptr = &commit_list_insert(new_parent, pptr)->next;
- n_refs++;
}
}
- item->date = parse_commit_date(bufptr);
-
- if (track_object_refs) {
- unsigned i = 0;
- struct commit_list *p;
- struct object_refs *refs = alloc_object_refs(n_refs);
- if (item->tree)
- refs->ref[i++] = &item->tree->object;
- for (p = item->parents; p; p = p->next)
- refs->ref[i++] = &p->item->object;
- set_object_refs(&item->object, refs);
- }
+ item->date = parse_commit_date(bufptr, tail);
return 0;
}
unsigned long size;
int ret;
+ if (!item)
+ return -1;
if (item->object.parsed)
return 0;
buffer = read_sha1_file(item->object.sha1, &type, &size);
return ret;
}
+int find_commit_subject(const char *commit_buffer, const char **subject)
+{
+ const char *eol;
+ const char *p = commit_buffer;
+
+ while (*p && (*p != '\n' || p[1] != '\n'))
+ p++;
+ if (*p) {
+ p += 2;
+ for (eol = p; *eol && *eol != '\n'; eol++)
+ ; /* do nothing */
+ } else
+ eol = p;
+
+ *subject = p;
+
+ return eol - p;
+}
+
struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
{
struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
return new_list;
}
+unsigned commit_list_count(const struct commit_list *l)
+{
+ unsigned c = 0;
+ for (; l; l = l->next )
+ c++;
+ return c;
+}
+
void free_commit_list(struct commit_list *list)
{
while (list) {
}
}
-struct commit_list * insert_by_date(struct commit *item, struct commit_list **list)
+struct commit_list * commit_list_insert_by_date(struct commit *item, struct commit_list **list)
{
struct commit_list **pp = list;
struct commit_list *p;
return commit_list_insert(item, pp);
}
+static int commit_list_compare_by_date(const void *a, const void *b)
+{
+ unsigned long a_date = ((const struct commit_list *)a)->item->date;
+ unsigned long b_date = ((const struct commit_list *)b)->item->date;
+ if (a_date < b_date)
+ return 1;
+ if (a_date > b_date)
+ return -1;
+ return 0;
+}
-void sort_by_date(struct commit_list **list)
+static void *commit_list_get_next(const void *a)
{
- struct commit_list *ret = NULL;
- while (*list) {
- insert_by_date((*list)->item, &ret);
- *list = (*list)->next;
- }
- *list = ret;
+ return ((const struct commit_list *)a)->next;
+}
+
+static void commit_list_set_next(void *a, void *next)
+{
+ ((struct commit_list *)a)->next = next;
+}
+
+void commit_list_sort_by_date(struct commit_list **list)
+{
+ *list = llist_mergesort(*list, commit_list_get_next, commit_list_set_next,
+ commit_list_compare_by_date);
}
struct commit *pop_most_recent_commit(struct commit_list **list,
while (parents) {
struct commit *commit = parents->item;
- parse_commit(commit);
- if (!(commit->object.flags & mark)) {
+ if (!parse_commit(commit) && !(commit->object.flags & mark)) {
commit->object.flags |= mark;
- insert_by_date(commit, list);
+ commit_list_insert_by_date(commit, list);
}
parents = parents->next;
}
return ret;
}
-void clear_commit_marks(struct commit *commit, unsigned int mark)
+static void clear_commit_marks_1(struct commit_list **plist,
+ struct commit *commit, unsigned int mark)
{
- struct commit_list *parents;
-
- commit->object.flags &= ~mark;
- parents = commit->parents;
- while (parents) {
- struct commit *parent = parents->item;
-
- /* Have we already cleared this? */
- if (mark & parent->object.flags)
- clear_commit_marks(parent, mark);
- parents = parents->next;
- }
-}
-
-/*
- * Generic support for pretty-printing the header
- */
-static int get_one_line(const char *msg)
-{
- int ret = 0;
-
- for (;;) {
- char c = *msg++;
- if (!c)
- break;
- ret++;
- if (c == '\n')
- break;
- }
- return ret;
-}
-
-/* High bit set, or ISO-2022-INT */
-static int non_ascii(int ch)
-{
- ch = (ch & 0xff);
- return ((ch & 0x80) || (ch == 0x1b));
-}
-
-static int is_rfc2047_special(char ch)
-{
- return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_'));
-}
-
-static void add_rfc2047(struct strbuf *sb, const char *line, int len,
- const char *encoding)
-{
- int i, last;
-
- for (i = 0; i < len; i++) {
- int ch = line[i];
- if (non_ascii(ch))
- goto needquote;
- if ((i + 1 < len) && (ch == '=' && line[i+1] == '?'))
- goto needquote;
- }
- strbuf_add(sb, line, len);
- return;
-
-needquote:
- strbuf_grow(sb, len * 3 + strlen(encoding) + 100);
- strbuf_addf(sb, "=?%s?q?", encoding);
- for (i = last = 0; i < len; i++) {
- unsigned ch = line[i] & 0xFF;
- /*
- * We encode ' ' using '=20' even though rfc2047
- * allows using '_' for readability. Unfortunately,
- * many programs do not understand this and just
- * leave the underscore in place.
- */
- if (is_rfc2047_special(ch) || ch == ' ') {
- strbuf_add(sb, line + last, i - last);
- strbuf_addf(sb, "=%02X", ch);
- last = i + 1;
- }
- }
- strbuf_add(sb, line + last, len - last);
- strbuf_addstr(sb, "?=");
-}
-
-static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
- const char *line, enum date_mode dmode,
- const char *encoding)
-{
- char *date;
- int namelen;
- unsigned long time;
- int tz;
- const char *filler = " ";
+ while (commit) {
+ struct commit_list *parents;
- if (fmt == CMIT_FMT_ONELINE)
- return;
- date = strchr(line, '>');
- if (!date)
- return;
- namelen = ++date - line;
- time = strtoul(date, &date, 10);
- tz = strtol(date, NULL, 10);
-
- if (fmt == CMIT_FMT_EMAIL) {
- char *name_tail = strchr(line, '<');
- int display_name_length;
- if (!name_tail)
+ if (!(mark & commit->object.flags))
return;
- while (line < name_tail && isspace(name_tail[-1]))
- name_tail--;
- display_name_length = name_tail - line;
- filler = "";
- strbuf_addstr(sb, "From: ");
- add_rfc2047(sb, line, display_name_length, encoding);
- strbuf_add(sb, name_tail, namelen - display_name_length);
- strbuf_addch(sb, '\n');
- } else {
- strbuf_addf(sb, "%s: %.*s%.*s\n", what,
- (fmt == CMIT_FMT_FULLER) ? 4 : 0,
- filler, namelen, line);
- }
- switch (fmt) {
- case CMIT_FMT_MEDIUM:
- strbuf_addf(sb, "Date: %s\n", show_date(time, tz, dmode));
- break;
- case CMIT_FMT_EMAIL:
- strbuf_addf(sb, "Date: %s\n", show_date(time, tz, DATE_RFC2822));
- break;
- case CMIT_FMT_FULLER:
- strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, dmode));
- break;
- default:
- /* notin' */
- break;
- }
-}
-
-static int is_empty_line(const char *line, int *len_p)
-{
- int len = *len_p;
- while (len && isspace(line[len-1]))
- len--;
- *len_p = len;
- return !len;
-}
-static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
- const struct commit *commit, int abbrev)
-{
- struct commit_list *parent = commit->parents;
-
- if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
- !parent || !parent->next)
- return;
-
- strbuf_addstr(sb, "Merge:");
-
- while (parent) {
- struct commit *p = parent->item;
- const char *hex = NULL;
- const char *dots;
- if (abbrev)
- hex = find_unique_abbrev(p->object.sha1, abbrev);
- if (!hex)
- hex = sha1_to_hex(p->object.sha1);
- dots = (abbrev && strlen(hex) != 40) ? "..." : "";
- parent = parent->next;
-
- strbuf_addf(sb, " %s%s", hex, dots);
- }
- strbuf_addch(sb, '\n');
-}
+ commit->object.flags &= ~mark;
-static char *get_header(const struct commit *commit, const char *key)
-{
- int key_len = strlen(key);
- const char *line = commit->buffer;
-
- for (;;) {
- const char *eol = strchr(line, '\n'), *next;
-
- if (line == eol)
- return NULL;
- if (!eol) {
- eol = line + strlen(line);
- next = NULL;
- } else
- next = eol + 1;
- if (eol - line > key_len &&
- !strncmp(line, key, key_len) &&
- line[key_len] == ' ') {
- return xmemdupz(line + key_len + 1, eol - line - key_len - 1);
- }
- line = next;
- }
-}
-
-static char *replace_encoding_header(char *buf, const char *encoding)
-{
- struct strbuf tmp;
- size_t start, len;
- char *cp = buf;
-
- /* guess if there is an encoding header before a \n\n */
- while (strncmp(cp, "encoding ", strlen("encoding "))) {
- cp = strchr(cp, '\n');
- if (!cp || *++cp == '\n')
- return buf;
- }
- start = cp - buf;
- cp = strchr(cp, '\n');
- if (!cp)
- return buf; /* should not happen but be defensive */
- len = cp + 1 - (buf + start);
-
- strbuf_init(&tmp, 0);
- strbuf_attach(&tmp, buf, strlen(buf), strlen(buf) + 1);
- if (is_encoding_utf8(encoding)) {
- /* we have re-coded to UTF-8; drop the header */
- strbuf_remove(&tmp, start, len);
- } else {
- /* just replaces XXXX in 'encoding XXXX\n' */
- strbuf_splice(&tmp, start + strlen("encoding "),
- len - strlen("encoding \n"),
- encoding, strlen(encoding));
- }
- return strbuf_detach(&tmp, NULL);
-}
-
-static char *logmsg_reencode(const struct commit *commit,
- const char *output_encoding)
-{
- static const char *utf8 = "utf-8";
- const char *use_encoding;
- char *encoding;
- char *out;
-
- if (!*output_encoding)
- return NULL;
- encoding = get_header(commit, "encoding");
- use_encoding = encoding ? encoding : utf8;
- if (!strcmp(use_encoding, output_encoding))
- if (encoding) /* we'll strip encoding header later */
- out = xstrdup(commit->buffer);
- else
- return NULL; /* nothing to do */
- else
- out = reencode_string(commit->buffer,
- output_encoding, use_encoding);
- if (out)
- out = replace_encoding_header(out, output_encoding);
-
- free(encoding);
- return out;
-}
-
-static void fill_person(struct interp *table, const char *msg, int len)
-{
- int start, end, tz = 0;
- unsigned long date;
- char *ep;
-
- /* parse name */
- for (end = 0; end < len && msg[end] != '<'; end++)
- ; /* do nothing */
- start = end + 1;
- while (end > 0 && isspace(msg[end - 1]))
- end--;
- table[0].value = xmemdupz(msg, end);
-
- if (start >= len)
- return;
-
- /* parse email */
- for (end = start + 1; end < len && msg[end] != '>'; end++)
- ; /* do nothing */
-
- if (end >= len)
- return;
-
- table[1].value = xmemdupz(msg + start, end - start);
-
- /* parse date */
- for (start = end + 1; start < len && isspace(msg[start]); start++)
- ; /* do nothing */
- if (start >= len)
- return;
- date = strtoul(msg + start, &ep, 10);
- if (msg + start == ep)
- return;
-
- table[5].value = xmemdupz(msg + start, ep - (msg + start));
-
- /* parse tz */
- for (start = ep - msg + 1; start < len && isspace(msg[start]); start++)
- ; /* do nothing */
- if (start + 1 < len) {
- tz = strtoul(msg + start + 1, NULL, 10);
- if (msg[start] == '-')
- tz = -tz;
- }
-
- interp_set_entry(table, 2, show_date(date, tz, DATE_NORMAL));
- interp_set_entry(table, 3, show_date(date, tz, DATE_RFC2822));
- interp_set_entry(table, 4, show_date(date, tz, DATE_RELATIVE));
- interp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));
-}
-
-void format_commit_message(const struct commit *commit,
- const void *format, struct strbuf *sb)
-{
- struct interp table[] = {
- { "%H" }, /* commit hash */
- { "%h" }, /* abbreviated commit hash */
- { "%T" }, /* tree hash */
- { "%t" }, /* abbreviated tree hash */
- { "%P" }, /* parent hashes */
- { "%p" }, /* abbreviated parent hashes */
- { "%an" }, /* author name */
- { "%ae" }, /* author email */
- { "%ad" }, /* author date */
- { "%aD" }, /* author date, RFC2822 style */
- { "%ar" }, /* author date, relative */
- { "%at" }, /* author date, UNIX timestamp */
- { "%ai" }, /* author date, ISO 8601 */
- { "%cn" }, /* committer name */
- { "%ce" }, /* committer email */
- { "%cd" }, /* committer date */
- { "%cD" }, /* committer date, RFC2822 style */
- { "%cr" }, /* committer date, relative */
- { "%ct" }, /* committer date, UNIX timestamp */
- { "%ci" }, /* committer date, ISO 8601 */
- { "%e" }, /* encoding */
- { "%s" }, /* subject */
- { "%b" }, /* body */
- { "%Cred" }, /* red */
- { "%Cgreen" }, /* green */
- { "%Cblue" }, /* blue */
- { "%Creset" }, /* reset color */
- { "%n" }, /* newline */
- { "%m" }, /* left/right/bottom */
- };
- enum interp_index {
- IHASH = 0, IHASH_ABBREV,
- ITREE, ITREE_ABBREV,
- IPARENTS, IPARENTS_ABBREV,
- IAUTHOR_NAME, IAUTHOR_EMAIL,
- IAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,
- IAUTHOR_TIMESTAMP, IAUTHOR_ISO8601,
- ICOMMITTER_NAME, ICOMMITTER_EMAIL,
- ICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,
- ICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,
- ICOMMITTER_ISO8601,
- IENCODING,
- ISUBJECT,
- IBODY,
- IRED, IGREEN, IBLUE, IRESET_COLOR,
- INEWLINE,
- ILEFT_RIGHT,
- };
- struct commit_list *p;
- char parents[1024];
- unsigned long len;
- int i;
- enum { HEADER, SUBJECT, BODY } state;
- const char *msg = commit->buffer;
-
- if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))
- die("invalid interp table!");
-
- /* these are independent of the commit */
- interp_set_entry(table, IRED, "\033[31m");
- interp_set_entry(table, IGREEN, "\033[32m");
- interp_set_entry(table, IBLUE, "\033[34m");
- interp_set_entry(table, IRESET_COLOR, "\033[m");
- interp_set_entry(table, INEWLINE, "\n");
-
- /* these depend on the commit */
- if (!commit->object.parsed)
- parse_object(commit->object.sha1);
- interp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));
- interp_set_entry(table, IHASH_ABBREV,
- find_unique_abbrev(commit->object.sha1,
- DEFAULT_ABBREV));
- interp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));
- interp_set_entry(table, ITREE_ABBREV,
- find_unique_abbrev(commit->tree->object.sha1,
- DEFAULT_ABBREV));
- interp_set_entry(table, ILEFT_RIGHT,
- (commit->object.flags & BOUNDARY)
- ? "-"
- : (commit->object.flags & SYMMETRIC_LEFT)
- ? "<"
- : ">");
-
- parents[1] = 0;
- for (i = 0, p = commit->parents;
- p && i < sizeof(parents) - 1;
- p = p->next)
- i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
- sha1_to_hex(p->item->object.sha1));
- interp_set_entry(table, IPARENTS, parents + 1);
-
- parents[1] = 0;
- for (i = 0, p = commit->parents;
- p && i < sizeof(parents) - 1;
- p = p->next)
- i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
- find_unique_abbrev(p->item->object.sha1,
- DEFAULT_ABBREV));
- interp_set_entry(table, IPARENTS_ABBREV, parents + 1);
-
- for (i = 0, state = HEADER; msg[i] && state < BODY; i++) {
- int eol;
- for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)
- ; /* do nothing */
-
- if (state == SUBJECT) {
- table[ISUBJECT].value = xmemdupz(msg + i, eol - i);
- i = eol;
- }
- if (i == eol) {
- state++;
- /* strip empty lines */
- while (msg[eol + 1] == '\n')
- eol++;
- } else if (!prefixcmp(msg + i, "author "))
- fill_person(table + IAUTHOR_NAME,
- msg + i + 7, eol - i - 7);
- else if (!prefixcmp(msg + i, "committer "))
- fill_person(table + ICOMMITTER_NAME,
- msg + i + 10, eol - i - 10);
- else if (!prefixcmp(msg + i, "encoding "))
- table[IENCODING].value =
- xmemdupz(msg + i + 9, eol - i - 9);
- i = eol;
- }
- if (msg[i])
- table[IBODY].value = xstrdup(msg + i);
- for (i = 0; i < ARRAY_SIZE(table); i++)
- if (!table[i].value)
- interp_set_entry(table, i, "<unknown>");
-
- len = interpolate(sb->buf + sb->len, strbuf_avail(sb),
- format, table, ARRAY_SIZE(table));
- if (len > strbuf_avail(sb)) {
- strbuf_grow(sb, len);
- interpolate(sb->buf + sb->len, strbuf_avail(sb) + 1,
- format, table, ARRAY_SIZE(table));
- }
- strbuf_setlen(sb, sb->len + len);
- interp_clear_table(table, ARRAY_SIZE(table));
-}
-
-static void pp_header(enum cmit_fmt fmt,
- int abbrev,
- enum date_mode dmode,
- const char *encoding,
- const struct commit *commit,
- const char **msg_p,
- struct strbuf *sb)
-{
- int parents_shown = 0;
-
- for (;;) {
- const char *line = *msg_p;
- int linelen = get_one_line(*msg_p);
-
- if (!linelen)
- return;
- *msg_p += linelen;
-
- if (linelen == 1)
- /* End of header */
+ parents = commit->parents;
+ if (!parents)
return;
- if (fmt == CMIT_FMT_RAW) {
- strbuf_add(sb, line, linelen);
- continue;
- }
+ while ((parents = parents->next))
+ commit_list_insert(parents->item, plist);
- if (!memcmp(line, "parent ", 7)) {
- if (linelen != 48)
- die("bad parent line in commit");
- continue;
- }
-
- if (!parents_shown) {
- struct commit_list *parent;
- int num;
- for (parent = commit->parents, num = 0;
- parent;
- parent = parent->next, num++)
- ;
- /* with enough slop */
- strbuf_grow(sb, num * 50 + 20);
- add_merge_info(fmt, sb, commit, abbrev);
- parents_shown = 1;
- }
-
- /*
- * MEDIUM == DEFAULT shows only author with dates.
- * FULL shows both authors but not dates.
- * FULLER shows both authors and dates.
- */
- if (!memcmp(line, "author ", 7)) {
- strbuf_grow(sb, linelen + 80);
- add_user_info("Author", fmt, sb, line + 7, dmode, encoding);
- }
- if (!memcmp(line, "committer ", 10) &&
- (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
- strbuf_grow(sb, linelen + 80);
- add_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
- }
- }
-}
-
-static void pp_title_line(enum cmit_fmt fmt,
- const char **msg_p,
- struct strbuf *sb,
- const char *subject,
- const char *after_subject,
- const char *encoding,
- int plain_non_ascii)
-{
- struct strbuf title;
-
- strbuf_init(&title, 80);
-
- for (;;) {
- const char *line = *msg_p;
- int linelen = get_one_line(line);
-
- *msg_p += linelen;
- if (!linelen || is_empty_line(line, &linelen))
- break;
-
- strbuf_grow(&title, linelen + 2);
- if (title.len) {
- if (fmt == CMIT_FMT_EMAIL) {
- strbuf_addch(&title, '\n');
- }
- strbuf_addch(&title, ' ');
- }
- strbuf_add(&title, line, linelen);
- }
-
- strbuf_grow(sb, title.len + 1024);
- if (subject) {
- strbuf_addstr(sb, subject);
- add_rfc2047(sb, title.buf, title.len, encoding);
- } else {
- strbuf_addbuf(sb, &title);
+ commit = commit->parents->item;
}
- strbuf_addch(sb, '\n');
-
- if (plain_non_ascii) {
- const char *header_fmt =
- "MIME-Version: 1.0\n"
- "Content-Type: text/plain; charset=%s\n"
- "Content-Transfer-Encoding: 8bit\n";
- strbuf_addf(sb, header_fmt, encoding);
- }
- if (after_subject) {
- strbuf_addstr(sb, after_subject);
- }
- if (fmt == CMIT_FMT_EMAIL) {
- strbuf_addch(sb, '\n');
- }
- strbuf_release(&title);
}
-static void pp_remainder(enum cmit_fmt fmt,
- const char **msg_p,
- struct strbuf *sb,
- int indent)
+void clear_commit_marks(struct commit *commit, unsigned int mark)
{
- int first = 1;
- for (;;) {
- const char *line = *msg_p;
- int linelen = get_one_line(line);
- *msg_p += linelen;
-
- if (!linelen)
- break;
-
- if (is_empty_line(line, &linelen)) {
- if (first)
- continue;
- if (fmt == CMIT_FMT_SHORT)
- break;
- }
- first = 0;
-
- strbuf_grow(sb, linelen + indent + 20);
- if (indent) {
- memset(sb->buf + sb->len, ' ', indent);
- strbuf_setlen(sb, sb->len + indent);
- }
- strbuf_add(sb, line, linelen);
- strbuf_addch(sb, '\n');
- }
+ struct commit_list *list = NULL;
+ commit_list_insert(commit, &list);
+ while (list)
+ clear_commit_marks_1(&list, pop_commit(&list), mark);
}
-void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
- struct strbuf *sb, int abbrev,
- const char *subject, const char *after_subject,
- enum date_mode dmode)
+void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
{
- unsigned long beginning_of_body;
- int indent = 4;
- const char *msg = commit->buffer;
- int plain_non_ascii = 0;
- char *reencoded;
- const char *encoding;
-
- if (fmt == CMIT_FMT_USERFORMAT) {
- format_commit_message(commit, user_format, sb);
- return;
- }
-
- encoding = (git_log_output_encoding
- ? git_log_output_encoding
- : git_commit_encoding);
- if (!encoding)
- encoding = "utf-8";
- reencoded = logmsg_reencode(commit, encoding);
- if (reencoded) {
- msg = reencoded;
- }
-
- if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
- indent = 0;
-
- /* After-subject is used to pass in Content-Type: multipart
- * MIME header; in that case we do not have to do the
- * plaintext content type even if the commit message has
- * non 7-bit ASCII character. Otherwise, check if we need
- * to say this is not a 7-bit ASCII.
- */
- if (fmt == CMIT_FMT_EMAIL && !after_subject) {
- int i, ch, in_body;
-
- for (in_body = i = 0; (ch = msg[i]); i++) {
- if (!in_body) {
- /* author could be non 7-bit ASCII but
- * the log may be so; skip over the
- * header part first.
- */
- if (ch == '\n' && msg[i+1] == '\n')
- in_body = 1;
- }
- else if (non_ascii(ch)) {
- plain_non_ascii = 1;
- break;
- }
- }
- }
-
- pp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb);
- if (fmt != CMIT_FMT_ONELINE && !subject) {
- strbuf_addch(sb, '\n');
+ struct object *object;
+ struct commit *commit;
+ unsigned int i;
+
+ for (i = 0; i < a->nr; i++) {
+ object = a->objects[i].item;
+ commit = lookup_commit_reference_gently(object->sha1, 1);
+ if (commit)
+ clear_commit_marks(commit, mark);
}
-
- /* Skip excess blank lines at the beginning of body, if any... */
- for (;;) {
- int linelen = get_one_line(msg);
- int ll = linelen;
- if (!linelen)
- break;
- if (!is_empty_line(msg, &ll))
- break;
- msg += linelen;
- }
-
- /* These formats treat the title line specially. */
- if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
- pp_title_line(fmt, &msg, sb, subject,
- after_subject, encoding, plain_non_ascii);
-
- beginning_of_body = sb->len;
- if (fmt != CMIT_FMT_ONELINE)
- pp_remainder(fmt, &msg, sb, indent);
- strbuf_rtrim(sb);
-
- /* Make sure there is an EOLN for the non-oneline case */
- if (fmt != CMIT_FMT_ONELINE)
- strbuf_addch(sb, '\n');
-
- /*
- * The caller may append additional body text in e-mail
- * format. Make sure we did not strip the blank line
- * between the header and the body.
- */
- if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
- strbuf_addch(sb, '\n');
- free(reencoded);
}
struct commit *pop_commit(struct commit_list **stack)
return item;
}
-void topo_sort_default_setter(struct commit *c, void *data)
-{
- c->util = data;
-}
-
-void *topo_sort_default_getter(struct commit *c)
-{
- return c->util;
-}
-
/*
* Performs an in-place topological sort on the list supplied.
*/
void sort_in_topological_order(struct commit_list ** list, int lifo)
{
- sort_in_topological_order_fn(list, lifo, topo_sort_default_setter,
- topo_sort_default_getter);
-}
-
-void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
- topo_sort_set_fn_t setter,
- topo_sort_get_fn_t getter)
-{
- struct commit_list * next = *list;
- struct commit_list * work = NULL, **insert;
- struct commit_list ** pptr = list;
- struct sort_node * nodes;
- struct sort_node * next_nodes;
- int count = 0;
-
- /* determine the size of the list */
- while (next) {
- next = next->next;
- count++;
- }
+ struct commit_list *next, *orig = *list;
+ struct commit_list *work, **insert;
+ struct commit_list **pptr;
- if (!count)
+ if (!orig)
return;
- /* allocate an array to help sort the list */
- nodes = xcalloc(count, sizeof(*nodes));
- /* link the list to the array */
- next_nodes = nodes;
- next=*list;
- while (next) {
- next_nodes->list_item = next;
- setter(next->item, next_nodes);
- next_nodes++;
- next = next->next;
+ *list = NULL;
+
+ /* Mark them and clear the indegree */
+ for (next = orig; next; next = next->next) {
+ struct commit *commit = next->item;
+ commit->indegree = 1;
}
+
/* update the indegree */
- next=*list;
- while (next) {
+ for (next = orig; next; next = next->next) {
struct commit_list * parents = next->item->parents;
while (parents) {
- struct commit * parent=parents->item;
- struct sort_node * pn = (struct sort_node *) getter(parent);
+ struct commit *parent = parents->item;
- if (pn)
- pn->indegree++;
- parents=parents->next;
+ if (parent->indegree)
+ parent->indegree++;
+ parents = parents->next;
}
- next=next->next;
}
+
/*
* find the tips
*
*
* the tips serve as a starting set for the work queue.
*/
- next=*list;
+ work = NULL;
insert = &work;
- while (next) {
- struct sort_node * node = (struct sort_node *) getter(next->item);
+ for (next = orig; next; next = next->next) {
+ struct commit *commit = next->item;
- if (node->indegree == 0) {
- insert = &commit_list_insert(next->item, insert)->next;
- }
- next=next->next;
+ if (commit->indegree == 1)
+ insert = &commit_list_insert(commit, insert)->next;
}
/* process the list in topological order */
if (!lifo)
- sort_by_date(&work);
+ commit_list_sort_by_date(&work);
+
+ pptr = list;
+ *list = NULL;
while (work) {
- struct commit * work_item = pop_commit(&work);
- struct sort_node * work_node = (struct sort_node *) getter(work_item);
- struct commit_list * parents = work_item->parents;
+ struct commit *commit;
+ struct commit_list *parents, *work_item;
- while (parents) {
- struct commit * parent=parents->item;
- struct sort_node * pn = (struct sort_node *) getter(parent);
-
- if (pn) {
- /*
- * parents are only enqueued for emission
- * when all their children have been emitted thereby
- * guaranteeing topological order.
- */
- pn->indegree--;
- if (!pn->indegree) {
- if (!lifo)
- insert_by_date(parent, &work);
- else
- commit_list_insert(parent, &work);
- }
+ work_item = work;
+ work = work_item->next;
+ work_item->next = NULL;
+
+ commit = work_item->item;
+ for (parents = commit->parents; parents ; parents = parents->next) {
+ struct commit *parent = parents->item;
+
+ if (!parent->indegree)
+ continue;
+
+ /*
+ * parents are only enqueued for emission
+ * when all their children have been emitted thereby
+ * guaranteeing topological order.
+ */
+ if (--parent->indegree == 1) {
+ if (!lifo)
+ commit_list_insert_by_date(parent, &work);
+ else
+ commit_list_insert(parent, &work);
}
- parents=parents->next;
}
/*
* work_item is a commit all of whose children
* have already been emitted. we can emit it now.
*/
- *pptr = work_node->list_item;
- pptr = &(*pptr)->next;
- *pptr = NULL;
- setter(work_item, NULL);
+ commit->indegree = 0;
+ *pptr = work_item;
+ pptr = &work_item->next;
}
- free(nodes);
}
/* merge-base stuff */
return NULL;
}
-static struct commit_list *merge_bases(struct commit *one, struct commit *two)
+/* all input commits in one and twos[] must have been parsed! */
+static struct commit_list *paint_down_to_common(struct commit *one, int n, struct commit **twos)
{
struct commit_list *list = NULL;
struct commit_list *result = NULL;
-
- if (one == two)
- /* We do not mark this even with RESULT so we do not
- * have to clean it up.
- */
- return commit_list_insert(one, &result);
-
- parse_commit(one);
- parse_commit(two);
+ int i;
one->object.flags |= PARENT1;
- two->object.flags |= PARENT2;
- insert_by_date(one, &list);
- insert_by_date(two, &list);
+ commit_list_insert_by_date(one, &list);
+ if (!n)
+ return list;
+ for (i = 0; i < n; i++) {
+ twos[i]->object.flags |= PARENT2;
+ commit_list_insert_by_date(twos[i], &list);
+ }
while (interesting(list)) {
struct commit *commit;
struct commit_list *parents;
- struct commit_list *n;
+ struct commit_list *next;
int flags;
commit = list->item;
- n = list->next;
+ next = list->next;
free(list);
- list = n;
+ list = next;
flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
if (flags == (PARENT1 | PARENT2)) {
if (!(commit->object.flags & RESULT)) {
commit->object.flags |= RESULT;
- insert_by_date(commit, &result);
+ commit_list_insert_by_date(commit, &result);
}
/* Mark parents of a found merge stale */
flags |= STALE;
parents = parents->next;
if ((p->object.flags & flags) == flags)
continue;
- parse_commit(p);
+ if (parse_commit(p))
+ return NULL;
p->object.flags |= flags;
- insert_by_date(p, &list);
+ commit_list_insert_by_date(p, &list);
}
}
- /* Clean up the result to remove stale ones */
free_commit_list(list);
- list = result; result = NULL;
+ return result;
+}
+
+static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
+{
+ struct commit_list *list = NULL;
+ struct commit_list *result = NULL;
+ int i;
+
+ for (i = 0; i < n; i++) {
+ if (one == twos[i])
+ /*
+ * We do not mark this even with RESULT so we do not
+ * have to clean it up.
+ */
+ return commit_list_insert(one, &result);
+ }
+
+ if (parse_commit(one))
+ return NULL;
+ for (i = 0; i < n; i++) {
+ if (parse_commit(twos[i]))
+ return NULL;
+ }
+
+ list = paint_down_to_common(one, n, twos);
+
while (list) {
- struct commit_list *n = list->next;
+ struct commit_list *next = list->next;
if (!(list->item->object.flags & STALE))
- insert_by_date(list->item, &result);
+ commit_list_insert_by_date(list->item, &result);
free(list);
- list = n;
+ list = next;
}
return result;
}
-struct commit_list *get_merge_bases(struct commit *one,
- struct commit *two, int cleanup)
+struct commit_list *get_octopus_merge_bases(struct commit_list *in)
+{
+ struct commit_list *i, *j, *k, *ret = NULL;
+ struct commit_list **pptr = &ret;
+
+ for (i = in; i; i = i->next) {
+ if (!ret)
+ pptr = &commit_list_insert(i->item, pptr)->next;
+ else {
+ struct commit_list *new = NULL, *end = NULL;
+
+ for (j = ret; j; j = j->next) {
+ struct commit_list *bases;
+ bases = get_merge_bases(i->item, j->item, 1);
+ if (!new)
+ new = bases;
+ else
+ end->next = bases;
+ for (k = bases; k; k = k->next)
+ end = k;
+ }
+ ret = new;
+ }
+ }
+ return ret;
+}
+
+static int remove_redundant(struct commit **array, int cnt)
+{
+ /*
+ * Some commit in the array may be an ancestor of
+ * another commit. Move such commit to the end of
+ * the array, and return the number of commits that
+ * are independent from each other.
+ */
+ struct commit **work;
+ unsigned char *redundant;
+ int *filled_index;
+ int i, j, filled;
+
+ work = xcalloc(cnt, sizeof(*work));
+ redundant = xcalloc(cnt, 1);
+ filled_index = xmalloc(sizeof(*filled_index) * (cnt - 1));
+
+ for (i = 0; i < cnt; i++)
+ parse_commit(array[i]);
+ for (i = 0; i < cnt; i++) {
+ struct commit_list *common;
+
+ if (redundant[i])
+ continue;
+ for (j = filled = 0; j < cnt; j++) {
+ if (i == j || redundant[j])
+ continue;
+ filled_index[filled] = j;
+ work[filled++] = array[j];
+ }
+ common = paint_down_to_common(array[i], filled, work);
+ if (array[i]->object.flags & PARENT2)
+ redundant[i] = 1;
+ for (j = 0; j < filled; j++)
+ if (work[j]->object.flags & PARENT1)
+ redundant[filled_index[j]] = 1;
+ clear_commit_marks(array[i], all_flags);
+ for (j = 0; j < filled; j++)
+ clear_commit_marks(work[j], all_flags);
+ free_commit_list(common);
+ }
+
+ /* Now collect the result */
+ memcpy(work, array, sizeof(*array) * cnt);
+ for (i = filled = 0; i < cnt; i++)
+ if (!redundant[i])
+ array[filled++] = work[i];
+ for (j = filled, i = 0; i < cnt; i++)
+ if (redundant[i])
+ array[j++] = work[i];
+ free(work);
+ free(redundant);
+ free(filled_index);
+ return filled;
+}
+
+struct commit_list *get_merge_bases_many(struct commit *one,
+ int n,
+ struct commit **twos,
+ int cleanup)
{
struct commit_list *list;
struct commit **rslt;
struct commit_list *result;
- int cnt, i, j;
+ int cnt, i;
- result = merge_bases(one, two);
- if (one == two)
- return result;
+ result = merge_bases_many(one, n, twos);
+ for (i = 0; i < n; i++) {
+ if (one == twos[i])
+ return result;
+ }
if (!result || !result->next) {
if (cleanup) {
clear_commit_marks(one, all_flags);
- clear_commit_marks(two, all_flags);
+ for (i = 0; i < n; i++)
+ clear_commit_marks(twos[i], all_flags);
}
return result;
}
free_commit_list(result);
clear_commit_marks(one, all_flags);
- clear_commit_marks(two, all_flags);
- for (i = 0; i < cnt - 1; i++) {
- for (j = i+1; j < cnt; j++) {
- if (!rslt[i] || !rslt[j])
- continue;
- result = merge_bases(rslt[i], rslt[j]);
- clear_commit_marks(rslt[i], all_flags);
- clear_commit_marks(rslt[j], all_flags);
- for (list = result; list; list = list->next) {
- if (rslt[i] == list->item)
- rslt[i] = NULL;
- if (rslt[j] == list->item)
- rslt[j] = NULL;
- }
- }
- }
+ for (i = 0; i < n; i++)
+ clear_commit_marks(twos[i], all_flags);
- /* Surviving ones in rslt[] are the independent results */
+ cnt = remove_redundant(rslt, cnt);
result = NULL;
- for (i = 0; i < cnt; i++) {
- if (rslt[i])
- insert_by_date(rslt[i], &result);
- }
+ for (i = 0; i < cnt; i++)
+ commit_list_insert_by_date(rslt[i], &result);
free(rslt);
return result;
}
-int in_merge_bases(struct commit *commit, struct commit **reference, int num)
+struct commit_list *get_merge_bases(struct commit *one, struct commit *two,
+ int cleanup)
+{
+ return get_merge_bases_many(one, 1, &two, cleanup);
+}
+
+/*
+ * Is "commit" a decendant of one of the elements on the "with_commit" list?
+ */
+int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
+{
+ if (!with_commit)
+ return 1;
+ while (with_commit) {
+ struct commit *other;
+
+ other = with_commit->item;
+ with_commit = with_commit->next;
+ if (in_merge_bases(other, commit))
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Is "commit" an ancestor of (i.e. reachable from) the "reference"?
+ */
+int in_merge_bases(struct commit *commit, struct commit *reference)
{
- struct commit_list *bases, *b;
+ struct commit_list *bases;
int ret = 0;
- if (num == 1)
- bases = get_merge_bases(commit, *reference, 1);
+ if (parse_commit(commit) || parse_commit(reference))
+ return ret;
+
+ bases = paint_down_to_common(commit, 1, &reference);
+ if (commit->object.flags & PARENT2)
+ ret = 1;
+ clear_commit_marks(commit, all_flags);
+ clear_commit_marks(reference, all_flags);
+ free_commit_list(bases);
+ return ret;
+}
+
+struct commit_list *reduce_heads(struct commit_list *heads)
+{
+ struct commit_list *p;
+ struct commit_list *result = NULL, **tail = &result;
+ struct commit **array;
+ int num_head, i;
+
+ if (!heads)
+ return NULL;
+
+ /* Uniquify */
+ for (p = heads; p; p = p->next)
+ p->item->object.flags &= ~STALE;
+ for (p = heads, num_head = 0; p; p = p->next) {
+ if (p->item->object.flags & STALE)
+ continue;
+ p->item->object.flags |= STALE;
+ num_head++;
+ }
+ array = xcalloc(sizeof(*array), num_head);
+ for (p = heads, i = 0; p; p = p->next) {
+ if (p->item->object.flags & STALE) {
+ array[i++] = p->item;
+ p->item->object.flags &= ~STALE;
+ }
+ }
+ num_head = remove_redundant(array, num_head);
+ for (i = 0; i < num_head; i++)
+ tail = &commit_list_insert(array[i], tail)->next;
+ return result;
+}
+
+static const char gpg_sig_header[] = "gpgsig";
+static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
+
+static int do_sign_commit(struct strbuf *buf, const char *keyid)
+{
+ struct strbuf sig = STRBUF_INIT;
+ int inspos, copypos;
+
+ /* find the end of the header */
+ inspos = strstr(buf->buf, "\n\n") - buf->buf + 1;
+
+ if (!keyid || !*keyid)
+ keyid = get_signing_key();
+ if (sign_buffer(buf, &sig, keyid)) {
+ strbuf_release(&sig);
+ return -1;
+ }
+
+ for (copypos = 0; sig.buf[copypos]; ) {
+ const char *bol = sig.buf + copypos;
+ const char *eol = strchrnul(bol, '\n');
+ int len = (eol - bol) + !!*eol;
+
+ if (!copypos) {
+ strbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);
+ inspos += gpg_sig_header_len;
+ }
+ strbuf_insert(buf, inspos++, " ", 1);
+ strbuf_insert(buf, inspos, bol, len);
+ inspos += len;
+ copypos += len;
+ }
+ strbuf_release(&sig);
+ return 0;
+}
+
+int parse_signed_commit(const unsigned char *sha1,
+ struct strbuf *payload, struct strbuf *signature)
+{
+ unsigned long size;
+ enum object_type type;
+ char *buffer = read_sha1_file(sha1, &type, &size);
+ int in_signature, saw_signature = -1;
+ char *line, *tail;
+
+ if (!buffer || type != OBJ_COMMIT)
+ goto cleanup;
+
+ line = buffer;
+ tail = buffer + size;
+ in_signature = 0;
+ saw_signature = 0;
+ while (line < tail) {
+ const char *sig = NULL;
+ char *next = memchr(line, '\n', tail - line);
+
+ next = next ? next + 1 : tail;
+ if (in_signature && line[0] == ' ')
+ sig = line + 1;
+ else if (!prefixcmp(line, gpg_sig_header) &&
+ line[gpg_sig_header_len] == ' ')
+ sig = line + gpg_sig_header_len + 1;
+ if (sig) {
+ strbuf_add(signature, sig, next - sig);
+ saw_signature = 1;
+ in_signature = 1;
+ } else {
+ if (*line == '\n')
+ /* dump the whole remainder of the buffer */
+ next = tail;
+ strbuf_add(payload, line, next - line);
+ in_signature = 0;
+ }
+ line = next;
+ }
+ cleanup:
+ free(buffer);
+ return saw_signature;
+}
+
+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);
+ if (extra->len)
+ strbuf_add_lines(buffer, " ", extra->value, extra->len);
else
- die("not yet");
- for (b = bases; b; b = b->next) {
- if (!hashcmp(commit->object.sha1, b->item->object.sha1)) {
- ret = 1;
- break;
+ strbuf_addch(buffer, '\n');
+}
+
+struct commit_extra_header *read_commit_extra_headers(struct commit *commit,
+ const char **exclude)
+{
+ 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, exclude);
+ 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)));
+}
+
+static int excluded_header_field(const char *field, size_t len, const char **exclude)
+{
+ if (!exclude)
+ return 0;
+
+ while (*exclude) {
+ size_t xlen = strlen(*exclude);
+ if (len == xlen &&
+ !memcmp(field, *exclude, xlen) && field[xlen] == ' ')
+ return 1;
+ exclude++;
+ }
+ return 0;
+}
+
+static struct commit_extra_header *read_commit_extra_header_lines(
+ const char *buffer, size_t size,
+ const char **exclude)
+{
+ 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) ||
+ excluded_header_field(line, eof - line, exclude))
+ 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;
+}
- free_commit_list(bases);
- return ret;
+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 struct strbuf *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret,
+ const char *author, const char *sign_commit)
+{
+ 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, sign_commit, extra);
+ free_commit_extra_headers(extra);
+ return result;
+}
+
+static int find_invalid_utf8(const char *buf, int len)
+{
+ int offset = 0;
+
+ while (len) {
+ unsigned char c = *buf++;
+ int bytes, bad_offset;
+
+ len--;
+ offset++;
+
+ /* Simple US-ASCII? No worries. */
+ if (c < 0x80)
+ continue;
+
+ bad_offset = offset-1;
+
+ /*
+ * Count how many more high bits set: that's how
+ * many more bytes this sequence should have.
+ */
+ bytes = 0;
+ while (c & 0x40) {
+ c <<= 1;
+ bytes++;
+ }
+
+ /* Must be between 1 and 5 more bytes */
+ if (bytes < 1 || bytes > 5)
+ return bad_offset;
+
+ /* Do we *have* that many bytes? */
+ if (len < bytes)
+ return bad_offset;
+
+ offset += bytes;
+ len -= bytes;
+
+ /* And verify that they are good continuation bytes */
+ do {
+ if ((*buf++ & 0xc0) != 0x80)
+ return bad_offset;
+ } while (--bytes);
+
+ /* We could/should check the value and length here too */
+ }
+ return -1;
+}
+
+/*
+ * This verifies that the buffer is in proper utf8 format.
+ *
+ * If it isn't, it assumes any non-utf8 characters are Latin1,
+ * and does the conversion.
+ *
+ * Fixme: we should probably also disallow overlong forms and
+ * invalid characters. But we don't do that currently.
+ */
+static int verify_utf8(struct strbuf *buf)
+{
+ int ok = 1;
+ long pos = 0;
+
+ for (;;) {
+ int bad;
+ unsigned char c;
+ unsigned char replace[2];
+
+ bad = find_invalid_utf8(buf->buf + pos, buf->len - pos);
+ if (bad < 0)
+ return ok;
+ pos += bad;
+ ok = 0;
+ c = buf->buf[pos];
+ strbuf_remove(buf, pos, 1);
+
+ /* We know 'c' must be in the range 128-255 */
+ replace[0] = 0xc0 + (c >> 6);
+ replace[1] = 0x80 + (c & 0x3f);
+ strbuf_insert(buf, pos, replace, 2);
+ pos += 2;
+ }
+}
+
+static const char commit_utf8_warn[] =
+"Warning: commit message did 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_extended(const struct strbuf *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret,
+ const char *author, const char *sign_commit,
+ struct commit_extra_header *extra)
+{
+ int result;
+ int encoding_is_utf8;
+ struct strbuf buffer;
+
+ assert_sha1_type(tree, OBJ_TREE);
+
+ if (memchr(msg->buf, '\0', msg->len))
+ return error("a NUL byte in commit log message not allowed.");
+
+ /* Not having i18n.commitencoding is the same as having utf-8 */
+ encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
+
+ strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
+ strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
+
+ /*
+ * NOTE! This ordering means that the same exact tree merged with a
+ * different order of parents will be a _different_ changeset even
+ * if everything else stays the same.
+ */
+ while (parents) {
+ struct commit_list *next = parents->next;
+ struct commit *parent = parents->item;
+
+ strbuf_addf(&buffer, "parent %s\n",
+ sha1_to_hex(parent->object.sha1));
+ free(parents);
+ parents = next;
+ }
+
+ /* Person/date information */
+ if (!author)
+ author = git_author_info(IDENT_STRICT);
+ strbuf_addf(&buffer, "author %s\n", author);
+ strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_STRICT));
+ 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 */
+ strbuf_addbuf(&buffer, msg);
+
+ /* And check the encoding */
+ if (encoding_is_utf8 && !verify_utf8(&buffer))
+ fprintf(stderr, commit_utf8_warn);
+
+ if (sign_commit && do_sign_commit(&buffer, sign_commit))
+ return -1;
+
+ result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
+ 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;
+}
+
+/*
+ * 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;
+ */
+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;
+}
+
+void print_commit_list(struct commit_list *list,
+ const char *format_cur,
+ const char *format_last)
+{
+ for ( ; list; list = list->next) {
+ const char *format = list->next ? format_cur : format_last;
+ printf(format, sha1_to_hex(list->item->object.sha1));
+ }
}