diff options
Diffstat (limited to 'builtin')
36 files changed, 1589 insertions, 756 deletions
diff --git a/builtin/apply.c b/builtin/apply.c index 9c5724eacc..16cc93587f 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -1281,9 +1281,7 @@ static int parse_git_header(const char *line, int len, unsigned int size, struct */ patch->def_name = git_header_name(line, len); if (patch->def_name && root) { - char *s = xmalloc(root_len + strlen(patch->def_name) + 1); - strcpy(s, root); - strcpy(s + root_len, patch->def_name); + char *s = xstrfmt("%s%s", root, patch->def_name); free(patch->def_name); patch->def_name = s; } @@ -3847,9 +3845,10 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned ce->ce_flags = create_ce_flags(0); ce->ce_namelen = namelen; if (S_ISGITLINK(mode)) { - const char *s = buf; + const char *s; - if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1)) + if (!skip_prefix(buf, "Subproject commit ", &s) || + get_sha1_hex(s, ce->sha1)) die(_("corrupt patch for submodule %s"), path); } else { if (!cached) { diff --git a/builtin/blame.c b/builtin/blame.c index d8b276048e..d3b256e545 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -1,7 +1,8 @@ /* * Blame * - * Copyright (c) 2006, Junio C Hamano + * Copyright (c) 2006, 2014 by its authors + * See COPYING for licensing conditions */ #include "cache.h" @@ -18,7 +19,9 @@ #include "cache-tree.h" #include "string-list.h" #include "mailmap.h" +#include "mergesort.h" #include "parse-options.h" +#include "prio-queue.h" #include "utf8.h" #include "userdiff.h" #include "line-range.h" @@ -83,11 +86,42 @@ static unsigned blame_copy_score; */ struct origin { int refcnt; + /* Record preceding blame record for this blob */ struct origin *previous; + /* origins are put in a list linked via `next' hanging off the + * corresponding commit's util field in order to make finding + * them fast. The presence in this chain does not count + * towards the origin's reference count. It is tempting to + * let it count as long as the commit is pending examination, + * but even under circumstances where the commit will be + * present multiple times in the priority queue of unexamined + * commits, processing the first instance will not leave any + * work requiring the origin data for the second instance. An + * interspersed commit changing that would have to be + * preexisting with a different ancestry and with the same + * commit date in order to wedge itself between two instances + * of the same commit in the priority queue _and_ produce + * blame entries relevant for it. While we don't want to let + * us get tripped up by this case, it certainly does not seem + * worth optimizing for. + */ + struct origin *next; struct commit *commit; + /* `suspects' contains blame entries that may be attributed to + * this origin's commit or to parent commits. When a commit + * is being processed, all suspects will be moved, either by + * assigning them to an origin in a different commit, or by + * shipping them to the scoreboard's ent list because they + * cannot be attributed to a different commit. + */ + struct blame_entry *suspects; mmfile_t file; unsigned char blob_sha1[20]; unsigned mode; + /* guilty gets set when shipping any suspects to the final + * blame list instead of other commits + */ + char guilty; char path[FLEX_ARRAY]; }; @@ -176,10 +210,22 @@ static inline struct origin *origin_incref(struct origin *o) static void origin_decref(struct origin *o) { if (o && --o->refcnt <= 0) { + struct origin *p, *l = NULL; if (o->previous) origin_decref(o->previous); free(o->file.ptr); - free(o); + /* Should be present exactly once in commit chain */ + for (p = o->commit->util; p; l = p, p = p->next) { + if (p == o) { + if (l) + l->next = p->next; + else + o->commit->util = p->next; + free(o); + return; + } + } + die("internal error in blame::origin_decref"); } } @@ -193,8 +239,12 @@ static void drop_origin_blob(struct origin *o) /* * Each group of lines is described by a blame_entry; it can be split - * as we pass blame to the parents. They form a linked list in the - * scoreboard structure, sorted by the target line number. + * as we pass blame to the parents. They are arranged in linked lists + * kept as `suspects' of some unprocessed origin, or entered (when the + * blame origin has been finalized) into the scoreboard structure. + * While the scoreboard structure is only sorted at the end of + * processing (according to final image line number), the lists + * attached to an origin are sorted by the target line number. */ struct blame_entry { struct blame_entry *next; @@ -210,15 +260,6 @@ struct blame_entry { /* the commit that introduced this group into the final image */ struct origin *suspect; - /* true if the suspect is truly guilty; false while we have not - * checked if the group came from one of its parents. - */ - char guilty; - - /* true if the entry has been scanned for copies in the current parent - */ - char scanned; - /* the line number of the first line of this group in the * suspect's file; internally all line numbers are 0 based. */ @@ -231,11 +272,112 @@ struct blame_entry { }; /* + * Any merge of blames happens on lists of blames that arrived via + * different parents in a single suspect. In this case, we want to + * sort according to the suspect line numbers as opposed to the final + * image line numbers. The function body is somewhat longish because + * it avoids unnecessary writes. + */ + +static struct blame_entry *blame_merge(struct blame_entry *list1, + struct blame_entry *list2) +{ + struct blame_entry *p1 = list1, *p2 = list2, + **tail = &list1; + + if (!p1) + return p2; + if (!p2) + return p1; + + if (p1->s_lno <= p2->s_lno) { + do { + tail = &p1->next; + if ((p1 = *tail) == NULL) { + *tail = p2; + return list1; + } + } while (p1->s_lno <= p2->s_lno); + } + for (;;) { + *tail = p2; + do { + tail = &p2->next; + if ((p2 = *tail) == NULL) { + *tail = p1; + return list1; + } + } while (p1->s_lno > p2->s_lno); + *tail = p1; + do { + tail = &p1->next; + if ((p1 = *tail) == NULL) { + *tail = p2; + return list1; + } + } while (p1->s_lno <= p2->s_lno); + } +} + +static void *get_next_blame(const void *p) +{ + return ((struct blame_entry *)p)->next; +} + +static void set_next_blame(void *p1, void *p2) +{ + ((struct blame_entry *)p1)->next = p2; +} + +/* + * Final image line numbers are all different, so we don't need a + * three-way comparison here. + */ + +static int compare_blame_final(const void *p1, const void *p2) +{ + return ((struct blame_entry *)p1)->lno > ((struct blame_entry *)p2)->lno + ? 1 : -1; +} + +static int compare_blame_suspect(const void *p1, const void *p2) +{ + const struct blame_entry *s1 = p1, *s2 = p2; + /* + * to allow for collating suspects, we sort according to the + * respective pointer value as the primary sorting criterion. + * The actual relation is pretty unimportant as long as it + * establishes a total order. Comparing as integers gives us + * that. + */ + if (s1->suspect != s2->suspect) + return (intptr_t)s1->suspect > (intptr_t)s2->suspect ? 1 : -1; + if (s1->s_lno == s2->s_lno) + return 0; + return s1->s_lno > s2->s_lno ? 1 : -1; +} + +static struct blame_entry *blame_sort(struct blame_entry *head, + int (*compare_fn)(const void *, const void *)) +{ + return llist_mergesort (head, get_next_blame, set_next_blame, compare_fn); +} + +static int compare_commits_by_reverse_commit_date(const void *a, + const void *b, + void *c) +{ + return -compare_commits_by_commit_date(a, b, c); +} + +/* * The current state of the blame assignment. */ struct scoreboard { /* the final commit (i.e. where we started digging from) */ struct commit *final; + /* Priority queue for commits with unassigned blame records */ + struct prio_queue commits; struct rev_info *revs; const char *path; @@ -268,7 +410,6 @@ static void coalesce(struct scoreboard *sb) for (ent = sb->ent; ent && (next = ent->next); ent = next) { if (ent->suspect == next->suspect && - ent->guilty == next->guilty && ent->s_lno + ent->num_lines == next->s_lno) { ent->num_lines += next->num_lines; ent->next = next->next; @@ -284,6 +425,30 @@ static void coalesce(struct scoreboard *sb) } /* + * Merge the given sorted list of blames into a preexisting origin. + * If there were no previous blames to that commit, it is entered into + * the commit priority queue of the score board. + */ + +static void queue_blames(struct scoreboard *sb, struct origin *porigin, + struct blame_entry *sorted) +{ + if (porigin->suspects) + porigin->suspects = blame_merge(porigin->suspects, sorted); + else { + struct origin *o; + for (o = porigin->commit->util; o; o = o->next) { + if (o->suspects) { + porigin->suspects = sorted; + return; + } + } + porigin->suspects = sorted; + prio_queue_put(&sb->commits, porigin->commit); + } +} + +/* * Given a commit and a path in it, create a new origin structure. * The callers that add blame to the scoreboard should use * get_origin() to obtain shared, refcounted copy instead of calling @@ -295,23 +460,32 @@ static struct origin *make_origin(struct commit *commit, const char *path) o = xcalloc(1, sizeof(*o) + strlen(path) + 1); o->commit = commit; o->refcnt = 1; + o->next = commit->util; + commit->util = o; strcpy(o->path, path); return o; } /* * Locate an existing origin or create a new one. + * This moves the origin to front position in the commit util list. */ static struct origin *get_origin(struct scoreboard *sb, struct commit *commit, const char *path) { - struct blame_entry *e; + struct origin *o, *l; - for (e = sb->ent; e; e = e->next) { - if (e->suspect->commit == commit && - !strcmp(e->suspect->path, path)) - return origin_incref(e->suspect); + for (o = commit->util, l = NULL; o; l = o, o = o->next) { + if (!strcmp(o->path, path)) { + /* bump to front */ + if (l) { + l->next = o->next; + o->next = commit->util; + commit->util = o; + } + return origin_incref(o); + } } return make_origin(commit, path); } @@ -350,41 +524,19 @@ static struct origin *find_origin(struct scoreboard *sb, struct commit *parent, struct origin *origin) { - struct origin *porigin = NULL; + struct origin *porigin; struct diff_options diff_opts; const char *paths[2]; - if (parent->util) { - /* - * Each commit object can cache one origin in that - * commit. This is a freestanding copy of origin and - * not refcounted. - */ - struct origin *cached = parent->util; - if (!strcmp(cached->path, origin->path)) { + /* First check any existing origins */ + for (porigin = parent->util; porigin; porigin = porigin->next) + if (!strcmp(porigin->path, origin->path)) { /* * The same path between origin and its parent * without renaming -- the most common case. */ - porigin = get_origin(sb, parent, cached->path); - - /* - * If the origin was newly created (i.e. get_origin - * would call make_origin if none is found in the - * scoreboard), it does not know the blob_sha1/mode, - * so copy it. Otherwise porigin was in the - * scoreboard and already knows blob_sha1/mode. - */ - if (porigin->refcnt == 1) { - hashcpy(porigin->blob_sha1, cached->blob_sha1); - porigin->mode = cached->mode; - } - return porigin; + return origin_incref (porigin); } - /* otherwise it was not very useful; free it */ - free(parent->util); - parent->util = NULL; - } /* See if the origin->path is different between parent * and origin first. Most of the time they are the @@ -450,19 +602,6 @@ static struct origin *find_origin(struct scoreboard *sb, } diff_flush(&diff_opts); free_pathspec(&diff_opts.pathspec); - if (porigin) { - /* - * Create a freestanding copy that is not part of - * the refcounted origin found in the scoreboard, and - * cache it in the commit. - */ - struct origin *cached; - - cached = make_origin(porigin->commit, porigin->path); - hashcpy(cached->blob_sha1, porigin->blob_sha1); - cached->mode = porigin->mode; - parent->util = cached; - } return porigin; } @@ -509,46 +648,31 @@ static struct origin *find_rename(struct scoreboard *sb, } /* - * Link in a new blame entry to the scoreboard. Entries that cover the - * same line range have been removed from the scoreboard previously. + * Append a new blame entry to a given output queue. */ -static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e) +static void add_blame_entry(struct blame_entry ***queue, struct blame_entry *e) { - struct blame_entry *ent, *prev = NULL; - origin_incref(e->suspect); - for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next) - prev = ent; - - /* prev, if not NULL, is the last one that is below e */ - - if (prev) { - e->next = prev->next; - prev->next = e; - } - else { - e->next = sb->ent; - sb->ent = e; - } + e->next = **queue; + **queue = e; + *queue = &e->next; } /* * src typically is on-stack; we want to copy the information in it to - * a malloced blame_entry that is already on the linked list of the - * scoreboard. The origin of dst loses a refcnt while the origin of src - * gains one. + * a malloced blame_entry that gets added to the given queue. The + * origin of dst loses a refcnt. */ -static void dup_entry(struct blame_entry *dst, struct blame_entry *src) +static void dup_entry(struct blame_entry ***queue, + struct blame_entry *dst, struct blame_entry *src) { - struct blame_entry *n; - - n = dst->next; origin_incref(src->suspect); origin_decref(dst->suspect); memcpy(dst, src, sizeof(*src)); - dst->next = n; - dst->score = 0; + dst->next = **queue; + **queue = dst; + *queue = &dst->next; } static const char *nth_line(struct scoreboard *sb, long lno) @@ -620,10 +744,11 @@ static void split_overlap(struct blame_entry *split, /* * split_overlap() divided an existing blame e into up to three parts - * in split. Adjust the linked list of blames in the scoreboard to + * in split. Any assigned blame is moved to queue to * reflect the split. */ -static void split_blame(struct scoreboard *sb, +static void split_blame(struct blame_entry ***blamed, + struct blame_entry ***unblamed, struct blame_entry *split, struct blame_entry *e) { @@ -631,61 +756,39 @@ static void split_blame(struct scoreboard *sb, if (split[0].suspect && split[2].suspect) { /* The first part (reuse storage for the existing entry e) */ - dup_entry(e, &split[0]); + dup_entry(unblamed, e, &split[0]); /* The last part -- me */ new_entry = xmalloc(sizeof(*new_entry)); memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); + add_blame_entry(unblamed, new_entry); /* ... and the middle part -- parent */ new_entry = xmalloc(sizeof(*new_entry)); memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); + add_blame_entry(blamed, new_entry); } else if (!split[0].suspect && !split[2].suspect) /* * The parent covers the entire area; reuse storage for * e and replace it with the parent. */ - dup_entry(e, &split[1]); + dup_entry(blamed, e, &split[1]); else if (split[0].suspect) { /* me and then parent */ - dup_entry(e, &split[0]); + dup_entry(unblamed, e, &split[0]); new_entry = xmalloc(sizeof(*new_entry)); memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); + add_blame_entry(blamed, new_entry); } else { /* parent and then me */ - dup_entry(e, &split[1]); + dup_entry(blamed, e, &split[1]); new_entry = xmalloc(sizeof(*new_entry)); memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - } - - if (DEBUG) { /* sanity */ - struct blame_entry *ent; - int lno = sb->ent->lno, corrupt = 0; - - for (ent = sb->ent; ent; ent = ent->next) { - if (lno != ent->lno) - corrupt = 1; - if (ent->s_lno < 0) - corrupt = 1; - lno += ent->num_lines; - } - if (corrupt) { - lno = sb->ent->lno; - for (ent = sb->ent; ent; ent = ent->next) { - printf("L %8d l %8d n %8d\n", - lno, ent->lno, ent->num_lines); - lno = ent->lno + ent->num_lines; - } - die("oops"); - } + add_blame_entry(unblamed, new_entry); } } @@ -702,74 +805,146 @@ static void decref_split(struct blame_entry *split) } /* - * Helper for blame_chunk(). blame_entry e is known to overlap with - * the patch hunk; split it and pass blame to the parent. + * reverse_blame reverses the list given in head, appending tail. + * That allows us to build lists in reverse order, then reverse them + * afterwards. This can be faster than building the list in proper + * order right away. The reason is that building in proper order + * requires writing a link in the _previous_ element, while building + * in reverse order just requires placing the list head into the + * _current_ element. */ -static void blame_overlap(struct scoreboard *sb, struct blame_entry *e, - int tlno, int plno, int same, - struct origin *parent) -{ - struct blame_entry split[3]; - - split_overlap(split, e, tlno, plno, same, parent); - if (split[1].suspect) - split_blame(sb, split, e); - decref_split(split); -} -/* - * Find the line number of the last line the target is suspected for. - */ -static int find_last_in_target(struct scoreboard *sb, struct origin *target) +static struct blame_entry *reverse_blame(struct blame_entry *head, + struct blame_entry *tail) { - struct blame_entry *e; - int last_in_target = -1; - - for (e = sb->ent; e; e = e->next) { - if (e->guilty || e->suspect != target) - continue; - if (last_in_target < e->s_lno + e->num_lines) - last_in_target = e->s_lno + e->num_lines; + while (head) { + struct blame_entry *next = head->next; + head->next = tail; + tail = head; + head = next; } - return last_in_target; + return tail; } /* * Process one hunk from the patch between the current suspect for - * blame_entry e and its parent. Find and split the overlap, and - * pass blame to the overlapping part to the parent. + * blame_entry e and its parent. This first blames any unfinished + * entries before the chunk (which is where target and parent start + * differing) on the parent, and then splits blame entries at the + * start and at the end of the difference region. Since use of -M and + * -C options may lead to overlapping/duplicate source line number + * ranges, all we can rely on from sorting/merging is the order of the + * first suspect line number. */ -static void blame_chunk(struct scoreboard *sb, - int tlno, int plno, int same, - struct origin *target, struct origin *parent) +static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq, + int tlno, int offset, int same, + struct origin *parent) { - struct blame_entry *e; + struct blame_entry *e = **srcq; + struct blame_entry *samep = NULL, *diffp = NULL; - for (e = sb->ent; e; e = e->next) { - if (e->guilty || e->suspect != target) - continue; - if (same <= e->s_lno) - continue; - if (tlno < e->s_lno + e->num_lines) - blame_overlap(sb, e, tlno, plno, same, parent); + while (e && e->s_lno < tlno) { + struct blame_entry *next = e->next; + /* + * current record starts before differing portion. If + * it reaches into it, we need to split it up and + * examine the second part separately. + */ + if (e->s_lno + e->num_lines > tlno) { + /* Move second half to a new record */ + int len = tlno - e->s_lno; + struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry)); + n->suspect = e->suspect; + n->lno = e->lno + len; + n->s_lno = e->s_lno + len; + n->num_lines = e->num_lines - len; + e->num_lines = len; + e->score = 0; + /* Push new record to diffp */ + n->next = diffp; + diffp = n; + } else + origin_decref(e->suspect); + /* Pass blame for everything before the differing + * chunk to the parent */ + e->suspect = origin_incref(parent); + e->s_lno += offset; + e->next = samep; + samep = e; + e = next; + } + /* + * As we don't know how much of a common stretch after this + * diff will occur, the currently blamed parts are all that we + * can assign to the parent for now. + */ + + if (samep) { + **dstq = reverse_blame(samep, **dstq); + *dstq = &samep->next; + } + /* + * Prepend the split off portions: everything after e starts + * after the blameable portion. + */ + e = reverse_blame(diffp, e); + + /* + * Now retain records on the target while parts are different + * from the parent. + */ + samep = NULL; + diffp = NULL; + while (e && e->s_lno < same) { + struct blame_entry *next = e->next; + + /* + * If current record extends into sameness, need to split. + */ + if (e->s_lno + e->num_lines > same) { + /* + * Move second half to a new record to be + * processed by later chunks + */ + int len = same - e->s_lno; + struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry)); + n->suspect = origin_incref(e->suspect); + n->lno = e->lno + len; + n->s_lno = e->s_lno + len; + n->num_lines = e->num_lines - len; + e->num_lines = len; + e->score = 0; + /* Push new record to samep */ + n->next = samep; + samep = n; + } + e->next = diffp; + diffp = e; + e = next; } + **srcq = reverse_blame(diffp, reverse_blame(samep, e)); + /* Move across elements that are in the unblamable portion */ + if (diffp) + *srcq = &diffp->next; } struct blame_chunk_cb_data { - struct scoreboard *sb; - struct origin *target; struct origin *parent; - long plno; - long tlno; + long offset; + struct blame_entry **dstq; + struct blame_entry **srcq; }; +/* diff chunks are from parent to target */ static int blame_chunk_cb(long start_a, long count_a, long start_b, long count_b, void *data) { struct blame_chunk_cb_data *d = data; - blame_chunk(d->sb, d->tlno, d->plno, start_b, d->target, d->parent); - d->plno = start_a + count_a; - d->tlno = start_b + count_b; + if (start_a - start_b != d->offset) + die("internal error in blame::blame_chunk_cb"); + blame_chunk(&d->dstq, &d->srcq, start_b, start_a - start_b, + start_b + count_b, d->parent); + d->offset = start_a + count_a - (start_b + count_b); return 0; } @@ -778,29 +953,32 @@ static int blame_chunk_cb(long start_a, long count_a, * for the lines it is suspected to its parent. Run diff to find * which lines came from parent and pass blame for them. */ -static int pass_blame_to_parent(struct scoreboard *sb, - struct origin *target, - struct origin *parent) +static void pass_blame_to_parent(struct scoreboard *sb, + struct origin *target, + struct origin *parent) { - int last_in_target; mmfile_t file_p, file_o; struct blame_chunk_cb_data d; + struct blame_entry *newdest = NULL; - memset(&d, 0, sizeof(d)); - d.sb = sb; d.target = target; d.parent = parent; - last_in_target = find_last_in_target(sb, target); - if (last_in_target < 0) - return 1; /* nothing remains for this target */ + if (!target->suspects) + return; /* nothing remains for this target */ + + d.parent = parent; + d.offset = 0; + d.dstq = &newdest; d.srcq = &target->suspects; fill_origin_blob(&sb->revs->diffopt, parent, &file_p); fill_origin_blob(&sb->revs->diffopt, target, &file_o); num_get_patch++; diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d); - /* The rest (i.e. anything after tlno) are the same as the parent */ - blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent); + /* The rest are the same as the parent */ + blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent); + *d.dstq = NULL; + queue_blames(sb, parent, newdest); - return 0; + return; } /* @@ -945,43 +1123,80 @@ static void find_copy_in_blob(struct scoreboard *sb, handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split); } +/* Move all blame entries from list *source that have a score smaller + * than score_min to the front of list *small. + * Returns a pointer to the link pointing to the old head of the small list. + */ + +static struct blame_entry **filter_small(struct scoreboard *sb, + struct blame_entry **small, + struct blame_entry **source, + unsigned score_min) +{ + struct blame_entry *p = *source; + struct blame_entry *oldsmall = *small; + while (p) { + if (ent_score(sb, p) <= score_min) { + *small = p; + small = &p->next; + p = *small; + } else { + *source = p; + source = &p->next; + p = *source; + } + } + *small = oldsmall; + *source = NULL; + return small; +} + /* * See if lines currently target is suspected for can be attributed to * parent. */ -static int find_move_in_parent(struct scoreboard *sb, - struct origin *target, - struct origin *parent) +static void find_move_in_parent(struct scoreboard *sb, + struct blame_entry ***blamed, + struct blame_entry **toosmall, + struct origin *target, + struct origin *parent) { - int last_in_target, made_progress; struct blame_entry *e, split[3]; + struct blame_entry *unblamed = target->suspects; + struct blame_entry *leftover = NULL; mmfile_t file_p; - last_in_target = find_last_in_target(sb, target); - if (last_in_target < 0) - return 1; /* nothing remains for this target */ + if (!unblamed) + return; /* nothing remains for this target */ fill_origin_blob(&sb->revs->diffopt, parent, &file_p); if (!file_p.ptr) - return 0; + return; - made_progress = 1; - while (made_progress) { - made_progress = 0; - for (e = sb->ent; e; e = e->next) { - if (e->guilty || e->suspect != target || - ent_score(sb, e) < blame_move_score) - continue; + /* At each iteration, unblamed has a NULL-terminated list of + * entries that have not yet been tested for blame. leftover + * contains the reversed list of entries that have been tested + * without being assignable to the parent. + */ + do { + struct blame_entry **unblamedtail = &unblamed; + struct blame_entry *next; + for (e = unblamed; e; e = next) { + next = e->next; find_copy_in_blob(sb, e, parent, split, &file_p); if (split[1].suspect && blame_move_score < ent_score(sb, &split[1])) { - split_blame(sb, split, e); - made_progress = 1; + split_blame(blamed, &unblamedtail, split, e); + } else { + e->next = leftover; + leftover = e; } decref_split(split); } - } - return 0; + *unblamedtail = NULL; + toosmall = filter_small(sb, toosmall, &unblamed, blame_move_score); + } while (unblamed); + target->suspects = reverse_blame(leftover, NULL); } struct blame_list { @@ -993,62 +1208,46 @@ struct blame_list { * Count the number of entries the target is suspected for, * and prepare a list of entry and the best split. */ -static struct blame_list *setup_blame_list(struct scoreboard *sb, - struct origin *target, - int min_score, +static struct blame_list *setup_blame_list(struct blame_entry *unblamed, int *num_ents_p) { struct blame_entry *e; int num_ents, i; struct blame_list *blame_list = NULL; - for (e = sb->ent, num_ents = 0; e; e = e->next) - if (!e->scanned && !e->guilty && - e->suspect == target && - min_score < ent_score(sb, e)) - num_ents++; + for (e = unblamed, num_ents = 0; e; e = e->next) + num_ents++; if (num_ents) { blame_list = xcalloc(num_ents, sizeof(struct blame_list)); - for (e = sb->ent, i = 0; e; e = e->next) - if (!e->scanned && !e->guilty && - e->suspect == target && - min_score < ent_score(sb, e)) - blame_list[i++].ent = e; + for (e = unblamed, i = 0; e; e = e->next) + blame_list[i++].ent = e; } *num_ents_p = num_ents; return blame_list; } /* - * Reset the scanned status on all entries. - */ -static void reset_scanned_flag(struct scoreboard *sb) -{ - struct blame_entry *e; - for (e = sb->ent; e; e = e->next) - e->scanned = 0; -} - -/* * For lines target is suspected for, see if we can find code movement * across file boundary from the parent commit. porigin is the path * in the parent we already tried. */ -static int find_copy_in_parent(struct scoreboard *sb, - struct origin *target, - struct commit *parent, - struct origin *porigin, - int opt) +static void find_copy_in_parent(struct scoreboard *sb, + struct blame_entry ***blamed, + struct blame_entry **toosmall, + struct origin *target, + struct commit *parent, + struct origin *porigin, + int opt) { struct diff_options diff_opts; int i, j; - int retval; struct blame_list *blame_list; int num_ents; + struct blame_entry *unblamed = target->suspects; + struct blame_entry *leftover = NULL; - blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents); - if (!blame_list) - return 1; /* nothing remains for this target */ + if (!unblamed) + return; /* nothing remains for this target */ diff_setup(&diff_opts); DIFF_OPT_SET(&diff_opts, RECURSIVE); @@ -1078,9 +1277,9 @@ static int find_copy_in_parent(struct scoreboard *sb, if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER)) diffcore_std(&diff_opts); - retval = 0; - while (1) { - int made_progress = 0; + do { + struct blame_entry **unblamedtail = &unblamed; + blame_list = setup_blame_list(unblamed, &num_ents); for (i = 0; i < diff_queued_diff.nr; i++) { struct diff_filepair *p = diff_queued_diff.queue[i]; @@ -1117,27 +1316,21 @@ static int find_copy_in_parent(struct scoreboard *sb, struct blame_entry *split = blame_list[j].split; if (split[1].suspect && blame_copy_score < ent_score(sb, &split[1])) { - split_blame(sb, split, blame_list[j].ent); - made_progress = 1; + split_blame(blamed, &unblamedtail, split, + blame_list[j].ent); + } else { + blame_list[j].ent->next = leftover; + leftover = blame_list[j].ent; } - else - blame_list[j].ent->scanned = 1; decref_split(split); } free(blame_list); - - if (!made_progress) - break; - blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents); - if (!blame_list) { - retval = 1; - break; - } - } - reset_scanned_flag(sb); + *unblamedtail = NULL; + toosmall = filter_small(sb, toosmall, &unblamed, blame_copy_score); + } while (unblamed); + target->suspects = reverse_blame(leftover, NULL); diff_flush(&diff_opts); free_pathspec(&diff_opts.pathspec); - return retval; } /* @@ -1147,20 +1340,21 @@ static int find_copy_in_parent(struct scoreboard *sb, static void pass_whole_blame(struct scoreboard *sb, struct origin *origin, struct origin *porigin) { - struct blame_entry *e; + struct blame_entry *e, *suspects; if (!porigin->file.ptr && origin->file.ptr) { /* Steal its file */ porigin->file = origin->file; origin->file.ptr = NULL; } - for (e = sb->ent; e; e = e->next) { - if (e->suspect != origin) - continue; + suspects = origin->suspects; + origin->suspects = NULL; + for (e = suspects; e; e = e->next) { origin_incref(porigin); origin_decref(e->suspect); e->suspect = porigin; } + queue_blames(sb, porigin, suspects); } /* @@ -1184,6 +1378,27 @@ static int num_scapegoats(struct rev_info *revs, struct commit *commit) return cnt; } +/* Distribute collected unsorted blames to the respected sorted lists + * in the various origins. + */ +static void distribute_blame(struct scoreboard *sb, struct blame_entry *blamed) +{ + blamed = blame_sort(blamed, compare_blame_suspect); + while (blamed) + { + struct origin *porigin = blamed->suspect; + struct blame_entry *suspects = NULL; + do { + struct blame_entry *next = blamed->next; + blamed->next = suspects; + suspects = blamed; + blamed = next; + } while (blamed && blamed->suspect == porigin); + suspects = reverse_blame(suspects, NULL); + queue_blames(sb, porigin, suspects); + } +} + #define MAXSG 16 static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) @@ -1194,6 +1409,8 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) struct commit_list *sg; struct origin *sg_buf[MAXSG]; struct origin *porigin, **sg_origin = sg_buf; + struct blame_entry *toosmall = NULL; + struct blame_entry *blames, **blametail = &blames; num_sg = num_scapegoats(revs, commit); if (!num_sg) @@ -1255,38 +1472,71 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) origin_incref(porigin); origin->previous = porigin; } - if (pass_blame_to_parent(sb, origin, porigin)) + pass_blame_to_parent(sb, origin, porigin); + if (!origin->suspects) goto finish; } /* * Optionally find moves in parents' files. */ - if (opt & PICKAXE_BLAME_MOVE) - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct origin *porigin = sg_origin[i]; - if (!porigin) - continue; - if (find_move_in_parent(sb, origin, porigin)) - goto finish; + if (opt & PICKAXE_BLAME_MOVE) { + filter_small(sb, &toosmall, &origin->suspects, blame_move_score); + if (origin->suspects) { + for (i = 0, sg = first_scapegoat(revs, commit); + i < num_sg && sg; + sg = sg->next, i++) { + struct origin *porigin = sg_origin[i]; + if (!porigin) + continue; + find_move_in_parent(sb, &blametail, &toosmall, origin, porigin); + if (!origin->suspects) + break; + } } + } /* * Optionally find copies from parents' files. */ - if (opt & PICKAXE_BLAME_COPY) + if (opt & PICKAXE_BLAME_COPY) { + if (blame_copy_score > blame_move_score) + filter_small(sb, &toosmall, &origin->suspects, blame_copy_score); + else if (blame_copy_score < blame_move_score) { + origin->suspects = blame_merge(origin->suspects, toosmall); + toosmall = NULL; + filter_small(sb, &toosmall, &origin->suspects, blame_copy_score); + } + if (!origin->suspects) + goto finish; + for (i = 0, sg = first_scapegoat(revs, commit); i < num_sg && sg; sg = sg->next, i++) { struct origin *porigin = sg_origin[i]; - if (find_copy_in_parent(sb, origin, sg->item, - porigin, opt)) + find_copy_in_parent(sb, &blametail, &toosmall, + origin, sg->item, porigin, opt); + if (!origin->suspects) goto finish; } + } - finish: +finish: + *blametail = NULL; + distribute_blame(sb, blames); + /* + * prepend toosmall to origin->suspects + * + * There is no point in sorting: this ends up on a big + * unsorted list in the caller anyway. + */ + if (toosmall) { + struct blame_entry **tail = &toosmall; + while (*tail) + tail = &(*tail)->next; + *tail = origin->suspects; + origin->suspects = toosmall; + } for (i = 0; i < num_sg; i++) { if (sg_origin[i]) { drop_origin_blob(sg_origin[i]); @@ -1405,7 +1655,7 @@ static void get_commit_info(struct commit *commit, { int len; const char *subject, *encoding; - char *message; + const char *message; commit_info_init(ret); @@ -1416,7 +1666,7 @@ static void get_commit_info(struct commit *commit, &ret->author_time, &ret->author_tz); if (!detailed) { - logmsg_free(message, commit); + unuse_commit_buffer(commit, message); return; } @@ -1430,7 +1680,7 @@ static void get_commit_info(struct commit *commit, else strbuf_addf(&ret->summary, "(%s)", sha1_to_hex(commit->object.sha1)); - logmsg_free(message, commit); + unuse_commit_buffer(commit, message); } /* @@ -1481,14 +1731,11 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) } /* - * The blame_entry is found to be guilty for the range. Mark it - * as such, and show it in incremental output. + * The blame_entry is found to be guilty for the range. + * Show it in incremental output. */ static void found_guilty_entry(struct blame_entry *ent) { - if (ent->guilty) - return; - ent->guilty = 1; if (incremental) { struct origin *suspect = ent->suspect; @@ -1502,32 +1749,34 @@ static void found_guilty_entry(struct blame_entry *ent) } /* - * The main loop -- while the scoreboard has lines whose true origin - * is still unknown, pick one blame_entry, and allow its current - * suspect to pass blames to its parents. - */ + * The main loop -- while we have blobs with lines whose true origin + * is still unknown, pick one blob, and allow its lines to pass blames + * to its parents. */ static void assign_blame(struct scoreboard *sb, int opt) { struct rev_info *revs = sb->revs; + struct commit *commit = prio_queue_get(&sb->commits); - while (1) { + while (commit) { struct blame_entry *ent; - struct commit *commit; - struct origin *suspect = NULL; + struct origin *suspect = commit->util; /* find one suspect to break down */ - for (ent = sb->ent; !suspect && ent; ent = ent->next) - if (!ent->guilty) - suspect = ent->suspect; - if (!suspect) - return; /* all done */ + while (suspect && !suspect->suspects) + suspect = suspect->next; + + if (!suspect) { + commit = prio_queue_get(&sb->commits); + continue; + } + + assert(commit == suspect->commit); /* * We will use this suspect later in the loop, * so hold onto it in the meantime. */ origin_incref(suspect); - commit = suspect->commit; parse_commit(commit); if (reverse || (!(commit->object.flags & UNINTERESTING) && @@ -1543,9 +1792,22 @@ static void assign_blame(struct scoreboard *sb, int opt) commit->object.flags |= UNINTERESTING; /* Take responsibility for the remaining entries */ - for (ent = sb->ent; ent; ent = ent->next) - if (ent->suspect == suspect) + ent = suspect->suspects; + if (ent) { + suspect->guilty = 1; + for (;;) { + struct blame_entry *next = ent->next; found_guilty_entry(ent); + if (next) { + ent = next; + continue; + } + ent->next = sb->ent; + sb->ent = suspect->suspects; + suspect->suspects = NULL; + break; + } + } origin_decref(suspect); if (DEBUG) /* sanity */ @@ -1609,9 +1871,8 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, char hex[41]; strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); - printf("%s%c%d %d %d\n", + printf("%s %d %d %d\n", hex, - ent->guilty ? ' ' : '*', /* purely for debugging */ ent->s_lno + 1, ent->lno + 1, ent->num_lines); @@ -1724,17 +1985,16 @@ static void output(struct scoreboard *sb, int option) if (option & OUTPUT_PORCELAIN) { for (ent = sb->ent; ent; ent = ent->next) { - struct blame_entry *oth; - struct origin *suspect = ent->suspect; - struct commit *commit = suspect->commit; + int count = 0; + struct origin *suspect; + struct commit *commit = ent->suspect->commit; if (commit->object.flags & MORE_THAN_ONE_PATH) continue; - for (oth = ent->next; oth; oth = oth->next) { - if ((oth->suspect->commit != commit) || - !strcmp(oth->suspect->path, suspect->path)) - continue; - commit->object.flags |= MORE_THAN_ONE_PATH; - break; + for (suspect = commit->util; suspect; suspect = suspect->next) { + if (suspect->guilty && count++) { + commit->object.flags |= MORE_THAN_ONE_PATH; + break; + } } } } @@ -1748,6 +2008,12 @@ static void output(struct scoreboard *sb, int option) } } +static const char *get_next_line(const char *start, const char *end) +{ + const char *nl = memchr(start, '\n', end - start); + return nl ? nl + 1 : end; +} + /* * To allow quick access to the contents of nth line in the * final image, prepare an index in the scoreboard. @@ -1759,39 +2025,19 @@ static int prepare_lines(struct scoreboard *sb) const char *end = buf + len; const char *p; int *lineno; - int num = 0, incomplete = 0; + int num = 0; - for (p = buf;;) { - p = memchr(p, '\n', end - p); - if (p) { - p++; - num++; - continue; - } - break; - } + for (p = buf; p < end; p = get_next_line(p, end)) + num++; - if (len && end[-1] != '\n') - incomplete++; /* incomplete line at the end */ + sb->lineno = lineno = xmalloc(sizeof(*sb->lineno) * (num + 1)); - sb->lineno = xmalloc(sizeof(*sb->lineno) * (num + incomplete + 1)); - lineno = sb->lineno; + for (p = buf; p < end; p = get_next_line(p, end)) + *lineno++ = p - buf; - *lineno++ = 0; - for (p = buf;;) { - p = memchr(p, '\n', end - p); - if (p) { - p++; - *lineno++ = p - buf; - continue; - } - break; - } + *lineno = len; - if (incomplete) - *lineno++ = len; - - sb->num_lines = num + incomplete; + sb->num_lines = num; return sb->num_lines; } @@ -2006,6 +2252,18 @@ static void append_merge_parents(struct commit_list **tail) } /* + * This isn't as simple as passing sb->buf and sb->len, because we + * want to transfer ownership of the buffer to the commit (so we + * must use detach). + */ +static void set_commit_buffer_from_strbuf(struct commit *c, struct strbuf *sb) +{ + size_t len; + void *buf = strbuf_detach(sb, &len); + set_commit_buffer(c, buf, len); +} + +/* * Prepare a dummy commit that represents the work tree (or staged) item. * Note that annotating work tree item never works in the reverse. */ @@ -2026,7 +2284,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, struct strbuf msg = STRBUF_INIT; time(&now); - commit = xcalloc(1, sizeof(*commit)); + commit = alloc_commit_node(); commit->object.parsed = 1; commit->date = now; commit->object.type = OBJ_COMMIT; @@ -2053,7 +2311,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, ident, ident, path, (!contents_from ? path : (!strcmp(contents_from, "-") ? "standard input" : contents_from))); - commit->buffer = strbuf_detach(&msg, NULL); + set_commit_buffer_from_strbuf(commit, &msg); if (!contents_from || strcmp("-", contents_from)) { struct stat st; @@ -2098,7 +2356,6 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, origin->file.ptr = buf.buf; origin->file.size = buf.len; pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1); - commit->util = origin; /* * Read the current index, replace the path entry with @@ -2416,12 +2673,16 @@ parse_done: memset(&sb, 0, sizeof(sb)); sb.revs = &revs; - if (!reverse) + if (!reverse) { final_commit_name = prepare_final(&sb); + sb.commits.compare = compare_commits_by_commit_date; + } else if (contents_from) die("--contents and --children do not blend well."); - else + else { final_commit_name = prepare_initial(&sb); + sb.commits.compare = compare_commits_by_reverse_commit_date; + } if (!sb.final) { /* @@ -2510,12 +2771,16 @@ parse_done: ent->next = next; origin_incref(o); } + + o->suspects = ent; + prio_queue_put(&sb.commits, o->commit); + origin_decref(o); range_set_release(&ranges); string_list_clear(&range_list, 0); - sb.ent = ent; + sb.ent = NULL; sb.path = path; read_mailmap(&mailmap, NULL); @@ -2528,6 +2793,8 @@ parse_done: if (incremental) return 0; + sb.ent = blame_sort(sb.ent, compare_blame_final); + coalesce(&sb); if (!(output_option & OUTPUT_PORCELAIN)) diff --git a/builtin/branch.c b/builtin/branch.c index 652b1d2d14..0591b22a48 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -294,13 +294,13 @@ static char *resolve_symref(const char *src, const char *prefix) { unsigned char sha1[20]; int flag; - const char *dst, *cp; + const char *dst; dst = resolve_ref_unsafe(src, sha1, 0, &flag); if (!(dst && (flag & REF_ISSYMREF))) return NULL; - if (prefix && (cp = skip_prefix(dst, prefix))) - dst = cp; + if (prefix) + skip_prefix(dst, prefix, &dst); return xstrdup(dst); } diff --git a/builtin/checkout.c b/builtin/checkout.c index 07cf555309..463cfeea50 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -624,7 +624,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, /* Nothing to do. */ } else if (opts->force_detach || !new->path) { /* No longer on any branch. */ update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, - REF_NODEREF, DIE_ON_ERR); + REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); if (!opts->quiet) { if (old->path && advice_detached_head) detach_advice(new->name); @@ -651,12 +651,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts, } } if (old->path && old->name) { - char log_file[PATH_MAX], ref_file[PATH_MAX]; - - git_snpath(log_file, sizeof(log_file), "logs/%s", old->path); - git_snpath(ref_file, sizeof(ref_file), "%s", old->path); - if (!file_exists(ref_file) && file_exists(log_file)) - remove_path(log_file); + if (!ref_exists(old->path) && reflog_exists(old->path)) + delete_reflog(old->path); } } remove_branch_state(); @@ -780,8 +776,8 @@ static int switch_branches(const struct checkout_opts *opts, if (!(flag & REF_ISSYMREF)) old.path = NULL; - if (old.path && starts_with(old.path, "refs/heads/")) - old.name = old.path + strlen("refs/heads/"); + if (old.path) + skip_prefix(old.path, "refs/heads/", &old.name); if (!new->name) { new->name = "HEAD"; diff --git a/builtin/clean.c b/builtin/clean.c index 9a9151575d..27701d222c 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -48,7 +48,7 @@ enum color_clean { CLEAN_COLOR_PROMPT = 2, CLEAN_COLOR_HEADER = 3, CLEAN_COLOR_HELP = 4, - CLEAN_COLOR_ERROR = 5, + CLEAN_COLOR_ERROR = 5 }; #define MENU_OPTS_SINGLETON 01 diff --git a/builtin/clone.c b/builtin/clone.c index 9b3c04d914..e15ca332b5 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -521,7 +521,7 @@ static void write_followtags(const struct ref *refs, const char *msg) if (!has_sha1_file(ref->old_sha1)) continue; update_ref(msg, ref->name, ref->old_sha1, - NULL, 0, DIE_ON_ERR); + NULL, 0, UPDATE_REFS_DIE_ON_ERR); } } @@ -584,19 +584,20 @@ 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 && starts_with(our->name, "refs/heads/")) { + const char *head; + if (our && skip_prefix(our->name, "refs/heads/", &head)) { /* Local default branch link */ create_symref("HEAD", our->name, NULL); if (!option_bare) { - const char *head = skip_prefix(our->name, "refs/heads/"); - update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR); + update_ref(msg, "HEAD", our->old_sha1, NULL, 0, + UPDATE_REFS_DIE_ON_ERR); install_branch_config(0, head, option_origin, our->name); } } else if (our) { struct commit *c = lookup_commit_reference(our->old_sha1); /* --branch specifies a non-branch (i.e. tags), detach HEAD */ update_ref(msg, "HEAD", c->object.sha1, - NULL, REF_NODEREF, DIE_ON_ERR); + NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); } else if (remote) { /* * We know remote HEAD points to a non-branch, or @@ -604,7 +605,7 @@ static void update_head(const struct ref *our, const struct ref *remote, * Detach HEAD in all these cases. */ update_ref(msg, "HEAD", remote->old_sha1, - NULL, REF_NODEREF, DIE_ON_ERR); + NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); } } @@ -695,16 +696,19 @@ static void write_refspec_config(const char* src_ref_prefix, if (option_mirror || !option_bare) { if (option_single_branch && !option_mirror) { if (option_branch) { - if (strstr(our_head_points_at->name, "refs/tags/")) + if (starts_with(our_head_points_at->name, "refs/tags/")) strbuf_addf(&value, "+%s:%s", our_head_points_at->name, our_head_points_at->name); else strbuf_addf(&value, "+%s:%s%s", our_head_points_at->name, branch_top->buf, option_branch); } else if (remote_head_points_at) { + const char *head = remote_head_points_at->name; + if (!skip_prefix(head, "refs/heads/", &head)) + die("BUG: remote HEAD points at non-head?"); + strbuf_addf(&value, "+%s:%s%s", remote_head_points_at->name, - branch_top->buf, - skip_prefix(remote_head_points_at->name, "refs/heads/")); + branch_top->buf, head); } /* * otherwise, the next "git fetch" will diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 987a4c3d73..8a66c74e0f 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -123,8 +123,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) die_errno("git commit-tree: failed to read"); } - if (commit_tree(&buffer, tree_sha1, parents, commit_sha1, - NULL, sign_commit)) { + if (commit_tree(buffer.buf, buffer.len, tree_sha1, parents, + commit_sha1, NULL, sign_commit)) { strbuf_release(&buffer); return 1; } diff --git a/builtin/commit.c b/builtin/commit.c index 12afc42d19..461c3b1cad 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -526,10 +526,29 @@ static int sane_ident_split(struct ident_split *person) return 1; } +static int parse_force_date(const char *in, char *out, int len) +{ + if (len < 1) + return -1; + *out++ = '@'; + len--; + + if (parse_date(in, out, len) < 0) { + int errors = 0; + unsigned long t = approxidate_careful(in, &errors); + if (errors) + return -1; + snprintf(out, len, "%lu", t); + } + + return 0; +} + static void determine_author_info(struct strbuf *author_ident) { char *name, *email, *date; struct ident_split author; + char date_buf[64]; name = getenv("GIT_AUTHOR_NAME"); email = getenv("GIT_AUTHOR_EMAIL"); @@ -574,8 +593,12 @@ static void determine_author_info(struct strbuf *author_ident) email = xstrndup(lb + 2, rb - (lb + 2)); } - if (force_date) - date = force_date; + if (force_date) { + if (parse_force_date(force_date, date_buf, sizeof(date_buf))) + die(_("invalid date format: %s"), force_date); + date = date_buf; + } + strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT)); if (!split_ident_line(&author, author_ident->buf, author_ident->len) && sane_ident_split(&author)) { @@ -585,13 +608,46 @@ static void determine_author_info(struct strbuf *author_ident) } } -static char *cut_ident_timestamp_part(char *string) +static void split_ident_or_die(struct ident_split *id, const struct strbuf *buf) +{ + if (split_ident_line(id, buf->buf, buf->len) || + !sane_ident_split(id)) + die(_("Malformed ident string: '%s'"), buf->buf); +} + +static int author_date_is_interesting(void) +{ + return author_message || force_date; +} + +static void adjust_comment_line_char(const struct strbuf *sb) { - char *ket = strrchr(string, '>'); - if (!ket || ket[1] != ' ') - die(_("Malformed ident string: '%s'"), string); - *++ket = '\0'; - return ket; + char candidates[] = "#;@!$%^&|:"; + char *candidate; + const char *p; + + comment_line_char = candidates[0]; + if (!memchr(sb->buf, comment_line_char, sb->len)) + return; + + p = sb->buf; + candidate = strchr(candidates, *p); + if (candidate) + *candidate = ' '; + for (p = sb->buf; *p; p++) { + if ((p[0] == '\n' || p[0] == '\r') && p[1]) { + candidate = strchr(candidates, p[1]); + if (candidate) + *candidate = ' '; + } + } + + for (p = candidates; *p == ' '; p++) + ; + if (!*p) + die(_("unable to select a comment character that is not used\n" + "in the current commit message")); + comment_line_char = *p; } static int prepare_to_commit(const char *index_file, const char *prefix, @@ -747,6 +803,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) die_errno(_("could not write commit template")); + if (auto_comment_line_char) + adjust_comment_line_char(&sb); strbuf_release(&sb); /* This checks if committer ident is explicitly given */ @@ -754,7 +812,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (use_editor && include_status) { int ident_shown = 0; int saved_color_setting; - char *ai_tmp, *ci_tmp; + struct ident_split ci, ai; + if (whence != FROM_COMMIT) { if (cleanup_mode == CLEANUP_SCISSORS) wt_status_add_cut_line(s->fp); @@ -794,32 +853,39 @@ static int prepare_to_commit(const char *index_file, const char *prefix, status_printf_ln(s, GIT_COLOR_NORMAL, "%s", only_include_assumed); - ai_tmp = cut_ident_timestamp_part(author_ident->buf); - ci_tmp = cut_ident_timestamp_part(committer_ident.buf); - if (strcmp(author_ident->buf, committer_ident.buf)) + split_ident_or_die(&ai, author_ident); + split_ident_or_die(&ci, &committer_ident); + + if (ident_cmp(&ai, &ci)) status_printf_ln(s, GIT_COLOR_NORMAL, _("%s" - "Author: %s"), + "Author: %.*s <%.*s>"), ident_shown++ ? "" : "\n", - author_ident->buf); + (int)(ai.name_end - ai.name_begin), ai.name_begin, + (int)(ai.mail_end - ai.mail_begin), ai.mail_begin); + + if (author_date_is_interesting()) + status_printf_ln(s, GIT_COLOR_NORMAL, + _("%s" + "Date: %s"), + ident_shown++ ? "" : "\n", + show_ident_date(&ai, DATE_NORMAL)); if (!committer_ident_sufficiently_given()) status_printf_ln(s, GIT_COLOR_NORMAL, _("%s" - "Committer: %s"), + "Committer: %.*s <%.*s>"), ident_shown++ ? "" : "\n", - committer_ident.buf); + (int)(ci.name_end - ci.name_begin), ci.name_begin, + (int)(ci.mail_end - ci.mail_begin), ci.mail_begin); if (ident_shown) - status_printf_ln(s, GIT_COLOR_NORMAL, ""); + status_printf_ln(s, GIT_COLOR_NORMAL, "%s", ""); saved_color_setting = s->use_color; s->use_color = 0; commitable = run_status(s->fp, index_file, prefix, 1, s); s->use_color = saved_color_setting; - - *ai_tmp = ' '; - *ci_tmp = ' '; } else { unsigned char sha1[20]; const char *parent = "HEAD"; @@ -954,7 +1020,7 @@ static int message_is_empty(struct strbuf *sb) static int template_untouched(struct strbuf *sb) { struct strbuf tmpl = STRBUF_INIT; - char *start; + const char *start; if (cleanup_mode == CLEANUP_NONE && sb->len) return 0; @@ -963,8 +1029,7 @@ static int template_untouched(struct strbuf *sb) return 0; stripspace(&tmpl, cleanup_mode == CLEANUP_ALL); - start = (char *)skip_prefix(sb->buf, tmpl.buf); - if (!start) + if (!skip_prefix(sb->buf, tmpl.buf, &start)) start = sb->buf; strbuf_release(&tmpl); return rest_is_empty(sb, start - sb->buf); @@ -1369,6 +1434,13 @@ static void print_summary(const char *prefix, const unsigned char *sha1, strbuf_addstr(&format, "\n Author: "); strbuf_addbuf_percentquote(&format, &author_ident); } + if (author_date_is_interesting()) { + struct strbuf date = STRBUF_INIT; + format_commit_message(commit, "%ad", &date, &pctx); + strbuf_addstr(&format, "\n Date: "); + strbuf_addbuf_percentquote(&format, &date); + strbuf_release(&date); + } if (!committer_ident_sufficiently_given()) { strbuf_addstr(&format, "\n Committer: "); strbuf_addbuf_percentquote(&format, &committer_ident); @@ -1672,8 +1744,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) append_merge_tag_headers(parents, &tail); } - if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1, - author_ident.buf, sign_commit, extra)) { + if (commit_tree_extended(sb.buf, sb.len, active_cache_tree->sha1, + parents, sha1, author_ident.buf, sign_commit, extra)) { rollback_index_files(); die(_("failed to write commit object")); } @@ -1685,6 +1757,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) ? NULL : current_head->object.sha1, 0, NULL); + if (!ref_lock) { + rollback_index_files(); + die(_("cannot lock HEAD ref")); + } nl = strchr(sb.buf, '\n'); if (nl) @@ -1694,10 +1770,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix) strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg)); strbuf_insert(&sb, strlen(reflog_msg), ": ", 2); - if (!ref_lock) { - rollback_index_files(); - die(_("cannot lock HEAD ref")); - } if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) { rollback_index_files(); die(_("cannot update HEAD ref")); diff --git a/builtin/config.c b/builtin/config.c index 5677c942b6..fcd8474701 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -395,19 +395,6 @@ static int urlmatch_collect_fn(const char *var, const char *value, void *cb) return 0; } -static char *dup_downcase(const char *string) -{ - char *result; - size_t len, i; - - len = strlen(string); - result = xmalloc(len + 1); - for (i = 0; i < len; i++) - result[i] = tolower(string[i]); - result[i] = '\0'; - return result; -} - static int get_urlmatch(const char *var, const char *url) { char *section_tail; @@ -422,7 +409,7 @@ static int get_urlmatch(const char *var, const char *url) if (!url_normalize(url, &config.url)) die("%s", config.url.err); - config.section = dup_downcase(var); + config.section = xstrdup_tolower(var); section_tail = strchr(config.section, '.'); if (section_tail) { *section_tail = '\0'; diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index be6417d166..ce0e019e0c 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -22,14 +22,10 @@ static int stdin_diff_commit(struct commit *commit, char *line, int len) if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) { /* Graft the fake parents locally to the commit */ int pos = 41; - struct commit_list **pptr, *parents; + struct commit_list **pptr; /* Free the real parent list */ - for (parents = commit->parents; parents; ) { - struct commit_list *tmp = parents->next; - free(parents); - parents = tmp; - } + free_commit_list(commit->parents); commit->parents = NULL; pptr = &(commit->parents); while (line[pos] && !get_sha1_hex(line + pos, sha1)) { diff --git a/builtin/fast-export.c b/builtin/fast-export.c index b8d8a3aaf9..92b4624a4b 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -17,6 +17,7 @@ #include "utf8.h" #include "parse-options.h" #include "quote.h" +#include "remote.h" static const char *fast_export_usage[] = { N_("git fast-export [rev-list-opts]"), @@ -31,6 +32,8 @@ static int use_done_feature; static int no_data; static int full_tree; static struct string_list extra_refs = STRING_LIST_INIT_NODUP; +static struct refspec *refspecs; +static int refspecs_nr; static int parse_opt_signed_tag_mode(const struct option *opt, const char *arg, int unset) @@ -279,6 +282,7 @@ static const char *find_encoding(const char *begin, const char *end) static void handle_commit(struct commit *commit, struct rev_info *rev) { int saved_output_format = rev->diffopt.output_format; + const char *commit_buffer; const char *author, *author_end, *committer, *committer_end; const char *encoding, *message; char *reencoded = NULL; @@ -288,7 +292,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) rev->diffopt.output_format = DIFF_FORMAT_CALLBACK; parse_commit_or_die(commit); - author = strstr(commit->buffer, "\nauthor "); + commit_buffer = get_commit_buffer(commit, NULL); + author = strstr(commit_buffer, "\nauthor "); if (!author) die ("Could not find author in commit %s", sha1_to_hex(commit->object.sha1)); @@ -335,6 +340,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) ? strlen(message) : 0), reencoded ? reencoded : message ? message : ""); free(reencoded); + unuse_commit_buffer(commit, commit_buffer); for (i = 0, p = commit->parents; p; p = p->next) { int mark = get_object_mark(&p->item->object); @@ -525,6 +531,15 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1) continue; + if (refspecs) { + char *private; + private = apply_refspecs(refspecs, refspecs_nr, full_name); + if (private) { + free(full_name); + full_name = private; + } + } + commit = get_commit(e, full_name); if (!commit) { warning("%s: Unexpected object of type %s, skipping.", @@ -661,6 +676,19 @@ static void import_marks(char *input_file) fclose(f); } +static void handle_deletes(void) +{ + int i; + for (i = 0; i < refspecs_nr; i++) { + struct refspec *refspec = &refspecs[i]; + if (*refspec->src) + continue; + + printf("reset %s\nfrom %s\n\n", + refspec->dst, sha1_to_hex(null_sha1)); + } +} + int cmd_fast_export(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -668,6 +696,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) struct commit *commit; char *export_filename = NULL, *import_filename = NULL; uint32_t lastimportid; + struct string_list refspecs_list = STRING_LIST_INIT_NODUP; struct option options[] = { OPT_INTEGER(0, "progress", &progress, N_("show progress after <n> objects")), @@ -688,6 +717,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "use-done-feature", &use_done_feature, N_("Use the done feature to terminate the stream")), OPT_BOOL(0, "no-data", &no_data, N_("Skip output of blob data")), + OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"), + N_("Apply refspec to exported refs")), OPT_END() }; @@ -701,11 +732,27 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) revs.topo_order = 1; revs.show_source = 1; revs.rewrite_parents = 1; + argc = parse_options(argc, argv, prefix, options, fast_export_usage, + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN); argc = setup_revisions(argc, argv, &revs, NULL); - argc = parse_options(argc, argv, prefix, options, fast_export_usage, 0); if (argc > 1) usage_with_options (fast_export_usage, options); + if (refspecs_list.nr) { + const char **refspecs_str; + int i; + + refspecs_str = xmalloc(sizeof(*refspecs_str) * refspecs_list.nr); + for (i = 0; i < refspecs_list.nr; i++) + refspecs_str[i] = refspecs_list.items[i].string; + + refspecs_nr = refspecs_list.nr; + refspecs = parse_fetch_refspec(refspecs_nr, refspecs_str); + + string_list_clear(&refspecs_list, 1); + free(refspecs_str); + } + if (use_done_feature) printf("feature done\n"); @@ -733,6 +780,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) } handle_tags_and_duplicates(); + handle_deletes(); if (export_filename && lastimportid != last_idnum) export_marks(export_filename); @@ -740,5 +788,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (use_done_feature) printf("done\n"); + free_refspec(refspecs_nr, refspecs); + return 0; } diff --git a/builtin/fetch.c b/builtin/fetch.c index 55f457c04f..e8d0cca3e4 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -45,6 +45,8 @@ static struct transport *gsecondary; static const char *submodule_prefix = ""; static const char *recurse_submodules_default; static int shown_url = 0; +static int refmap_alloc, refmap_nr; +static const char **refmap_array; static int option_parse_recurse_submodules(const struct option *opt, const char *arg, int unset) @@ -69,6 +71,19 @@ static int git_fetch_config(const char *k, const char *v, void *cb) return 0; } +static int parse_refmap_arg(const struct option *opt, const char *arg, int unset) +{ + ALLOC_GROW(refmap_array, refmap_nr + 1, refmap_alloc); + + /* + * "git fetch --refmap='' origin foo" + * can be used to tell the command not to store anywhere + */ + if (*arg) + refmap_array[refmap_nr++] = arg; + return 0; +} + static struct option builtin_fetch_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "all", &all, @@ -107,6 +122,8 @@ static struct option builtin_fetch_options[] = { N_("default mode for recursion"), PARSE_OPT_HIDDEN }, OPT_BOOL(0, "update-shallow", &update_shallow, N_("accept refs that update .git/shallow")), + { OPTION_CALLBACK, 0, "refmap", NULL, N_("refmap"), + N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg }, OPT_END() }; @@ -278,6 +295,9 @@ static struct ref *get_ref_map(struct transport *transport, const struct ref *remote_refs = transport_get_remote_refs(transport); if (refspec_count) { + struct refspec *fetch_refspec; + int fetch_refspec_nr; + for (i = 0; i < refspec_count; i++) { get_fetch_map(remote_refs, &refspecs[i], &tail, 0); if (refspecs[i].dst && refspecs[i].dst[0]) @@ -307,12 +327,21 @@ static struct ref *get_ref_map(struct transport *transport, * by ref_remove_duplicates() in favor of one of these * opportunistic entries with FETCH_HEAD_IGNORE. */ - for (i = 0; i < transport->remote->fetch_refspec_nr; i++) - get_fetch_map(ref_map, &transport->remote->fetch[i], - &oref_tail, 1); + if (refmap_array) { + fetch_refspec = parse_fetch_refspec(refmap_nr, refmap_array); + fetch_refspec_nr = refmap_nr; + } else { + fetch_refspec = transport->remote->fetch; + fetch_refspec_nr = transport->remote->fetch_refspec_nr; + } + + for (i = 0; i < fetch_refspec_nr; i++) + get_fetch_map(ref_map, &fetch_refspec[i], &oref_tail, 1); if (tags == TAGS_SET) get_fetch_map(remote_refs, tag_refspec, &tail, 0); + } else if (refmap_array) { + die("--refmap option is only meaningful with command-line refspec(s)."); } else { /* Use the defaults */ struct remote *remote = transport->remote; @@ -1053,16 +1082,11 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) refs = xcalloc(argc + 1, sizeof(const char *)); for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "tag")) { - char *ref; i++; if (i >= argc) die(_("You need to specify a tag name.")); - ref = xmalloc(strlen(argv[i]) * 2 + 22); - strcpy(ref, "refs/tags/"); - strcat(ref, argv[i]); - strcat(ref, ":refs/tags/"); - strcat(ref, argv[i]); - refs[j++] = ref; + refs[j++] = xstrfmt("refs/tags/%s:refs/tags/%s", + argv[i], argv[i]); } else refs[j++] = argv[i]; } diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 3906eda877..79df05ef52 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -100,7 +100,8 @@ static int handle_line(char *line, struct merge_parents *merge_parents) { int i, len = strlen(line); struct origin_data *origin_data; - char *src, *origin; + char *src; + const char *origin; struct src_data *src_data; struct string_list_item *item; int pulling_head = 0; @@ -164,8 +165,7 @@ static int handle_line(char *line, struct merge_parents *merge_parents) origin = line; string_list_append(&src_data->tag, origin + 4); src_data->head_status |= 2; - } else if (starts_with(line, "remote-tracking branch ")) { - origin = line + strlen("remote-tracking branch "); + } else if (skip_prefix(line, "remote-tracking branch ", &origin)) { string_list_append(&src_data->r_branch, origin); src_data->head_status |= 2; } else { @@ -178,11 +178,8 @@ static int handle_line(char *line, struct merge_parents *merge_parents) int len = strlen(origin); if (origin[0] == '\'' && origin[len - 1] == '\'') origin = xmemdupz(origin + 1, len - 2); - } else { - char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5); - sprintf(new_origin, "%s of %s", origin, src); - origin = new_origin; - } + } else + origin = xstrfmt("%s of %s", origin, src); if (strcmp(".", src)) origin_data->is_local_branch = 0; string_list_append(&origins, origin)->util = origin_data; @@ -230,12 +227,14 @@ static void add_branch_desc(struct strbuf *out, const char *name) static void record_person(int which, struct string_list *people, struct commit *commit) { + const char *buffer; char *name_buf, *name, *name_end; struct string_list_item *elem; const char *field; field = (which == 'a') ? "\nauthor " : "\ncommitter "; - name = strstr(commit->buffer, field); + buffer = get_commit_buffer(commit, NULL); + name = strstr(buffer, field); if (!name) return; name += strlen(field); @@ -247,6 +246,7 @@ static void record_person(int which, struct string_list *people, if (name_end < name) return; name_buf = xmemdupz(name, name_end - name + 1); + unuse_commit_buffer(commit, buffer); elem = string_list_lookup(people, name_buf); if (!elem) { @@ -297,8 +297,8 @@ static void credit_people(struct strbuf *out, if (!them->nr || (them->nr == 1 && me && - (me = skip_prefix(me, them->items->string)) != NULL && - skip_prefix(me, " <"))) + skip_prefix(me, them->items->string, &me) && + starts_with(me, " <"))) return; strbuf_addf(out, "\n%c %s ", comment_line_char, label); add_people_count(out, them); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 3e1d5c3334..4135980f20 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -193,7 +193,7 @@ static int verify_format(const char *format) at = parse_atom(sp + 2, ep); cp = ep + 1; - if (!memcmp(used_atom[at], "color:", 6)) + if (starts_with(used_atom[at], "color:")) need_color_reset_at_eol = !!strcmp(used_atom[at], color_reset); } return 0; diff --git a/builtin/fsck.c b/builtin/fsck.c index fc150c8821..8aadca160e 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -310,8 +310,7 @@ static int fsck_obj(struct object *obj) if (obj->type == OBJ_COMMIT) { struct commit *commit = (struct commit *) obj; - free(commit->buffer); - commit->buffer = NULL; + free_commit_buffer(commit); if (!commit->parents && show_root) printf("root %s\n", sha1_to_hex(commit->object.sha1)); diff --git a/builtin/grep.c b/builtin/grep.c index b8d440d0e0..c86a142f30 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -361,9 +361,7 @@ static void run_pager(struct grep_opt *opt, const char *prefix) argv[i] = path_list->items[i].string; argv[path_list->nr] = NULL; - if (prefix && chdir(prefix)) - die(_("Failed to chdir: %s"), prefix); - status = run_command_v_opt(argv, RUN_USING_SHELL); + status = run_command_v_opt_cd_env(argv, RUN_USING_SHELL, prefix, NULL); if (status) exit(status); free(argv); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 18f57de58b..8b3bd29dbc 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -786,7 +786,8 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, } if (obj->type == OBJ_COMMIT) { struct commit *commit = (struct commit *) obj; - commit->buffer = NULL; + if (detach_commit_buffer(commit, NULL) != data) + die("BUG: parse_object_buffer transmogrified our buffer"); } obj->flags |= FLAG_CHECKED; } diff --git a/builtin/log.c b/builtin/log.c index 3b6a6bbadd..27c1b65db4 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -63,6 +63,8 @@ static int parse_decoration_style(const char *var, const char *value) return DECORATE_FULL_REFS; else if (!strcmp(value, "short")) return DECORATE_SHORT_REFS; + else if (!strcmp(value, "auto")) + return (isatty(1) || pager_in_use()) ? DECORATE_SHORT_REFS : 0; return -1; } @@ -345,8 +347,7 @@ static int cmd_log_walk(struct rev_info *rev) rev->max_count++; if (!rev->reflog_info) { /* we allow cycles in reflog ancestry */ - free(commit->buffer); - commit->buffer = NULL; + free_commit_buffer(commit); } free_commit_list(commit->parents); commit->parents = NULL; @@ -669,6 +670,7 @@ static void add_header(const char *value) static int thread; static int do_signoff; static const char *signature = git_version_string; +static const char *signature_file; static int config_cover_letter; enum { @@ -738,6 +740,8 @@ static int git_format_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "format.signature")) return git_config_string(&signature, var, value); + if (!strcmp(var, "format.signaturefile")) + return git_config_pathname(&signature_file, var, value); if (!strcmp(var, "format.coverletter")) { if (value && !strcasecmp(value, "auto")) { config_cover_letter = COVER_AUTO; @@ -840,8 +844,13 @@ static void gen_message_id(struct rev_info *info, char *base) static void print_signature(void) { - if (signature && *signature) - printf("-- \n%s\n\n", signature); + if (!signature || !*signature) + return; + + printf("-- \n%s", signature); + if (signature[strlen(signature)-1] != '\n') + putchar('\n'); + putchar('\n'); } static void add_branch_description(struct strbuf *buf, const char *branch_name) @@ -862,7 +871,7 @@ static char *find_branch_name(struct rev_info *rev) int i, positive = -1; unsigned char branch_sha1[20]; const unsigned char *tip_sha1; - const char *ref; + const char *ref, *v; char *full_ref, *branch = NULL; for (i = 0; i < rev->cmdline.nr; i++) { @@ -878,9 +887,9 @@ static char *find_branch_name(struct rev_info *rev) ref = rev->cmdline.rev[positive].name; tip_sha1 = rev->cmdline.rev[positive].item->sha1; if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) && - starts_with(full_ref, "refs/heads/") && + skip_prefix(full_ref, "refs/heads/", &v) && !hashcmp(tip_sha1, branch_sha1)) - branch = xstrdup(full_ref + strlen("refs/heads/")); + branch = xstrdup(v); free(full_ref); return branch; } @@ -915,9 +924,12 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, log_write_email_headers(rev, head, &pp.subject, &pp.after_subject, &need_8bit_cte); - for (i = 0; !need_8bit_cte && i < nr; i++) - if (has_non_ascii(list[i]->buffer)) + for (i = 0; !need_8bit_cte && i < nr; i++) { + const char *buf = get_commit_buffer(list[i], NULL); + if (has_non_ascii(buf)) need_8bit_cte = 1; + unuse_commit_buffer(list[i], buf); + } if (!branch_name) branch_name = find_branch_name(rev); @@ -1226,6 +1238,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, thread_callback }, OPT_STRING(0, "signature", &signature, N_("signature"), N_("add a signature")), + OPT_FILENAME(0, "signature-file", &signature_file, + N_("add a signature from a file")), OPT__QUIET(&quiet, N_("don't print the patch filenames")), OPT_END() }; @@ -1382,10 +1396,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (check_head) { unsigned char sha1[20]; - const char *ref; + const char *ref, *v; ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL); - if (ref && starts_with(ref, "refs/heads/")) - branch_name = xstrdup(ref + strlen("refs/heads/")); + if (ref && skip_prefix(ref, "refs/heads/", &v)) + branch_name = xstrdup(v); else branch_name = xstrdup(""); /* no branch */ } @@ -1443,6 +1457,18 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) cover_letter = (config_cover_letter == COVER_ON); } + if (!signature) { + ; /* --no-signature inhibits all signatures */ + } else if (signature && signature != git_version_string) { + ; /* non-default signature already set */ + } else if (signature_file) { + struct strbuf buf = STRBUF_INIT; + + if (strbuf_read_file(&buf, signature_file, 128) < 0) + die_errno(_("unable to read signature file '%s'"), signature_file); + signature = strbuf_detach(&buf, NULL); + } + if (in_reply_to || thread || cover_letter) rev.ref_message_ids = xcalloc(1, sizeof(struct string_list)); if (in_reply_to) { @@ -1504,8 +1530,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) reopen_stdout(rev.numbered_files ? NULL : commit, NULL, &rev, quiet)) die(_("Failed to create output files")); shown = log_tree_commit(&rev, commit); - free(commit->buffer); - commit->buffer = NULL; + free_commit_buffer(commit); /* We put one extra blank line between formatted * patches and this flag is used by log-tree code diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 3e9eefb091..b2a4b92992 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -92,7 +92,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (argv[i]) { int j; - pattern = xcalloc(sizeof(const char *), argc - i + 1); + pattern = xcalloc(argc - i + 1, sizeof(const char *)); for (j = i; j < argc; j++) { int len = strlen(argv[j]); char *p = xmalloc(len + 3); diff --git a/builtin/merge.c b/builtin/merge.c index 66d8843301..86e9c61277 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -63,7 +63,7 @@ static int verbosity; static int allow_rerere_auto; static int abort_current_merge; static int show_progress = -1; -static int default_to_upstream; +static int default_to_upstream = 1; static const char *sign_commit; static struct strategy all_strategy[] = { @@ -398,7 +398,7 @@ static void finish(struct commit *head_commit, const char *argv_gc_auto[] = { "gc", "--auto", NULL }; update_ref(reflog_message.buf, "HEAD", new_head, head, 0, - DIE_ON_ERR); + UPDATE_REFS_DIE_ON_ERR); /* * We ignore errors in 'gc --auto', since the * user should see them. @@ -852,8 +852,8 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) parent->next->item = remoteheads->item; parent->next->next = NULL; prepare_to_commit(remoteheads); - if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL, - sign_commit)) + if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parent, + result_commit, NULL, sign_commit)) die(_("failed to write commit object")); finish(head, remoteheads, result_commit, "In-index merge"); drop_save(); @@ -877,8 +877,8 @@ static int finish_automerge(struct commit *head, commit_list_insert(head, &parents); strbuf_addch(&merge_msg, '\n'); prepare_to_commit(remoteheads); - if (commit_tree(&merge_msg, result_tree, parents, result_commit, - NULL, sign_commit)) + if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents, + result_commit, NULL, sign_commit)) die(_("failed to write commit object")); strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); finish(head, remoteheads, result_commit, buf.buf); @@ -1222,7 +1222,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("%s - not something we can merge"), argv[0]); read_empty(remote_head->object.sha1, 0); update_ref("initial pull", "HEAD", remote_head->object.sha1, - NULL, 0, DIE_ON_ERR); + NULL, 0, UPDATE_REFS_DIE_ON_ERR); goto done; } else { struct strbuf merge_names = STRBUF_INIT; @@ -1282,10 +1282,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) printf(_("Commit %s has a good GPG signature by %s\n"), hex, signature_check.signer); - free(signature_check.gpg_output); - free(signature_check.gpg_status); - free(signature_check.signer); - free(signature_check.key); + signature_check_clear(&signature_check); } } @@ -1339,7 +1336,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1, - NULL, 0, DIE_ON_ERR); + NULL, 0, UPDATE_REFS_DIE_ON_ERR); if (remoteheads && !common) ; /* No common ancestors found. We need a real merge. */ diff --git a/builtin/name-rev.c b/builtin/name-rev.c index c824d4ec5f..3c8f319be6 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -33,10 +33,7 @@ static void name_rev(struct commit *commit, return; if (deref) { - char *new_name = xmalloc(strlen(tip_name)+3); - strcpy(new_name, tip_name); - strcat(new_name, "^0"); - tip_name = new_name; + tip_name = xstrfmt("%s^0", tip_name); if (generation) die("generation: %d, but deref?", generation); diff --git a/builtin/notes.c b/builtin/notes.c index 39c8573cde..820c34135c 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -717,7 +717,7 @@ static int merge_commit(struct notes_merge_options *o) strbuf_insert(&msg, 0, "notes: ", 7); update_ref(msg.buf, o->local_ref, sha1, is_null_sha1(parent_sha1) ? NULL : parent_sha1, - 0, DIE_ON_ERR); + 0, UPDATE_REFS_DIE_ON_ERR); free_notes(t); strbuf_release(&msg); @@ -812,11 +812,11 @@ static int merge(int argc, const char **argv, const char *prefix) if (result >= 0) /* Merge resulted (trivially) in result_sha1 */ /* Update default notes ref with new commit */ update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, - 0, DIE_ON_ERR); + 0, UPDATE_REFS_DIE_ON_ERR); else { /* Merge has unresolved conflicts */ /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, - 0, DIE_ON_ERR); + 0, UPDATE_REFS_DIE_ON_ERR); /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) die("Failed to store link to current notes ref (%s)", diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index de36c60ca1..238b5021eb 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2214,10 +2214,6 @@ static int git_pack_config(const char *k, const char *v, void *cb) cache_max_small_delta_size = git_config_int(k, v); return 0; } - if (!strcmp(k, "pack.writebitmaps")) { - write_bitmap_index = git_config_bool(k, v); - return 0; - } if (!strcmp(k, "pack.writebitmaphashcache")) { if (git_config_bool(k, v)) write_bitmap_options |= BITMAP_OPT_HASH_CACHE; diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 3cfe02d5a5..77db8739b5 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -1,17 +1,14 @@ #include "builtin.h" -static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c) +static void flush_current_id(int patchlen, unsigned char *id, unsigned char *result) { - unsigned char result[20]; char name[50]; if (!patchlen) return; - git_SHA1_Final(result, c); memcpy(name, sha1_to_hex(id), 41); printf("%s %s\n", sha1_to_hex(result), name); - git_SHA1_Init(c); } static int remove_space(char *line) @@ -56,10 +53,31 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) return 1; } -static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct strbuf *line_buf) +static void flush_one_hunk(unsigned char *result, git_SHA_CTX *ctx) +{ + unsigned char hash[20]; + unsigned short carry = 0; + int i; + + git_SHA1_Final(hash, ctx); + git_SHA1_Init(ctx); + /* 20-byte sum, with carry */ + for (i = 0; i < 20; ++i) { + carry += result[i] + hash[i]; + result[i] = carry; + carry >>= 8; + } +} + +static int get_one_patchid(unsigned char *next_sha1, unsigned char *result, + struct strbuf *line_buf, int stable) { int patchlen = 0, found_next = 0; int before = -1, after = -1; + git_SHA_CTX ctx; + + git_SHA1_Init(&ctx); + hashclr(result); while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) { char *line = line_buf->buf; @@ -107,6 +125,8 @@ static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct st break; /* Else we're parsing another header. */ + if (stable) + flush_one_hunk(result, &ctx); before = after = -1; } @@ -119,39 +139,63 @@ static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct st /* Compute the sha without whitespace */ len = remove_space(line); patchlen += len; - git_SHA1_Update(ctx, line, len); + git_SHA1_Update(&ctx, line, len); } if (!found_next) hashclr(next_sha1); + flush_one_hunk(result, &ctx); + return patchlen; } -static void generate_id_list(void) +static void generate_id_list(int stable) { - unsigned char sha1[20], n[20]; - git_SHA_CTX ctx; + unsigned char sha1[20], n[20], result[20]; int patchlen; struct strbuf line_buf = STRBUF_INIT; - git_SHA1_Init(&ctx); hashclr(sha1); while (!feof(stdin)) { - patchlen = get_one_patchid(n, &ctx, &line_buf); - flush_current_id(patchlen, sha1, &ctx); + patchlen = get_one_patchid(n, result, &line_buf, stable); + flush_current_id(patchlen, sha1, result); hashcpy(sha1, n); } strbuf_release(&line_buf); } -static const char patch_id_usage[] = "git patch-id < patch"; +static const char patch_id_usage[] = "git patch-id [--stable | --unstable] < patch"; + +static int git_patch_id_config(const char *var, const char *value, void *cb) +{ + int *stable = cb; + + if (!strcmp(var, "patchid.stable")) { + *stable = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} int cmd_patch_id(int argc, const char **argv, const char *prefix) { - if (argc != 1) + int stable = -1; + + git_config(git_patch_id_config, &stable); + + /* If nothing is set, default to unstable. */ + if (stable < 0) + stable = 0; + + if (argc == 2 && !strcmp(argv[1], "--stable")) + stable = 1; + else if (argc == 2 && !strcmp(argv[1], "--unstable")) + stable = 0; + else if (argc != 1) usage(patch_id_usage); - generate_id_list(); + generate_id_list(stable); return 0; } diff --git a/builtin/push.c b/builtin/push.c index f8dfea41e1..f50e3d5e77 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -127,11 +127,10 @@ static NORETURN int die_push_simple(struct branch *branch, struct remote *remote * them the big ugly fully qualified ref. */ const char *advice_maybe = ""; - const char *short_upstream = - skip_prefix(branch->merge[0]->src, "refs/heads/"); + const char *short_upstream = branch->merge[0]->src; + + skip_prefix(short_upstream, "refs/heads/", &short_upstream); - if (!short_upstream) - short_upstream = branch->merge[0]->src; /* * Don't show advice for people who explicitly set * push.default. diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index c3230817db..18458e81c6 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -614,12 +614,9 @@ static void run_update_post_hook(struct command *commands) argv[0] = hook; for (argc = 1, cmd = commands; cmd; cmd = cmd->next) { - char *p; if (cmd->error_string || cmd->did_not_exist) continue; - p = xmalloc(strlen(cmd->ref_name) + 1); - strcpy(p, cmd->ref_name); - argv[argc] = p; + argv[argc] = xstrdup(cmd->ref_name); argc++; } argv[argc] = NULL; diff --git a/builtin/reflog.c b/builtin/reflog.c index c12a9784e6..e8a8fb13b9 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -369,7 +369,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, if (!lock) return error("cannot lock ref '%s'", ref); log_file = git_pathdup("logs/%s", ref); - if (!file_exists(log_file)) + if (!reflog_exists(ref)) goto finish; if (!cmd->dry_run) { newlog_path = git_pathdup("logs/%s.lock", ref); diff --git a/builtin/remote.c b/builtin/remote.c index 9b3e368983..a8efe3da4d 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -250,9 +250,7 @@ static struct string_list branch_list; static const char *abbrev_ref(const char *name, const char *prefix) { - const char *abbrev = skip_prefix(name, prefix); - if (abbrev) - return abbrev; + skip_prefix(name, prefix, &name); return name; } #define abbrev_branch(name) abbrev_ref((name), "refs/heads/") @@ -282,7 +280,7 @@ static int config_read_branches(const char *key, const char *value, void *cb) item = string_list_insert(&branch_list, name); if (!item->util) - item->util = xcalloc(sizeof(struct branch_info), 1); + item->util = xcalloc(1, sizeof(struct branch_info)); info = item->util; if (type == REMOTE) { if (info->remote_name) @@ -398,7 +396,7 @@ static int get_push_ref_states(const struct ref *remote_refs, item = string_list_append(&states->push, abbrev_branch(ref->peer_ref->name)); - item->util = xcalloc(sizeof(struct push_info), 1); + item->util = xcalloc(1, sizeof(struct push_info)); info = item->util; info->forced = ref->force; info->dest = xstrdup(abbrev_branch(ref->name)); @@ -433,7 +431,7 @@ static int get_push_ref_states_noquery(struct ref_states *states) states->push.strdup_strings = 1; if (!remote->push_refspec_nr) { item = string_list_append(&states->push, _("(matching)")); - info = item->util = xcalloc(sizeof(struct push_info), 1); + info = item->util = xcalloc(1, sizeof(struct push_info)); info->status = PUSH_STATUS_NOTQUERIED; info->dest = xstrdup(item->string); } @@ -446,7 +444,7 @@ static int get_push_ref_states_noquery(struct ref_states *states) else item = string_list_append(&states->push, _("(delete)")); - info = item->util = xcalloc(sizeof(struct push_info), 1); + info = item->util = xcalloc(1, sizeof(struct push_info)); info->forced = spec->force; info->status = PUSH_STATUS_NOTQUERIED; info->dest = xstrdup(spec->dst ? spec->dst : item->string); diff --git a/builtin/repack.c b/builtin/repack.c index 6b0b62dcb2..ff2216a7aa 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -10,6 +10,7 @@ static int delta_base_offset = 1; static int pack_kept_objects = -1; +static int write_bitmaps; static char *packdir, *packtmp; static const char *const git_repack_usage[] = { @@ -27,6 +28,11 @@ static int repack_config(const char *var, const char *value, void *cb) pack_kept_objects = git_config_bool(var, value); return 0; } + if (!strcmp(var, "repack.writebitmaps") || + !strcmp(var, "pack.writebitmaps")) { + write_bitmaps = git_config_bool(var, value); + return 0; + } return git_default_config(var, value, cb); } @@ -149,7 +155,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix) int no_update_server_info = 0; int quiet = 0; int local = 0; - int write_bitmap = -1; struct option builtin_repack_options[] = { OPT_BIT('a', NULL, &pack_everything, @@ -168,7 +173,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) OPT__QUIET(&quiet, N_("be quiet")), OPT_BOOL('l', "local", &local, N_("pass --local to git-pack-objects")), - OPT_BOOL('b', "write-bitmap-index", &write_bitmap, + OPT_BOOL('b', "write-bitmap-index", &write_bitmaps, N_("write bitmap index")), OPT_STRING(0, "unpack-unreachable", &unpack_unreachable, N_("approxidate"), N_("with -A, do not loosen objects older than this")), @@ -191,7 +196,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) git_repack_usage, 0); if (pack_kept_objects < 0) - pack_kept_objects = write_bitmap; + pack_kept_objects = write_bitmaps; packdir = mkpathdup("%s/pack", get_object_directory()); packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid()); @@ -217,9 +222,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) argv_array_pushf(&cmd_args, "--no-reuse-delta"); if (no_reuse_object) argv_array_pushf(&cmd_args, "--no-reuse-object"); - if (write_bitmap >= 0) - argv_array_pushf(&cmd_args, "--%swrite-bitmap-index", - write_bitmap ? "" : "no-"); + if (write_bitmaps) + argv_array_push(&cmd_args, "--write-bitmap-index"); if (pack_everything & ALL_INTO_ONE) { get_non_kept_pack_filenames(&existing_packs); diff --git a/builtin/replace.c b/builtin/replace.c index b62420a01a..1bb491d3c4 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -12,9 +12,11 @@ #include "builtin.h" #include "refs.h" #include "parse-options.h" +#include "run-command.h" static const char * const git_replace_usage[] = { N_("git replace [-f] <object> <replacement>"), + N_("git replace [-f] --edit <object>"), N_("git replace -d <object>..."), N_("git replace [--format=<format>] [-l [<pattern>]]"), NULL @@ -123,26 +125,36 @@ static int delete_replace_ref(const char *name, const char *ref, return 0; } -static int replace_object(const char *object_ref, const char *replace_ref, - int force) +static void check_ref_valid(unsigned char object[20], + unsigned char prev[20], + char *ref, + int ref_size, + int force) { - unsigned char object[20], prev[20], repl[20]; - enum object_type obj_type, repl_type; - char ref[PATH_MAX]; - struct ref_lock *lock; - - if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); - if (get_sha1(replace_ref, repl)) - die("Failed to resolve '%s' as a valid ref.", replace_ref); - - if (snprintf(ref, sizeof(ref), + if (snprintf(ref, ref_size, "refs/replace/%s", - sha1_to_hex(object)) > sizeof(ref) - 1) + sha1_to_hex(object)) > ref_size - 1) die("replace ref name too long: %.*s...", 50, ref); if (check_refname_format(ref, 0)) die("'%s' is not a valid ref name.", ref); + if (read_ref(ref, prev)) + hashclr(prev); + else if (!force) + die("replace ref '%s' already exists", ref); +} + +static int replace_object_sha1(const char *object_ref, + unsigned char object[20], + const char *replace_ref, + unsigned char repl[20], + int force) +{ + unsigned char prev[20]; + enum object_type obj_type, repl_type; + char ref[PATH_MAX]; + struct ref_lock *lock; + obj_type = sha1_object_info(object, NULL); repl_type = sha1_object_info(repl, NULL); if (!force && obj_type != repl_type) @@ -152,10 +164,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, object_ref, typename(obj_type), replace_ref, typename(repl_type)); - if (read_ref(ref, prev)) - hashclr(prev); - else if (!force) - die("replace ref '%s' already exists", ref); + check_ref_valid(object, prev, ref, sizeof(ref), force); lock = lock_any_ref_for_update(ref, prev, 0, NULL); if (!lock) @@ -166,13 +175,140 @@ static int replace_object(const char *object_ref, const char *replace_ref, return 0; } +static int replace_object(const char *object_ref, const char *replace_ref, int force) +{ + unsigned char object[20], repl[20]; + + if (get_sha1(object_ref, object)) + die("Failed to resolve '%s' as a valid ref.", object_ref); + if (get_sha1(replace_ref, repl)) + die("Failed to resolve '%s' as a valid ref.", replace_ref); + + return replace_object_sha1(object_ref, object, replace_ref, repl, force); +} + +/* + * Write the contents of the object named by "sha1" to the file "filename", + * pretty-printed for human editing based on its type. + */ +static void export_object(const unsigned char *sha1, const char *filename) +{ + const char *argv[] = { "--no-replace-objects", "cat-file", "-p", NULL, NULL }; + struct child_process cmd = { argv }; + int fd; + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) + die_errno("unable to open %s for writing", filename); + + argv[3] = sha1_to_hex(sha1); + cmd.git_cmd = 1; + cmd.out = fd; + + if (run_command(&cmd)) + die("cat-file reported failure"); + + close(fd); +} + +/* + * Read a previously-exported (and possibly edited) object back from "filename", + * interpreting it as "type", and writing the result to the object database. + * The sha1 of the written object is returned via sha1. + */ +static void import_object(unsigned char *sha1, enum object_type type, + const char *filename) +{ + int fd; + + fd = open(filename, O_RDONLY); + if (fd < 0) + die_errno("unable to open %s for reading", filename); + + if (type == OBJ_TREE) { + const char *argv[] = { "mktree", NULL }; + struct child_process cmd = { argv }; + struct strbuf result = STRBUF_INIT; + + cmd.argv = argv; + cmd.git_cmd = 1; + cmd.in = fd; + cmd.out = -1; + + if (start_command(&cmd)) + die("unable to spawn mktree"); + + if (strbuf_read(&result, cmd.out, 41) < 0) + die_errno("unable to read from mktree"); + close(cmd.out); + + if (finish_command(&cmd)) + die("mktree reported failure"); + if (get_sha1_hex(result.buf, sha1) < 0) + die("mktree did not return an object name"); + + strbuf_release(&result); + } else { + struct stat st; + int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT; + + if (fstat(fd, &st) < 0) + die_errno("unable to fstat %s", filename); + if (index_fd(sha1, fd, &st, type, NULL, flags) < 0) + die("unable to write object to database"); + /* index_fd close()s fd for us */ + } + + /* + * No need to close(fd) here; both run-command and index-fd + * will have done it for us. + */ +} + +static int edit_and_replace(const char *object_ref, int force) +{ + char *tmpfile = git_pathdup("REPLACE_EDITOBJ"); + enum object_type type; + unsigned char old[20], new[20], prev[20]; + char ref[PATH_MAX]; + + if (get_sha1(object_ref, old) < 0) + die("Not a valid object name: '%s'", object_ref); + + type = sha1_object_info(old, NULL); + if (type < 0) + die("unable to get object type for %s", sha1_to_hex(old)); + + check_ref_valid(old, prev, ref, sizeof(ref), force); + + export_object(old, tmpfile); + if (launch_editor(tmpfile, NULL, NULL) < 0) + die("editing object file failed"); + import_object(new, type, tmpfile); + + free(tmpfile); + + if (!hashcmp(old, new)) + return error("new object is the same as the old one: '%s'", sha1_to_hex(old)); + + return replace_object_sha1(object_ref, old, "replacement", new, force); +} + int cmd_replace(int argc, const char **argv, const char *prefix) { - int list = 0, delete = 0, force = 0; + int force = 0; const char *format = NULL; + enum { + MODE_UNSPECIFIED = 0, + MODE_LIST, + MODE_DELETE, + MODE_EDIT, + MODE_REPLACE + } cmdmode = MODE_UNSPECIFIED; struct option options[] = { - OPT_BOOL('l', "list", &list, N_("list replace refs")), - OPT_BOOL('d', "delete", &delete, N_("delete replace refs")), + OPT_CMDMODE('l', "list", &cmdmode, N_("list replace refs"), MODE_LIST), + OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE), + OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT), OPT_BOOL('f', "force", &force, N_("replace the ref if it exists")), OPT_STRING(0, "format", &format, N_("format"), N_("use this format")), OPT_END() @@ -182,44 +318,43 @@ int cmd_replace(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0); - if (list && delete) - usage_msg_opt("-l and -d cannot be used together", - git_replace_usage, options); + if (!cmdmode) + cmdmode = argc ? MODE_REPLACE : MODE_LIST; - if (format && delete) - usage_msg_opt("--format and -d cannot be used together", + if (format && cmdmode != MODE_LIST) + usage_msg_opt("--format cannot be used when not listing", git_replace_usage, options); - if (force && (list || delete)) - usage_msg_opt("-f cannot be used with -d or -l", + if (force && cmdmode != MODE_REPLACE && cmdmode != MODE_EDIT) + usage_msg_opt("-f only makes sense when writing a replacement", git_replace_usage, options); - /* Delete refs */ - if (delete) { + switch (cmdmode) { + case MODE_DELETE: if (argc < 1) usage_msg_opt("-d needs at least one argument", git_replace_usage, options); return for_each_replace_name(argv, delete_replace_ref); - } - /* Replace object */ - if (!list && argc) { + case MODE_REPLACE: if (argc != 2) usage_msg_opt("bad number of arguments", git_replace_usage, options); - if (format) - usage_msg_opt("--format cannot be used when not listing", - git_replace_usage, options); return replace_object(argv[0], argv[1], force); - } - /* List refs, even if "list" is not set */ - if (argc > 1) - usage_msg_opt("only one pattern can be given with -l", - git_replace_usage, options); - if (force) - usage_msg_opt("-f needs some arguments", - git_replace_usage, options); + case MODE_EDIT: + if (argc != 1) + usage_msg_opt("-e needs exactly one argument", + git_replace_usage, options); + return edit_and_replace(argv[0], force); + + case MODE_LIST: + if (argc > 1) + usage_msg_opt("only one pattern can be given with -l", + git_replace_usage, options); + return list_replace_refs(argv[0], format); - return list_replace_refs(argv[0], format); + default: + die("BUG: invalid cmdmode %d", (int)cmdmode); + } } diff --git a/builtin/reset.c b/builtin/reset.c index f4e087596b..850d53229a 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -93,7 +93,7 @@ static int reset_index(const unsigned char *sha1, int reset_type, int quiet) static void print_new_head_line(struct commit *commit) { const char *hex, *body; - char *msg; + const char *msg; hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); printf(_("HEAD is now at %s"), hex); @@ -109,7 +109,7 @@ static void print_new_head_line(struct commit *commit) } else printf("\n"); - logmsg_free(msg, commit); + unuse_commit_buffer(commit, msg); } static void update_index_from_diff(struct diff_queue_struct *q, @@ -252,11 +252,13 @@ static int reset_refs(const char *rev, const unsigned char *sha1) if (!get_sha1("HEAD", sha1_orig)) { orig = sha1_orig; set_reflog_message(&msg, "updating ORIG_HEAD", NULL); - update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); + update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, + UPDATE_REFS_MSG_ON_ERR); } else if (old_orig) delete_ref("ORIG_HEAD", old_orig, 0); set_reflog_message(&msg, "updating HEAD", rev); - update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR); + update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, + UPDATE_REFS_MSG_ON_ERR); strbuf_release(&msg); return update_ref_status; } diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 9f92905379..ff84a825ff 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -106,7 +106,7 @@ static void show_commit(struct commit *commit, void *data) else putchar('\n'); - if (revs->verbose_header && commit->buffer) { + if (revs->verbose_header && get_cached_commit_buffer(commit, NULL)) { struct strbuf buf = STRBUF_INIT; struct pretty_print_context ctx = {0}; ctx.abbrev = revs->abbrev; @@ -173,8 +173,7 @@ static void finish_commit(struct commit *commit, void *data) free_commit_list(commit->parents); commit->parents = NULL; } - free(commit->buffer); - commit->buffer = NULL; + free_commit_buffer(commit); } static void finish_object(struct object *obj, diff --git a/builtin/show-branch.c b/builtin/show-branch.c index d87317290c..5fd4e4e488 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -755,7 +755,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) } for (i = 0; i < reflog; i++) { - char *logmsg, *m; + char *logmsg; const char *msg; unsigned long timestamp; int tz; @@ -770,11 +770,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) msg = "(none)"; else msg++; - m = xmalloc(strlen(msg) + 200); - sprintf(m, "(%s) %s", - show_date(timestamp, tz, 1), - msg); - reflog_msg[i] = m; + reflog_msg[i] = xstrfmt("(%s) %s", + show_date(timestamp, tz, 1), + msg); free(logmsg); sprintf(nth_desc, "%s@{%d}", *av, base+i); append_ref(nth_desc, sha1, 1); diff --git a/builtin/tag.c b/builtin/tag.c index 6c7c6bde9d..ef76556338 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -80,11 +80,19 @@ static int in_commit_list(const struct commit_list *want, struct commit *c) return 0; } -static int contains_recurse(struct commit *candidate, +enum contains_result { + CONTAINS_UNKNOWN = -1, + CONTAINS_NO = 0, + CONTAINS_YES = 1 +}; + +/* + * Test whether the candidate or one of its parents is contained in the list. + * Do not recurse to find out, though, but return -1 if inconclusive. + */ +static enum contains_result contains_test(struct commit *candidate, const struct commit_list *want) { - struct commit_list *p; - /* was it previously marked as containing a want commit? */ if (candidate->object.flags & TMP_MARK) return 1; @@ -92,26 +100,78 @@ static int contains_recurse(struct commit *candidate, if (candidate->object.flags & UNINTERESTING) return 0; /* or are we it? */ - if (in_commit_list(want, candidate)) + if (in_commit_list(want, candidate)) { + candidate->object.flags |= TMP_MARK; return 1; + } if (parse_commit(candidate) < 0) return 0; - /* Otherwise recurse and mark ourselves for future traversals. */ - for (p = candidate->parents; p; p = p->next) { - if (contains_recurse(p->item, want)) { - candidate->object.flags |= TMP_MARK; - return 1; - } - } - candidate->object.flags |= UNINTERESTING; - return 0; + return -1; } -static int contains(struct commit *candidate, const struct commit_list *want) +/* + * Mimicking the real stack, this stack lives on the heap, avoiding stack + * overflows. + * + * At each recursion step, the stack items points to the commits whose + * ancestors are to be inspected. + */ +struct stack { + int nr, alloc; + struct stack_entry { + struct commit *commit; + struct commit_list *parents; + } *stack; +}; + +static void push_to_stack(struct commit *candidate, struct stack *stack) +{ + int index = stack->nr++; + ALLOC_GROW(stack->stack, stack->nr, stack->alloc); + stack->stack[index].commit = candidate; + stack->stack[index].parents = candidate->parents; +} + +static enum contains_result contains(struct commit *candidate, + const struct commit_list *want) { - return contains_recurse(candidate, want); + struct stack stack = { 0, 0, NULL }; + int result = contains_test(candidate, want); + + if (result != CONTAINS_UNKNOWN) + return result; + + push_to_stack(candidate, &stack); + while (stack.nr) { + struct stack_entry *entry = &stack.stack[stack.nr - 1]; + struct commit *commit = entry->commit; + struct commit_list *parents = entry->parents; + + if (!parents) { + commit->object.flags |= UNINTERESTING; + stack.nr--; + } + /* + * If we just popped the stack, parents->item has been marked, + * therefore contains_test will return a meaningful 0 or 1. + */ + else switch (contains_test(parents->item, want)) { + case CONTAINS_YES: + commit->object.flags |= TMP_MARK; + stack.nr--; + break; + case CONTAINS_NO: + entry->parents = parents->next; + break; + case CONTAINS_UNKNOWN: + push_to_stack(parents->item, &stack); + break; + } + } + free(stack.stack); + return contains_test(candidate, want); } static void show_tag_lines(const unsigned char *sha1, int lines) @@ -278,11 +338,11 @@ static int do_sign(struct strbuf *buffer) } static const char tag_template[] = - N_("\nWrite a tag message\n" + N_("\nWrite a message for tag:\n %s\n" "Lines starting with '%c' will be ignored.\n"); static const char tag_template_nocleanup[] = - N_("\nWrite a tag message\n" + N_("\nWrite a message for tag:\n %s\n" "Lines starting with '%c' will be kept; you may remove them" " yourself if you want to.\n"); @@ -378,9 +438,9 @@ static void create_tag(const unsigned char *object, const char *tag, struct strbuf buf = STRBUF_INIT; strbuf_addch(&buf, '\n'); if (opt->cleanup_mode == CLEANUP_ALL) - strbuf_commented_addf(&buf, _(tag_template), comment_line_char); + strbuf_commented_addf(&buf, _(tag_template), tag, comment_line_char); else - strbuf_commented_addf(&buf, _(tag_template_nocleanup), comment_line_char); + strbuf_commented_addf(&buf, _(tag_template_nocleanup), tag, comment_line_char); write_or_die(fd, buf.buf, buf.len); strbuf_release(&buf); } diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 5c208bb1fc..405267f6e2 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -12,238 +12,329 @@ static const char * const git_update_ref_usage[] = { NULL }; -static int updates_alloc; -static int updates_count; -static const struct ref_update **updates; +static struct ref_transaction *transaction; static char line_termination = '\n'; static int update_flags; -static struct ref_update *update_alloc(void) -{ - struct ref_update *update; - - /* Allocate and zero-init a struct ref_update */ - update = xcalloc(1, sizeof(*update)); - ALLOC_GROW(updates, updates_count + 1, updates_alloc); - updates[updates_count++] = update; - - /* Store and reset accumulated options */ - update->flags = update_flags; - update_flags = 0; - - return update; -} - -static void update_store_ref_name(struct ref_update *update, - const char *ref_name) -{ - if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL)) - die("invalid ref format: %s", ref_name); - update->ref_name = xstrdup(ref_name); -} - -static void update_store_new_sha1(struct ref_update *update, - const char *newvalue) -{ - if (*newvalue && get_sha1(newvalue, update->new_sha1)) - die("invalid new value for ref %s: %s", - update->ref_name, newvalue); -} - -static void update_store_old_sha1(struct ref_update *update, - const char *oldvalue) -{ - if (*oldvalue && get_sha1(oldvalue, update->old_sha1)) - die("invalid old value for ref %s: %s", - update->ref_name, oldvalue); - - /* We have an old value if non-empty, or if empty without -z */ - update->have_old = *oldvalue || line_termination; -} - +/* + * Parse one whitespace- or NUL-terminated, possibly C-quoted argument + * and append the result to arg. Return a pointer to the terminator. + * Die if there is an error in how the argument is C-quoted. This + * function is only used if not -z. + */ static const char *parse_arg(const char *next, struct strbuf *arg) { - /* Parse SP-terminated, possibly C-quoted argument */ - if (*next != '"') + if (*next == '"') { + const char *orig = next; + + if (unquote_c_style(arg, next, &next)) + die("badly quoted argument: %s", orig); + if (*next && !isspace(*next)) + die("unexpected character after quoted argument: %s", orig); + } else { while (*next && !isspace(*next)) strbuf_addch(arg, *next++); - else if (unquote_c_style(arg, next, &next)) - die("badly quoted argument: %s", next); + } - /* Return position after the argument */ return next; } -static const char *parse_first_arg(const char *next, struct strbuf *arg) +/* + * Parse the reference name immediately after "command SP". If not + * -z, then handle C-quoting. Return a pointer to a newly allocated + * string containing the name of the reference, or NULL if there was + * an error. Update *next to point at the character that terminates + * the argument. Die if C-quoting is malformed or the reference name + * is invalid. + */ +static char *parse_refname(struct strbuf *input, const char **next) { - /* Parse argument immediately after "command SP" */ - strbuf_reset(arg); + struct strbuf ref = STRBUF_INIT; + if (line_termination) { /* Without -z, use the next argument */ - next = parse_arg(next, arg); + *next = parse_arg(*next, &ref); } else { - /* With -z, use rest of first NUL-terminated line */ - strbuf_addstr(arg, next); - next = next + arg->len; + /* With -z, use everything up to the next NUL */ + strbuf_addstr(&ref, *next); + *next += ref.len; } - return next; + + if (!ref.len) { + strbuf_release(&ref); + return NULL; + } + + if (check_refname_format(ref.buf, REFNAME_ALLOW_ONELEVEL)) + die("invalid ref format: %s", ref.buf); + + return strbuf_detach(&ref, NULL); } -static const char *parse_next_arg(const char *next, struct strbuf *arg) +/* + * The value being parsed is <oldvalue> (as opposed to <newvalue>; the + * difference affects which error messages are generated): + */ +#define PARSE_SHA1_OLD 0x01 + +/* + * For backwards compatibility, accept an empty string for update's + * <newvalue> in binary mode to be equivalent to specifying zeros. + */ +#define PARSE_SHA1_ALLOW_EMPTY 0x02 + +/* + * Parse an argument separator followed by the next argument, if any. + * If there is an argument, convert it to a SHA-1, write it to sha1, + * set *next to point at the character terminating the argument, and + * return 0. If there is no argument at all (not even the empty + * string), return 1 and leave *next unchanged. If the value is + * provided but cannot be converted to a SHA-1, die. flags can + * include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY. + */ +static int parse_next_sha1(struct strbuf *input, const char **next, + unsigned char *sha1, + const char *command, const char *refname, + int flags) { - /* Parse next SP-terminated or NUL-terminated argument, if any */ - strbuf_reset(arg); + struct strbuf arg = STRBUF_INIT; + int ret = 0; + + if (*next == input->buf + input->len) + goto eof; + if (line_termination) { /* Without -z, consume SP and use next argument */ - if (!*next) - return NULL; - if (*next != ' ') - die("expected SP but got: %s", next); - next = parse_arg(next + 1, arg); + if (!**next || **next == line_termination) + return 1; + if (**next != ' ') + die("%s %s: expected SP but got: %s", + command, refname, *next); + (*next)++; + *next = parse_arg(*next, &arg); + if (arg.len) { + if (get_sha1(arg.buf, sha1)) + goto invalid; + } else { + /* Without -z, an empty value means all zeros: */ + hashclr(sha1); + } } else { /* With -z, read the next NUL-terminated line */ - if (*next) - die("expected NUL but got: %s", next); - if (strbuf_getline(arg, stdin, '\0') == EOF) - return NULL; - next = arg->buf + arg->len; + if (**next) + die("%s %s: expected NUL but got: %s", + command, refname, *next); + (*next)++; + if (*next == input->buf + input->len) + goto eof; + strbuf_addstr(&arg, *next); + *next += arg.len; + + if (arg.len) { + if (get_sha1(arg.buf, sha1)) + goto invalid; + } else if (flags & PARSE_SHA1_ALLOW_EMPTY) { + /* With -z, treat an empty value as all zeros: */ + warning("%s %s: missing <newvalue>, treating as zero", + command, refname); + hashclr(sha1); + } else { + /* + * With -z, an empty non-required value means + * unspecified: + */ + ret = 1; + } } - return next; + + strbuf_release(&arg); + + return ret; + + invalid: + die(flags & PARSE_SHA1_OLD ? + "%s %s: invalid <oldvalue>: %s" : + "%s %s: invalid <newvalue>: %s", + command, refname, arg.buf); + + eof: + die(flags & PARSE_SHA1_OLD ? + "%s %s: unexpected end of input when reading <oldvalue>" : + "%s %s: unexpected end of input when reading <newvalue>", + command, refname); } -static void parse_cmd_update(const char *next) + +/* + * The following five parse_cmd_*() functions parse the corresponding + * command. In each case, next points at the character following the + * command name and the following space. They each return a pointer + * to the character terminating the command, and die with an + * explanatory message if there are any parsing problems. All of + * these functions handle either text or binary format input, + * depending on how line_termination is set. + */ + +static const char *parse_cmd_update(struct strbuf *input, const char *next) { - struct strbuf ref = STRBUF_INIT; - struct strbuf newvalue = STRBUF_INIT; - struct strbuf oldvalue = STRBUF_INIT; - struct ref_update *update; + char *refname; + unsigned char new_sha1[20]; + unsigned char old_sha1[20]; + int have_old; - update = update_alloc(); + refname = parse_refname(input, &next); + if (!refname) + die("update: missing <ref>"); - if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) - update_store_ref_name(update, ref.buf); - else - die("update line missing <ref>"); + if (parse_next_sha1(input, &next, new_sha1, "update", refname, + PARSE_SHA1_ALLOW_EMPTY)) + die("update %s: missing <newvalue>", refname); - if ((next = parse_next_arg(next, &newvalue)) != NULL) - update_store_new_sha1(update, newvalue.buf); - else - die("update %s missing <newvalue>", ref.buf); + have_old = !parse_next_sha1(input, &next, old_sha1, "update", refname, + PARSE_SHA1_OLD); - if ((next = parse_next_arg(next, &oldvalue)) != NULL) - update_store_old_sha1(update, oldvalue.buf); - else if(!line_termination) - die("update %s missing [<oldvalue>] NUL", ref.buf); + if (*next != line_termination) + die("update %s: extra input: %s", refname, next); - if (next && *next) - die("update %s has extra input: %s", ref.buf, next); + ref_transaction_update(transaction, refname, new_sha1, old_sha1, + update_flags, have_old); + + update_flags = 0; + free(refname); + + return next; } -static void parse_cmd_create(const char *next) +static const char *parse_cmd_create(struct strbuf *input, const char *next) { - struct strbuf ref = STRBUF_INIT; - struct strbuf newvalue = STRBUF_INIT; - struct ref_update *update; + char *refname; + unsigned char new_sha1[20]; - update = update_alloc(); - update->have_old = 1; + refname = parse_refname(input, &next); + if (!refname) + die("create: missing <ref>"); - if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) - update_store_ref_name(update, ref.buf); - else - die("create line missing <ref>"); + if (parse_next_sha1(input, &next, new_sha1, "create", refname, 0)) + die("create %s: missing <newvalue>", refname); - if ((next = parse_next_arg(next, &newvalue)) != NULL) - update_store_new_sha1(update, newvalue.buf); - else - die("create %s missing <newvalue>", ref.buf); - if (is_null_sha1(update->new_sha1)) - die("create %s given zero new value", ref.buf); + if (is_null_sha1(new_sha1)) + die("create %s: zero <newvalue>", refname); + + if (*next != line_termination) + die("create %s: extra input: %s", refname, next); - if (next && *next) - die("create %s has extra input: %s", ref.buf, next); + ref_transaction_create(transaction, refname, new_sha1, update_flags); + + update_flags = 0; + free(refname); + + return next; } -static void parse_cmd_delete(const char *next) +static const char *parse_cmd_delete(struct strbuf *input, const char *next) { - struct strbuf ref = STRBUF_INIT; - struct strbuf oldvalue = STRBUF_INIT; - struct ref_update *update; + char *refname; + unsigned char old_sha1[20]; + int have_old; - update = update_alloc(); + refname = parse_refname(input, &next); + if (!refname) + die("delete: missing <ref>"); - if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) - update_store_ref_name(update, ref.buf); - else - die("delete line missing <ref>"); + if (parse_next_sha1(input, &next, old_sha1, "delete", refname, + PARSE_SHA1_OLD)) { + have_old = 0; + } else { + if (is_null_sha1(old_sha1)) + die("delete %s: zero <oldvalue>", refname); + have_old = 1; + } + + if (*next != line_termination) + die("delete %s: extra input: %s", refname, next); - if ((next = parse_next_arg(next, &oldvalue)) != NULL) - update_store_old_sha1(update, oldvalue.buf); - else if(!line_termination) - die("delete %s missing [<oldvalue>] NUL", ref.buf); - if (update->have_old && is_null_sha1(update->old_sha1)) - die("delete %s given zero old value", ref.buf); + ref_transaction_delete(transaction, refname, old_sha1, + update_flags, have_old); - if (next && *next) - die("delete %s has extra input: %s", ref.buf, next); + update_flags = 0; + free(refname); + + return next; } -static void parse_cmd_verify(const char *next) +static const char *parse_cmd_verify(struct strbuf *input, const char *next) { - struct strbuf ref = STRBUF_INIT; - struct strbuf value = STRBUF_INIT; - struct ref_update *update; + char *refname; + unsigned char new_sha1[20]; + unsigned char old_sha1[20]; + int have_old; + + refname = parse_refname(input, &next); + if (!refname) + die("verify: missing <ref>"); + + if (parse_next_sha1(input, &next, old_sha1, "verify", refname, + PARSE_SHA1_OLD)) { + hashclr(new_sha1); + have_old = 0; + } else { + hashcpy(new_sha1, old_sha1); + have_old = 1; + } - update = update_alloc(); + if (*next != line_termination) + die("verify %s: extra input: %s", refname, next); - if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) - update_store_ref_name(update, ref.buf); - else - die("verify line missing <ref>"); + ref_transaction_update(transaction, refname, new_sha1, old_sha1, + update_flags, have_old); - if ((next = parse_next_arg(next, &value)) != NULL) { - update_store_old_sha1(update, value.buf); - update_store_new_sha1(update, value.buf); - } else if(!line_termination) - die("verify %s missing [<oldvalue>] NUL", ref.buf); + update_flags = 0; + free(refname); - if (next && *next) - die("verify %s has extra input: %s", ref.buf, next); + return next; } -static void parse_cmd_option(const char *next) +static const char *parse_cmd_option(struct strbuf *input, const char *next) { - if (!strcmp(next, "no-deref")) + if (!strncmp(next, "no-deref", 8) && next[8] == line_termination) update_flags |= REF_NODEREF; else die("option unknown: %s", next); + return next + 8; } static void update_refs_stdin(void) { - struct strbuf cmd = STRBUF_INIT; + struct strbuf input = STRBUF_INIT; + const char *next; + if (strbuf_read(&input, 0, 1000) < 0) + die_errno("could not read from stdin"); + next = input.buf; /* Read each line dispatch its command */ - while (strbuf_getline(&cmd, stdin, line_termination) != EOF) - if (!cmd.buf[0]) + while (next < input.buf + input.len) { + if (*next == line_termination) die("empty command in input"); - else if (isspace(*cmd.buf)) - die("whitespace before command: %s", cmd.buf); - else if (starts_with(cmd.buf, "update ")) - parse_cmd_update(cmd.buf + 7); - else if (starts_with(cmd.buf, "create ")) - parse_cmd_create(cmd.buf + 7); - else if (starts_with(cmd.buf, "delete ")) - parse_cmd_delete(cmd.buf + 7); - else if (starts_with(cmd.buf, "verify ")) - parse_cmd_verify(cmd.buf + 7); - else if (starts_with(cmd.buf, "option ")) - parse_cmd_option(cmd.buf + 7); + else if (isspace(*next)) + die("whitespace before command: %s", next); + else if (starts_with(next, "update ")) + next = parse_cmd_update(&input, next + 7); + else if (starts_with(next, "create ")) + next = parse_cmd_create(&input, next + 7); + else if (starts_with(next, "delete ")) + next = parse_cmd_delete(&input, next + 7); + else if (starts_with(next, "verify ")) + next = parse_cmd_verify(&input, next + 7); + else if (starts_with(next, "option ")) + next = parse_cmd_option(&input, next + 7); else - die("unknown command: %s", cmd.buf); + die("unknown command: %s", next); + + next++; + } - strbuf_release(&cmd); + strbuf_release(&input); } int cmd_update_ref(int argc, const char **argv, const char *prefix) @@ -268,12 +359,17 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) die("Refusing to perform update with empty message."); if (read_stdin) { + int ret; + transaction = ref_transaction_begin(); + if (delete || no_deref || argc > 0) usage_with_options(git_update_ref_usage, options); if (end_null) line_termination = '\0'; update_refs_stdin(); - return update_refs(msg, updates, updates_count, DIE_ON_ERR); + ret = ref_transaction_commit(transaction, msg, + UPDATE_REFS_DIE_ON_ERR); + return ret; } if (end_null) @@ -305,5 +401,5 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) return delete_ref(refname, oldval ? oldsha1 : NULL, flags); else return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, - flags, DIE_ON_ERR); + flags, UPDATE_REFS_DIE_ON_ERR); } diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c new file mode 100644 index 0000000000..b0f85042b2 --- /dev/null +++ b/builtin/verify-commit.c @@ -0,0 +1,93 @@ +/* + * Builtin "git commit-commit" + * + * Copyright (c) 2014 Michael J Gruber <git@drmicha.warpmail.net> + * + * Based on git-verify-tag + */ +#include "cache.h" +#include "builtin.h" +#include "commit.h" +#include "run-command.h" +#include <signal.h> +#include "parse-options.h" +#include "gpg-interface.h" + +static const char * const verify_commit_usage[] = { + N_("git verify-commit [-v|--verbose] <commit>..."), + NULL +}; + +static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, int verbose) +{ + struct signature_check signature_check; + + memset(&signature_check, 0, sizeof(signature_check)); + + check_commit_signature(lookup_commit(sha1), &signature_check); + + if (verbose && signature_check.payload) + fputs(signature_check.payload, stdout); + + if (signature_check.gpg_output) + fputs(signature_check.gpg_output, stderr); + + signature_check_clear(&signature_check); + return signature_check.result != 'G'; +} + +static int verify_commit(const char *name, int verbose) +{ + enum object_type type; + unsigned char sha1[20]; + char *buf; + unsigned long size; + int ret; + + if (get_sha1(name, sha1)) + return error("commit '%s' not found.", name); + + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + return error("%s: unable to read file.", name); + if (type != OBJ_COMMIT) + return error("%s: cannot verify a non-commit object of type %s.", + name, typename(type)); + + ret = run_gpg_verify(sha1, buf, size, verbose); + + free(buf); + return ret; +} + +static int git_verify_commit_config(const char *var, const char *value, void *cb) +{ + int status = git_gpg_config(var, value, cb); + if (status) + return status; + return git_default_config(var, value, cb); +} + +int cmd_verify_commit(int argc, const char **argv, const char *prefix) +{ + int i = 1, verbose = 0, had_error = 0; + const struct option verify_commit_options[] = { + OPT__VERBOSE(&verbose, N_("print commit contents")), + OPT_END() + }; + + git_config(git_verify_commit_config, NULL); + + argc = parse_options(argc, argv, prefix, verify_commit_options, + verify_commit_usage, PARSE_OPT_KEEP_ARGV0); + if (argc <= i) + usage_with_options(verify_commit_usage, verify_commit_options); + + /* sometimes the program was terminated because this signal + * was received in the process of writing the gpg input: */ + signal(SIGPIPE, SIG_IGN); + while (i < argc) + if (verify_commit(argv[i++], verbose)) + had_error = 1; + return had_error; +} |