diff options
Diffstat (limited to 'builtin')
44 files changed, 2037 insertions, 986 deletions
diff --git a/builtin/add.c b/builtin/add.c index 459208a326..4baf3a5635 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -299,7 +299,6 @@ static int add_files(struct dir_struct *dir, int flags) int cmd_add(int argc, const char **argv, const char *prefix) { int exit_status = 0; - int newfd; struct pathspec pathspec; struct dir_struct dir; int flags; @@ -345,7 +344,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) add_new_files = !take_worktree_changes && !refresh_only; require_pathspec = !take_worktree_changes; - newfd = hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, 1); flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | (show_only ? ADD_CACHE_PRETEND : 0) | @@ -443,8 +442,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) finish: if (active_cache_changed) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("Unable to write new index file")); } diff --git a/builtin/apply.c b/builtin/apply.c index 622ee1674a..6b7c764918 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -1075,7 +1075,7 @@ static int gitdiff_index(const char *line, struct patch *patch) line = ptr + 2; ptr = strchr(line, ' '); - eol = strchr(line, '\n'); + eol = strchrnul(line, '\n'); if (!ptr || eol < ptr) ptr = eol; @@ -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; } @@ -1922,6 +1920,66 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch) return used; } +static void prefix_one(char **name) +{ + char *old_name = *name; + if (!old_name) + return; + *name = xstrdup(prefix_filename(prefix, prefix_length, *name)); + free(old_name); +} + +static void prefix_patch(struct patch *p) +{ + if (!prefix || p->is_toplevel_relative) + return; + prefix_one(&p->new_name); + prefix_one(&p->old_name); +} + +/* + * include/exclude + */ + +static struct string_list limit_by_name; +static int has_include; +static void add_name_limit(const char *name, int exclude) +{ + struct string_list_item *it; + + it = string_list_append(&limit_by_name, name); + it->util = exclude ? NULL : (void *) 1; +} + +static int use_patch(struct patch *p) +{ + const char *pathname = p->new_name ? p->new_name : p->old_name; + int i; + + /* Paths outside are not touched regardless of "--include" */ + if (0 < prefix_length) { + int pathlen = strlen(pathname); + if (pathlen <= prefix_length || + memcmp(prefix, pathname, prefix_length)) + return 0; + } + + /* See if it matches any of exclude/include rule */ + for (i = 0; i < limit_by_name.nr; i++) { + struct string_list_item *it = &limit_by_name.items[i]; + if (!wildmatch(it->string, pathname, 0, NULL)) + return (it->util != NULL); + } + + /* + * If we had any include, a path that does not match any rule is + * not used. Otherwise, we saw bunch of exclude rules (or none) + * and such a path is used. + */ + return !has_include; +} + + /* * Read the patch text in "buffer" that extends for "size" bytes; stop * reading after seeing a single patch (i.e. changes to a single file). @@ -1937,9 +1995,14 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) if (offset < 0) return offset; - patch->ws_rule = whitespace_rule(patch->new_name - ? patch->new_name - : patch->old_name); + prefix_patch(patch); + + if (!use_patch(patch)) + patch->ws_rule = 0; + else + patch->ws_rule = whitespace_rule(patch->new_name + ? patch->new_name + : patch->old_name); patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch); @@ -3084,13 +3147,15 @@ static void prepare_fn_table(struct patch *patch) } } -static int checkout_target(struct cache_entry *ce, struct stat *st) +static int checkout_target(struct index_state *istate, + struct cache_entry *ce, struct stat *st) { struct checkout costate; memset(&costate, 0, sizeof(costate)); costate.base_dir = ""; costate.refresh_cache = 1; + costate.istate = istate; if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st)) return error(_("cannot checkout %s"), ce->name); return 0; @@ -3257,7 +3322,7 @@ static int load_current(struct image *image, struct patch *patch) if (lstat(name, &st)) { if (errno != ENOENT) return error(_("%s: %s"), name, strerror(errno)); - if (checkout_target(ce, &st)) + if (checkout_target(&the_index, ce, &st)) return -1; } if (verify_index_match(ce, &st)) @@ -3411,7 +3476,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s } *ce = active_cache[pos]; if (stat_ret < 0) { - if (checkout_target(*ce, st)) + if (checkout_target(&the_index, *ce, st)) return -1; } if (!cached && verify_index_match(*ce, st)) @@ -3644,7 +3709,7 @@ static void build_fake_ancestor(struct patch *list, const char *filename) { struct patch *patch; struct index_state result = { NULL }; - int fd; + static struct lock_file lock; /* Once we start supporting the reverse patch, it may be * worth showing the new sha1 prefix, but until then... @@ -3682,8 +3747,8 @@ static void build_fake_ancestor(struct patch *list, const char *filename) die ("Could not add %s to temporary index", name); } - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0 || write_index(&result, fd) || close(fd)) + hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR); + if (write_locked_index(&result, &lock, COMMIT_LOCK)) die ("Could not write temporary index to %s", filename); discard_index(&result); @@ -3845,9 +3910,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) { @@ -4126,64 +4192,6 @@ static int write_out_results(struct patch *list) static struct lock_file lock_file; -static struct string_list limit_by_name; -static int has_include; -static void add_name_limit(const char *name, int exclude) -{ - struct string_list_item *it; - - it = string_list_append(&limit_by_name, name); - it->util = exclude ? NULL : (void *) 1; -} - -static int use_patch(struct patch *p) -{ - const char *pathname = p->new_name ? p->new_name : p->old_name; - int i; - - /* Paths outside are not touched regardless of "--include" */ - if (0 < prefix_length) { - int pathlen = strlen(pathname); - if (pathlen <= prefix_length || - memcmp(prefix, pathname, prefix_length)) - return 0; - } - - /* See if it matches any of exclude/include rule */ - for (i = 0; i < limit_by_name.nr; i++) { - struct string_list_item *it = &limit_by_name.items[i]; - if (!wildmatch(it->string, pathname, 0, NULL)) - return (it->util != NULL); - } - - /* - * If we had any include, a path that does not match any rule is - * not used. Otherwise, we saw bunch of exclude rules (or none) - * and such a path is used. - */ - return !has_include; -} - - -static void prefix_one(char **name) -{ - char *old_name = *name; - if (!old_name) - return; - *name = xstrdup(prefix_filename(prefix, prefix_length, *name)); - free(old_name); -} - -static void prefix_patches(struct patch *p) -{ - if (!prefix || p->is_toplevel_relative) - return; - for ( ; p; p = p->next) { - prefix_one(&p->new_name); - prefix_one(&p->old_name); - } -} - #define INACCURATE_EOF (1<<0) #define RECOUNT (1<<1) @@ -4209,8 +4217,6 @@ static int apply_patch(int fd, const char *filename, int options) break; if (apply_in_reverse) reverse_patches(patch); - if (prefix) - prefix_patches(patch); if (use_patch(patch)) { patch_stats(patch); *listp = patch; @@ -4501,8 +4507,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) } if (update_index) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("Unable to write new index file")); } diff --git a/builtin/blame.c b/builtin/blame.c index eefd6bc2e1..17d30d00aa 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); } /* @@ -1177,11 +1371,29 @@ static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit static int num_scapegoats(struct rev_info *revs, struct commit *commit) { - int cnt; struct commit_list *l = first_scapegoat(revs, commit); - for (cnt = 0; l; l = l->next) - cnt++; - return cnt; + return commit_list_count(l); +} + +/* 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 @@ -1194,6 +1406,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 +1469,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]); @@ -1481,14 +1728,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 +1746,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 +1789,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 +1868,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 +1982,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 +2005,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 +2022,19 @@ static int prepare_lines(struct scoreboard *sb) const char *end = buf + len; const char *p; int *lineno; - int num = 0, incomplete = 0; - - for (p = buf;;) { - p = memchr(p, '\n', end - p); - if (p) { - p++; - num++; - continue; - } - break; - } + int num = 0; - if (len && end[-1] != '\n') - incomplete++; /* incomplete line at the end */ + for (p = buf; p < end; p = get_next_line(p, end)) + num++; - sb->lineno = xmalloc(sizeof(*sb->lineno) * (num + incomplete + 1)); - lineno = sb->lineno; + sb->lineno = lineno = xmalloc(sizeof(*sb->lineno) * (num + 1)); - *lineno++ = 0; - for (p = buf;;) { - p = memchr(p, '\n', end - p); - if (p) { - p++; - *lineno++ = p - buf; - continue; - } - break; - } + for (p = buf; p < end; p = get_next_line(p, end)) + *lineno++ = p - buf; - if (incomplete) - *lineno++ = len; + *lineno = len; - sb->num_lines = num + incomplete; + sb->num_lines = num; return sb->num_lines; } @@ -2109,7 +2352,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 @@ -2143,7 +2385,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, * right now, but someday we might optimize diff-index --cached * with cache-tree information. */ - cache_tree_invalidate_path(active_cache_tree, path); + cache_tree_invalidate_path(&the_index, path); return commit; } @@ -2427,12 +2669,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) { /* @@ -2518,12 +2764,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); @@ -2536,6 +2786,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-index.c b/builtin/checkout-index.c index 61e75eb60c..05edd9e1df 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -135,6 +135,7 @@ static int option_parse_u(const struct option *opt, int *newfd = opt->value; state.refresh_cache = 1; + state.istate = &the_index; if (*newfd < 0) *newfd = hold_locked_index(&lock_file, 1); return 0; @@ -279,8 +280,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) checkout_all(prefix, prefix_length); if (0 <= newfd && - (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file))) + write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die("Unable to write new index file"); return 0; } diff --git a/builtin/checkout.c b/builtin/checkout.c index 07cf555309..f71e74531d 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -225,7 +225,6 @@ static int checkout_paths(const struct checkout_opts *opts, int flag; struct commit *head; int errs = 0; - int newfd; struct lock_file *lock_file; if (opts->track != BRANCH_TRACK_UNSPECIFIED) @@ -256,7 +255,7 @@ static int checkout_paths(const struct checkout_opts *opts, lock_file = xcalloc(1, sizeof(struct lock_file)); - newfd = hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, 1); if (read_cache_preload(&opts->pathspec) < 0) return error(_("corrupt index file")); @@ -337,6 +336,7 @@ static int checkout_paths(const struct checkout_opts *opts, memset(&state, 0, sizeof(state)); state.force = 1; state.refresh_cache = 1; + state.istate = &the_index; for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; if (ce->ce_flags & CE_MATCHED) { @@ -352,8 +352,7 @@ static int checkout_paths(const struct checkout_opts *opts, } } - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); read_ref_full("HEAD", rev, 0, &flag); @@ -444,8 +443,8 @@ static int merge_working_tree(const struct checkout_opts *opts, { int ret; struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - int newfd = hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, 1); if (read_cache_preload(NULL) < 0) return error(_("corrupt index file")); @@ -553,8 +552,7 @@ static int merge_working_tree(const struct checkout_opts *opts, } } - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); if (!opts->force && !opts->quiet) @@ -624,7 +622,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 +649,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 +774,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 1032563e5f..3beeea6ec0 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -67,7 +67,7 @@ struct menu_item { char hotkey; const char *title; int selected; - int (*fn)(); + int (*fn)(void); }; enum menu_stuff_type { diff --git a/builtin/clone.c b/builtin/clone.c index 545105a86f..bbd169ceb4 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); } } @@ -616,7 +617,7 @@ static int checkout(void) struct unpack_trees_options opts; struct tree *tree; struct tree_desc t; - int err = 0, fd; + int err = 0; if (option_no_checkout) return 0; @@ -640,7 +641,7 @@ static int checkout(void) setup_work_tree(); lock_file = xcalloc(1, sizeof(struct lock_file)); - fd = hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, 1); memset(&opts, 0, sizeof opts); opts.update = 1; @@ -656,8 +657,7 @@ static int checkout(void) if (unpack_trees(1, &t, &opts) < 0) die(_("unable to checkout working tree")); - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(lock_file)) + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1), @@ -702,9 +702,12 @@ static void write_refspec_config(const char* src_ref_prefix, 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 @@ -796,18 +799,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) die(_("repository '%s' does not exist"), repo_name); else repo = repo_name; - is_local = option_local != 0 && path && !is_bundle; - if (is_local) { - if (option_depth) - warning(_("--depth is ignored in local clones; use file:// instead.")); - if (!access(mkpath("%s/shallow", path), F_OK)) { - if (option_local > 0) - warning(_("source repository is shallow, ignoring --local")); - is_local = 0; - } - } - if (option_local > 0 && !is_local) - warning(_("--local is ignored")); /* no need to be strict, transport_set_option() will validate it again */ if (option_depth && atoi(option_depth) < 1) @@ -900,6 +891,19 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote = remote_get(option_origin); transport = transport_get(remote, remote->url[0]); + path = get_repo_path(remote->url[0], &is_bundle); + is_local = option_local != 0 && path && !is_bundle; + if (is_local) { + if (option_depth) + warning(_("--depth is ignored in local clones; use file:// instead.")); + if (!access(mkpath("%s/shallow", path), F_OK)) { + if (option_local > 0) + warning(_("source repository is shallow, ignoring --local")); + is_local = 0; + } + } + if (option_local > 0 && !is_local) + warning(_("--local is ignored")); transport->cloning = 1; if (!transport->get_refs_list || (!is_local && !transport->fetch)) diff --git a/builtin/commit.c b/builtin/commit.c index 39cf8976e3..5ed60364ce 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -305,7 +305,6 @@ static void refresh_cache_or_die(int refresh_flags) static char *prepare_index(int argc, const char **argv, const char *prefix, const struct commit *current_head, int is_status) { - int fd; struct string_list partial; struct pathspec pathspec; int refresh_flags = REFRESH_QUIET; @@ -321,12 +320,11 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, if (interactive) { char *old_index_env = NULL; - fd = hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, 1); refresh_cache_or_die(refresh_flags); - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&index_lock)) + if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to create temporary index")); old_index_env = getenv(INDEX_ENVIRONMENT); @@ -360,12 +358,11 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, * (B) on failure, rollback the real index. */ if (all || (also && pathspec.nr)) { - fd = hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, 1); add_files_to_cache(also ? prefix : NULL, &pathspec, 0); refresh_cache_or_die(refresh_flags); update_main_cache_tree(WRITE_TREE_SILENT); - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&index_lock)) + if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to write new_index file")); commit_style = COMMIT_NORMAL; return index_lock.filename; @@ -381,12 +378,12 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, * We still need to refresh the index here. */ if (!only && !pathspec.nr) { - fd = hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, 1); refresh_cache_or_die(refresh_flags); if (active_cache_changed) { update_main_cache_tree(WRITE_TREE_SILENT); - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(&index_lock)) + if (write_locked_index(&the_index, &index_lock, + COMMIT_LOCK)) die(_("unable to write new_index file")); } else { rollback_lock_file(&index_lock); @@ -423,8 +420,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, die(_("cannot do a partial commit during a cherry-pick.")); } - memset(&partial, 0, sizeof(partial)); - partial.strdup_strings = 1; + string_list_init(&partial, 1); if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec)) exit(1); @@ -432,24 +428,22 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, if (read_cache() < 0) die(_("cannot read the index")); - fd = hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, 1); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&index_lock)) + if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to write new_index file")); - fd = hold_lock_file_for_update(&false_lock, - git_path("next-index-%"PRIuMAX, - (uintmax_t) getpid()), - LOCK_DIE_ON_ERROR); + hold_lock_file_for_update(&false_lock, + git_path("next-index-%"PRIuMAX, + (uintmax_t) getpid()), + LOCK_DIE_ON_ERROR); create_base_index(current_head); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&false_lock)) + if (write_locked_index(&the_index, &false_lock, CLOSE_LOCK)) die(_("unable to write temporary index file")); discard_cache(); @@ -526,10 +520,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 +587,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 +602,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) { - char *ket = strrchr(string, '>'); - if (!ket || ket[1] != ' ') - die(_("Malformed ident string: '%s'"), string); - *++ket = '\0'; - return ket; + 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 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, @@ -651,7 +701,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, char *buffer; buffer = strstr(use_message_buffer, "\n\n"); if (buffer) - strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); + strbuf_addstr(&sb, buffer + 2); hook_arg1 = "commit"; hook_arg2 = use_message; } else if (fixup_message) { @@ -747,6 +797,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 +806,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 +847,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 +1014,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 +1023,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 +1428,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); @@ -1685,6 +1751,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 +1764,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..7bba516383 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'; @@ -599,7 +586,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); return git_config_set_multivar_in_file(given_config_source.file, - argv[0], value, "^$", 0); + argv[0], value, + CONFIG_REGEX_NONE, 0); } else if (actions == ACTION_REPLACE_ALL) { check_write(); diff --git a/builtin/describe.c b/builtin/describe.c index 24d740c8b1..ee6a3b998f 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -56,18 +56,9 @@ static int commit_name_cmp(const struct commit_name *cn1, return hashcmp(cn1->peeled, peeled ? peeled : cn2->peeled); } -static inline unsigned int hash_sha1(const unsigned char *sha1) -{ - unsigned int hash; - memcpy(&hash, sha1, sizeof(hash)); - return hash; -} - static inline struct commit_name *find_commit_name(const unsigned char *peeled) { - struct commit_name key; - hashmap_entry_init(&key, hash_sha1(peeled)); - return hashmap_get(&names, &key, peeled); + return hashmap_get_from_hash(&names, sha1hash(peeled), peeled); } static int replace_name(struct commit_name *e, @@ -114,7 +105,7 @@ static void add_to_known_names(const char *path, if (!e) { e = xmalloc(sizeof(struct commit_name)); hashcpy(e->peeled, peeled); - hashmap_entry_init(e, hash_sha1(peeled)); + hashmap_entry_init(e, sha1hash(peeled)); hashmap_add(&names, e); e->path = NULL; } diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index dddd0f9188..1c4ad6223e 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 05d161f19f..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) @@ -528,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.", @@ -664,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; @@ -671,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")), @@ -691,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() }; @@ -704,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"); @@ -736,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); @@ -743,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..159fb7e916 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]; } @@ -1086,9 +1110,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) struct string_list list = STRING_LIST_INIT_NODUP; struct remote *remote; int result = 0; - static const char *argv_gc_auto[] = { - "gc", "--auto", NULL, - }; + struct argv_array argv_gc_auto = ARGV_ARRAY_INIT; packet_trace_identity("fetch"); @@ -1174,7 +1196,11 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) list.strdup_strings = 1; string_list_clear(&list, 0); - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL); + if (verbosity < 0) + argv_array_push(&argv_gc_auto, "--quiet"); + run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD); + argv_array_clear(&argv_gc_auto); return result; } diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index ef8b254ef2..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; @@ -300,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..47bd624696 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; @@ -283,18 +283,6 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob } } -static int num_parents(struct commit *commit) -{ - struct commit_list *parents; - int i; - - for (i = 0, parents = commit->parents; - parents; - parents = parents->next) - i++; - return i; -} - /* See grab_values */ static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { @@ -315,12 +303,12 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object } if (!strcmp(name, "numparent")) { char *s = xmalloc(40); - v->ul = num_parents(commit); + v->ul = commit_list_count(commit->parents); sprintf(s, "%lu", v->ul); v->s = s; } else if (!strcmp(name, "parent")) { - int num = num_parents(commit); + int num = commit_list_count(commit->parents); int i; struct commit_list *parents; char *s = xmalloc(41 * num + 1); diff --git a/builtin/fsck.c b/builtin/fsck.c index 8aadca160e..0928a98a71 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -388,7 +388,8 @@ static void fsck_sha1_list(void) unsigned char *sha1 = entry->sha1; sha1_list.entry[i] = NULL; - fsck_sha1(sha1); + if (fsck_sha1(sha1)) + errors_found |= ERROR_OBJECT; free(entry); } sha1_list.nr = 0; @@ -481,11 +482,6 @@ static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, in return 0; } -static int is_branch(const char *refname) -{ - return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/"); -} - static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct object *obj; @@ -493,6 +489,7 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f obj = parse_object(sha1); if (!obj) { error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1)); + errors_found |= ERROR_REACHABLE; /* We'll continue with the rest despite the error.. */ return 0; } @@ -509,7 +506,7 @@ static void get_default_heads(void) { if (head_points_at && !is_null_sha1(head_sha1)) fsck_handle_ref("HEAD", head_sha1, 0, NULL); - for_each_ref(fsck_handle_ref, NULL); + for_each_rawref(fsck_handle_ref, NULL); if (include_reflogs) for_each_reflog(fsck_handle_reflog, NULL); 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 9ca0203922..eebf1a8fc2 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -112,6 +112,10 @@ static pthread_mutex_t deepest_delta_mutex; #define deepest_delta_lock() lock_mutex(&deepest_delta_mutex) #define deepest_delta_unlock() unlock_mutex(&deepest_delta_mutex) +static pthread_mutex_t type_cas_mutex; +#define type_cas_lock() lock_mutex(&type_cas_mutex) +#define type_cas_unlock() unlock_mutex(&type_cas_mutex) + static pthread_key_t key; static inline void lock_mutex(pthread_mutex_t *mutex) @@ -135,6 +139,7 @@ static void init_thread(void) init_recursive_mutex(&read_mutex); pthread_mutex_init(&counter_mutex, NULL); pthread_mutex_init(&work_mutex, NULL); + pthread_mutex_init(&type_cas_mutex, NULL); if (show_stat) pthread_mutex_init(&deepest_delta_mutex, NULL); pthread_key_create(&key, NULL); @@ -157,6 +162,7 @@ static void cleanup_thread(void) pthread_mutex_destroy(&read_mutex); pthread_mutex_destroy(&counter_mutex); pthread_mutex_destroy(&work_mutex); + pthread_mutex_destroy(&type_cas_mutex); if (show_stat) pthread_mutex_destroy(&deepest_delta_mutex); for (i = 0; i < nr_threads; i++) @@ -862,7 +868,6 @@ static void resolve_delta(struct object_entry *delta_obj, { void *base_data, *delta_data; - delta_obj->real_type = base->obj->real_type; if (show_stat) { delta_obj->delta_depth = base->obj->delta_depth + 1; deepest_delta_lock(); @@ -888,6 +893,26 @@ static void resolve_delta(struct object_entry *delta_obj, counter_unlock(); } +/* + * Standard boolean compare-and-swap: atomically check whether "*type" is + * "want"; if so, swap in "set" and return true. Otherwise, leave it untouched + * and return false. + */ +static int compare_and_swap_type(enum object_type *type, + enum object_type want, + enum object_type set) +{ + enum object_type old; + + type_cas_lock(); + old = *type; + if (old == want) + *type = set; + type_cas_unlock(); + + return old == want; +} + static struct base_data *find_unresolved_deltas_1(struct base_data *base, struct base_data *prev_base) { @@ -915,7 +940,10 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base, struct object_entry *child = objects + deltas[base->ref_first].obj_no; struct base_data *result = alloc_base_data(); - assert(child->real_type == OBJ_REF_DELTA); + if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA, + base->obj->real_type)) + die("BUG: child->real_type != OBJ_REF_DELTA"); + resolve_delta(child, base, result); if (base->ref_first == base->ref_last && base->ofs_last == -1) free_base_data(base); @@ -929,6 +957,7 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base, struct base_data *result = alloc_base_data(); assert(child->real_type == OBJ_OFS_DELTA); + child->real_type = base->obj->real_type; resolve_delta(child, base, result); if (base->ofs_first == base->ofs_last) free_base_data(base); @@ -1505,7 +1534,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) const char *curr_index; const char *index_name = NULL, *pack_name = NULL; const char *keep_name = NULL, *keep_msg = NULL; - char *index_name_buf = NULL, *keep_name_buf = NULL; + struct strbuf index_name_buf = STRBUF_INIT, + keep_name_buf = STRBUF_INIT; struct pack_idx_entry **idx_objects; struct pack_idx_option opts; unsigned char pack_sha1[20]; @@ -1602,24 +1632,22 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (fix_thin_pack && !from_stdin) die(_("--fix-thin cannot be used without --stdin")); if (!index_name && pack_name) { - int len = strlen(pack_name); - if (!has_extension(pack_name, ".pack")) + size_t len; + if (!strip_suffix(pack_name, ".pack", &len)) die(_("packfile name '%s' does not end with '.pack'"), pack_name); - index_name_buf = xmalloc(len); - memcpy(index_name_buf, pack_name, len - 5); - strcpy(index_name_buf + len - 5, ".idx"); - index_name = index_name_buf; + strbuf_add(&index_name_buf, pack_name, len); + strbuf_addstr(&index_name_buf, ".idx"); + index_name = index_name_buf.buf; } if (keep_msg && !keep_name && pack_name) { - int len = strlen(pack_name); - if (!has_extension(pack_name, ".pack")) + size_t len; + if (!strip_suffix(pack_name, ".pack", &len)) die(_("packfile name '%s' does not end with '.pack'"), pack_name); - keep_name_buf = xmalloc(len); - memcpy(keep_name_buf, pack_name, len - 5); - strcpy(keep_name_buf + len - 5, ".keep"); - keep_name = keep_name_buf; + strbuf_add(&keep_name_buf, pack_name, len); + strbuf_addstr(&keep_name_buf, ".idx"); + keep_name = keep_name_buf.buf; } if (verify) { if (!index_name) @@ -1667,8 +1695,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) else close(input_fd); free(objects); - free(index_name_buf); - free(keep_name_buf); + strbuf_release(&index_name_buf); + strbuf_release(&keep_name_buf); if (pack_name == NULL) free((void *) curr_pack); if (index_name == NULL) diff --git a/builtin/log.c b/builtin/log.c index 4f678136d1..4389722b4b 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; } @@ -668,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 { @@ -737,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; @@ -839,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) @@ -851,7 +861,7 @@ static void add_branch_description(struct strbuf *buf, const char *branch_name) read_branch_desc(&desc, branch_name); if (desc.len) { strbuf_addch(buf, '\n'); - strbuf_add(buf, desc.buf, desc.len); + strbuf_addbuf(buf, &desc); strbuf_addch(buf, '\n'); } } @@ -861,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++) { @@ -877,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; } @@ -1228,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() }; @@ -1384,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 */ } @@ -1445,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) { diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index cf11c8d607..6a14d2985d 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -288,6 +288,22 @@ static inline int cmp_header(const struct strbuf *line, const char *hdr) line->buf[len] == ':' && isspace(line->buf[len + 1]); } +static int is_format_patch_separator(const char *line, int len) +{ + static const char SAMPLE[] = + "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n"; + const char *cp; + + if (len != strlen(SAMPLE)) + return 0; + if (!skip_prefix(line, "From ", &cp)) + return 0; + if (strspn(cp, "0123456789abcdef") != 40) + return 0; + cp += 40; + return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line)); +} + static int check_header(const struct strbuf *line, struct strbuf *hdr_data[], int overwrite) { @@ -329,7 +345,7 @@ static int check_header(const struct strbuf *line, /* for inbody stuff */ if (starts_with(line->buf, ">From") && isspace(line->buf[5])) { - ret = 1; /* Should this return 0? */ + ret = is_format_patch_separator(line->buf + 1, line->len - 1); goto check_header_out; } if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) { diff --git a/builtin/merge.c b/builtin/merge.c index 8763b2efa2..ce82eb297d 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. @@ -657,14 +657,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, struct commit_list *remoteheads, struct commit *head, const char *head_arg) { - int index_fd; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - index_fd = hold_locked_index(lock, 1); + hold_locked_index(lock, 1); refresh_cache(REFRESH_QUIET); if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(lock))) + write_locked_index(&the_index, lock, COMMIT_LOCK)) return error(_("Unable to write index.")); rollback_lock_file(lock); @@ -672,7 +670,6 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, int clean, x; struct commit *result; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - int index_fd; struct commit_list *reversed = NULL; struct merge_options o; struct commit_list *j; @@ -700,12 +697,11 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, for (j = common; j; j = j->next) commit_list_insert(j->item, &reversed); - index_fd = hold_locked_index(lock, 1); + hold_locked_index(lock, 1); clean = merge_recursive(&o, head, remoteheads->item, reversed, &result); if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(lock))) + write_locked_index(&the_index, lock, COMMIT_LOCK)) die (_("unable to write %s"), get_index_file()); rollback_lock_file(lock); return clean ? 0 : 1; @@ -843,16 +839,14 @@ static void prepare_to_commit(struct commit_list *remoteheads) static int merge_trivial(struct commit *head, struct commit_list *remoteheads) { unsigned char result_tree[20], result_commit[20]; - struct commit_list *parent = xmalloc(sizeof(*parent)); + struct commit_list *parents, **pptr = &parents; write_tree_trivial(result_tree); printf(_("Wonderful.\n")); - parent->item = head; - parent->next = xmalloc(sizeof(*parent->next)); - parent->next->item = remoteheads->item; - parent->next->next = NULL; + pptr = commit_list_append(head, pptr); + pptr = commit_list_append(remoteheads->item, pptr); prepare_to_commit(remoteheads); - if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parent, + if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents, result_commit, NULL, sign_commit)) die(_("failed to write commit object")); finish(head, remoteheads, result_commit, "In-index merge"); @@ -1222,7 +1216,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 +1276,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 +1330,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/mv.c b/builtin/mv.c index 180ef99127..6ffe540c20 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -63,7 +63,7 @@ static struct lock_file lock_file; int cmd_mv(int argc, const char **argv, const char *prefix) { - int i, newfd, gitmodules_modified = 0; + int i, gitmodules_modified = 0; int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; struct option builtin_mv_options[] = { OPT__VERBOSE(&verbose, N_("be verbose")), @@ -85,7 +85,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (--argc < 1) usage_with_options(builtin_mv_usage, builtin_mv_options); - newfd = hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, 1); if (read_cache() < 0) die(_("index file corrupt")); @@ -276,8 +276,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) stage_updated_gitmodules(); if (active_cache_changed) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("Unable to write new index file")); } 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..3eb86e0693 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -812,6 +812,7 @@ static void write_pack_file(void) fixup_pack_header_footer(fd, sha1, pack_tmp_name, nr_written, sha1, offset); close(fd); + write_bitmap_index = 0; } if (!pack_to_stdout) { @@ -2214,10 +2215,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; @@ -2498,6 +2495,7 @@ static void get_object_list(int ac, const char **av) if (get_sha1_hex(line + 10, sha1)) die("not an SHA-1 '%s'", line + 10); register_shallow(sha1); + use_bitmap_index = 0; continue; } die("not a rev '%s'", line); 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/read-tree.c b/builtin/read-tree.c index 0d7ef847a7..e7e1c33a7f 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -99,7 +99,7 @@ static struct lock_file lock_file; int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) { - int i, newfd, stage = 0; + int i, stage = 0; unsigned char sha1[20]; struct tree_desc t[MAX_UNPACK_TREES]; struct unpack_trees_options opts; @@ -149,12 +149,21 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) argc = parse_options(argc, argv, unused_prefix, read_tree_options, read_tree_usage, 0); - newfd = hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, 1); prefix_set = opts.prefix ? 1 : 0; if (1 < opts.merge + opts.reset + prefix_set) die("Which one? -m, --reset, or --prefix?"); + /* + * NEEDSWORK + * + * The old index should be read anyway even if we're going to + * destroy all index entries because we still need to preserve + * certain information such as index version or split-index + * mode. + */ + if (opts.reset || opts.merge || opts.prefix) { if (read_cache_unmerged() && (opts.prefix || opts.merge)) die("You need to resolve your current index first"); @@ -231,10 +240,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * what came from the tree. */ if (nr_trees == 1 && !opts.prefix) - prime_cache_tree(&active_cache_tree, trees[0]); + prime_cache_tree(&the_index, trees[0]); - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die("unable to write new index file"); return 0; } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index c3230817db..f93ac454b4 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -438,7 +438,7 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) uint32_t mask = 1 << (cmd->index % 32); int i; - trace_printf_key("GIT_TRACE_SHALLOW", + trace_printf_key(&trace_shallow, "shallow: update_shallow_ref %s\n", cmd->ref_name); for (i = 0; i < si->shallow->nr; i++) if (si->used_shallow[i] && @@ -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; @@ -1125,7 +1122,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) int advertise_refs = 0; int stateless_rpc = 0; int i; - char *dir = NULL; + const char *dir = NULL; struct command *commands; struct sha1_array shallow = SHA1_ARRAY_INIT; struct sha1_array ref = SHA1_ARRAY_INIT; @@ -1160,7 +1157,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) } if (dir) usage(receive_pack_usage); - dir = xstrdup(arg); + dir = arg; } if (!dir) usage(receive_pack_usage); 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 c9102e8fe9..9a4640dbf0 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/") @@ -265,16 +263,17 @@ static int config_read_branches(const char *key, const char *value, void *cb) struct string_list_item *item; struct branch_info *info; enum { REMOTE, MERGE, REBASE } type; + size_t key_len; key += 7; - if (ends_with(key, ".remote")) { - name = xstrndup(key, strlen(key) - 7); + if (strip_suffix(key, ".remote", &key_len)) { + name = xmemdupz(key, key_len); type = REMOTE; - } else if (ends_with(key, ".merge")) { - name = xstrndup(key, strlen(key) - 6); + } else if (strip_suffix(key, ".merge", &key_len)) { + name = xmemdupz(key, key_len); type = MERGE; - } else if (ends_with(key, ".rebase")) { - name = xstrndup(key, strlen(key) - 7); + } else if (strip_suffix(key, ".rebase", &key_len)) { + name = xmemdupz(key, key_len); type = REBASE; } else return 0; @@ -755,7 +754,7 @@ static int remove_branches(struct string_list *branches) branch_names = xmalloc(branches->nr * sizeof(*branch_names)); for (i = 0; i < branches->nr; i++) branch_names[i] = branches->items[i].string; - result |= repack_without_refs(branch_names, branches->nr); + result |= repack_without_refs(branch_names, branches->nr, NULL); free(branch_names); for (i = 0; i < branches->nr; i++) { @@ -1333,7 +1332,8 @@ static int prune_remote(const char *remote, int dry_run) for (i = 0; i < states.stale.nr; i++) delete_refs[i] = states.stale.items[i].util; if (!dry_run) - result |= repack_without_refs(delete_refs, states.stale.nr); + result |= repack_without_refs(delete_refs, + states.stale.nr, NULL); free(delete_refs); } diff --git a/builtin/repack.c b/builtin/repack.c index 36c1cf9c25..a77e743b94 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -10,7 +10,7 @@ static int delta_base_offset = 1; static int pack_kept_objects = -1; -static int write_bitmaps = -1; +static int write_bitmaps; static char *packdir, *packtmp; static const char *const git_repack_usage[] = { @@ -28,7 +28,8 @@ 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, "pack.writebitmaps")) { + if (!strcmp(var, "repack.writebitmaps") || + !strcmp(var, "pack.writebitmaps")) { write_bitmaps = git_config_bool(var, value); return 0; } @@ -82,16 +83,15 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list) DIR *dir; struct dirent *e; char *fname; - size_t len; if (!(dir = opendir(packdir))) return; while ((e = readdir(dir)) != NULL) { - if (!ends_with(e->d_name, ".pack")) + size_t len; + if (!strip_suffix(e->d_name, ".pack", &len)) continue; - len = strlen(e->d_name) - strlen(".pack"); fname = xmemdupz(e->d_name, len); if (!file_exists(mkpath("%s/%s.keep", packdir, fname))) @@ -195,7 +195,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_bitmaps > 0; + pack_kept_objects = write_bitmaps; packdir = mkpathdup("%s/pack", get_object_directory()); packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid()); @@ -221,9 +221,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_bitmaps >= 0) - argv_array_pushf(&cmd_args, "--%swrite-bitmap-index", - write_bitmaps ? "" : "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..294b61b97e 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -12,18 +12,22 @@ #include "builtin.h" #include "refs.h" #include "parse-options.h" +#include "run-command.h" +#include "tag.h" static const char * const git_replace_usage[] = { N_("git replace [-f] <object> <replacement>"), + N_("git replace [-f] --edit <object>"), + N_("git replace [-f] --graft <commit> [<parent>...]"), N_("git replace -d <object>..."), N_("git replace [--format=<format>] [-l [<pattern>]]"), NULL }; enum replace_format { - REPLACE_FORMAT_SHORT, - REPLACE_FORMAT_MEDIUM, - REPLACE_FORMAT_LONG + REPLACE_FORMAT_SHORT, + REPLACE_FORMAT_MEDIUM, + REPLACE_FORMAT_LONG }; struct show_data { @@ -123,26 +127,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 +166,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,14 +177,261 @@ 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". + * If "raw" is true, then the object's raw contents are printed according to + * "type". Otherwise, we pretty-print the contents for human editing. + */ +static void export_object(const unsigned char *sha1, enum object_type type, + int raw, const char *filename) +{ + struct child_process cmd = { NULL }; + 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_array_push(&cmd.args, "--no-replace-objects"); + argv_array_push(&cmd.args, "cat-file"); + if (raw) + argv_array_push(&cmd.args, typename(type)); + else + argv_array_push(&cmd.args, "-p"); + argv_array_push(&cmd.args, sha1_to_hex(sha1)); + cmd.git_cmd = 1; + cmd.out = fd; + + if (run_command(&cmd)) + die("cat-file reported failure"); +} + +/* + * 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, + int raw, const char *filename) +{ + int fd; + + fd = open(filename, O_RDONLY); + if (fd < 0) + die_errno("unable to open %s for reading", filename); + + if (!raw && 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, int raw) +{ + 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, type, raw, tmpfile); + if (launch_editor(tmpfile, NULL, NULL) < 0) + die("editing object file failed"); + import_object(new, type, raw, 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); +} + +static void replace_parents(struct strbuf *buf, int argc, const char **argv) +{ + struct strbuf new_parents = STRBUF_INIT; + const char *parent_start, *parent_end; + int i; + + /* find existing parents */ + parent_start = buf->buf; + parent_start += 46; /* "tree " + "hex sha1" + "\n" */ + parent_end = parent_start; + + while (starts_with(parent_end, "parent ")) + parent_end += 48; /* "parent " + "hex sha1" + "\n" */ + + /* prepare new parents */ + for (i = 0; i < argc; i++) { + unsigned char sha1[20]; + if (get_sha1(argv[i], sha1) < 0) + die(_("Not a valid object name: '%s'"), argv[i]); + lookup_commit_or_die(sha1, argv[i]); + strbuf_addf(&new_parents, "parent %s\n", sha1_to_hex(sha1)); + } + + /* replace existing parents with new ones */ + strbuf_splice(buf, parent_start - buf->buf, parent_end - parent_start, + new_parents.buf, new_parents.len); + + strbuf_release(&new_parents); +} + +struct check_mergetag_data { + int argc; + const char **argv; +}; + +static void check_one_mergetag(struct commit *commit, + struct commit_extra_header *extra, + void *data) +{ + struct check_mergetag_data *mergetag_data = (struct check_mergetag_data *)data; + const char *ref = mergetag_data->argv[0]; + unsigned char tag_sha1[20]; + struct tag *tag; + int i; + + hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), tag_sha1); + tag = lookup_tag(tag_sha1); + if (!tag) + die(_("bad mergetag in commit '%s'"), ref); + if (parse_tag_buffer(tag, extra->value, extra->len)) + die(_("malformed mergetag in commit '%s'"), ref); + + /* iterate over new parents */ + for (i = 1; i < mergetag_data->argc; i++) { + unsigned char sha1[20]; + if (get_sha1(mergetag_data->argv[i], sha1) < 0) + die(_("Not a valid object name: '%s'"), mergetag_data->argv[i]); + if (!hashcmp(tag->tagged->sha1, sha1)) + return; /* found */ + } + + die(_("original commit '%s' contains mergetag '%s' that is discarded; " + "use --edit instead of --graft"), ref, sha1_to_hex(tag_sha1)); +} + +static void check_mergetags(struct commit *commit, int argc, const char **argv) +{ + struct check_mergetag_data mergetag_data; + + mergetag_data.argc = argc; + mergetag_data.argv = argv; + for_each_mergetag(check_one_mergetag, commit, &mergetag_data); +} + +static int create_graft(int argc, const char **argv, int force) +{ + unsigned char old[20], new[20]; + const char *old_ref = argv[0]; + struct commit *commit; + struct strbuf buf = STRBUF_INIT; + const char *buffer; + unsigned long size; + + if (get_sha1(old_ref, old) < 0) + die(_("Not a valid object name: '%s'"), old_ref); + commit = lookup_commit_or_die(old, old_ref); + + buffer = get_commit_buffer(commit, &size); + strbuf_add(&buf, buffer, size); + unuse_commit_buffer(commit, buffer); + + replace_parents(&buf, argc - 1, &argv[1]); + + if (remove_signature(&buf)) { + warning(_("the original commit '%s' has a gpg signature."), old_ref); + warning(_("the signature will be removed in the replacement commit!")); + } + + check_mergetags(commit, argc, argv); + + if (write_sha1_file(buf.buf, buf.len, commit_type, new)) + die(_("could not write replacement commit for: '%s'"), old_ref); + + strbuf_release(&buf); + + if (!hashcmp(old, new)) + return error("new commit is the same as the old one: '%s'", sha1_to_hex(old)); + + return replace_object_sha1(old_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; + int raw = 0; const char *format = NULL; + enum { + MODE_UNSPECIFIED = 0, + MODE_LIST, + MODE_DELETE, + MODE_EDIT, + MODE_GRAFT, + 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_CMDMODE('g', "graft", &cmdmode, N_("change a commit's parents"), MODE_GRAFT), OPT_BOOL('f', "force", &force, N_("replace the ref if it exists")), + OPT_BOOL(0, "raw", &raw, N_("do not pretty-print contents for --edit")), OPT_STRING(0, "format", &format, N_("format"), N_("use this format")), OPT_END() }; @@ -182,44 +440,56 @@ 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", + if (!cmdmode) + cmdmode = argc ? MODE_REPLACE : MODE_LIST; + + if (format && cmdmode != MODE_LIST) + usage_msg_opt("--format cannot be used when not listing", git_replace_usage, options); - if (format && delete) - usage_msg_opt("--format and -d cannot be used together", + if (force && + cmdmode != MODE_REPLACE && + cmdmode != MODE_EDIT && + cmdmode != MODE_GRAFT) + usage_msg_opt("-f only makes sense when writing a replacement", git_replace_usage, options); - if (force && (list || delete)) - usage_msg_opt("-f cannot be used with -d or -l", + if (raw && cmdmode != MODE_EDIT) + usage_msg_opt("--raw only makes sense with --edit", 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, raw); - return list_replace_refs(argv[0], format); + case MODE_GRAFT: + if (argc < 1) + usage_msg_opt("-g needs at least one argument", + git_replace_usage, options); + return create_graft(argc, argv, 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); + + default: + die("BUG: invalid cmdmode %d", (int)cmdmode); + } } diff --git a/builtin/reset.c b/builtin/reset.c index 6bd6245821..855d478e3b 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -84,7 +84,7 @@ static int reset_index(const unsigned char *sha1, int reset_type, int quiet) if (reset_type == MIXED || reset_type == HARD) { tree = parse_tree_indirect(sha1); - prime_cache_tree(&active_cache_tree, tree); + prime_cache_tree(&the_index, tree); } return 0; @@ -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; } @@ -351,7 +353,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (reset_type != SOFT) { struct lock_file *lock = xcalloc(1, sizeof(*lock)); - int newfd = hold_locked_index(lock, 1); + hold_locked_index(lock, 1); if (reset_type == MIXED) { int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; if (read_from_tree(&pathspec, sha1, intent_to_add)) @@ -367,8 +369,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) die(_("Could not reset index file to revision '%s'."), rev); } - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock)) + if (write_locked_index(&the_index, lock, COMMIT_LOCK)) die(_("Could not write new index file.")); } diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 1a6122d3ae..d85e08cc9c 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -11,6 +11,7 @@ #include "parse-options.h" #include "diff.h" #include "revision.h" +#include "split-index.h" #define DO_REVS 1 #define DO_NOREV 2 @@ -150,6 +151,7 @@ static void show_rev(int type, const unsigned char *sha1, const char *name) error("refname '%s' is ambiguous", name); break; } + free(full); } else { show_with_type(type, name); } @@ -775,6 +777,15 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) : "false"); continue; } + if (!strcmp(arg, "--shared-index-path")) { + if (read_cache() < 0) + die(_("Could not read the index")); + if (the_index.split_index) { + const unsigned char *sha1 = the_index.split_index->base_sha1; + puts(git_path("sharedindex.%s", sha1_to_hex(sha1))); + } + continue; + } if (starts_with(arg, "--since=")) { show_datestring("--max-age=", arg+8); continue; diff --git a/builtin/rm.c b/builtin/rm.c index 960634dd0c..bc6490b8bc 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -278,7 +278,7 @@ static struct option builtin_rm_options[] = { int cmd_rm(int argc, const char **argv, const char *prefix) { - int i, newfd; + int i; struct pathspec pathspec; char *seen; @@ -293,7 +293,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!index_only) setup_work_tree(); - newfd = hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, 1); if (read_cache() < 0) die(_("index file corrupt")); @@ -427,8 +427,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) } if (active_cache_changed) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("Unable to write new index file")); } diff --git a/builtin/send-pack.c b/builtin/send-pack.c index f420b74665..4b1bc0fef7 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -110,6 +110,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) int flags; unsigned int reject_reasons; int progress = -1; + int from_stdin = 0; struct push_cas_option cas = {0}; argv++; @@ -169,6 +170,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.stateless_rpc = 1; continue; } + if (!strcmp(arg, "--stdin")) { + from_stdin = 1; + continue; + } if (!strcmp(arg, "--helper-status")) { helper_status = 1; continue; @@ -201,6 +206,28 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) } if (!dest) usage(send_pack_usage); + + if (from_stdin) { + struct argv_array all_refspecs = ARGV_ARRAY_INIT; + + for (i = 0; i < nr_refspecs; i++) + argv_array_push(&all_refspecs, refspecs[i]); + + if (args.stateless_rpc) { + const char *buf; + while ((buf = packet_read_line(0, NULL))) + argv_array_push(&all_refspecs, buf); + } else { + struct strbuf line = STRBUF_INIT; + while (strbuf_getline(&line, stdin, '\n') != EOF) + argv_array_push(&all_refspecs, line.buf); + strbuf_release(&line); + } + + refspecs = all_refspecs.argv; + nr_refspecs = all_refspecs.argc; + } + /* * --all and --mirror are incompatible; neither makes sense * with any refspecs. diff --git a/builtin/show-branch.c b/builtin/show-branch.c index d87317290c..298c95e3f8 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,15 +770,14 @@ 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); } + free(ref); } else if (all_heads + all_remotes) snarf_refs(all_heads, all_remotes); diff --git a/builtin/tag.c b/builtin/tag.c index 6c7c6bde9d..19eb747820 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -32,6 +32,8 @@ static const char * const git_tag_usage[] = { #define SORT_MASK 0x7fff #define REVERSE_SORT 0x8000 +static int tag_sort; + struct tag_filter { const char **patterns; int lines; @@ -80,11 +82,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 +102,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) { - return contains_recurse(candidate, want); + 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) +{ + 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,17 +340,59 @@ 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"); +/* + * Parse a sort string, and return 0 if parsed successfully. Will return + * non-zero when the sort string does not parse into a known type. If var is + * given, the error message becomes a warning and includes information about + * the configuration value. + */ +static int parse_sort_string(const char *var, const char *arg, int *sort) +{ + int type = 0, flags = 0; + + if (skip_prefix(arg, "-", &arg)) + flags |= REVERSE_SORT; + + if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg)) + type = VERCMP_SORT; + else + type = STRCMP_SORT; + + if (strcmp(arg, "refname")) { + if (!var) + return error(_("unsupported sort specification '%s'"), arg); + else { + warning(_("unsupported sort specification '%s' in variable '%s'"), + var, arg); + return -1; + } + } + + *sort = (type | flags); + + return 0; +} + static int git_tag_config(const char *var, const char *value, void *cb) { - int status = git_gpg_config(var, value, cb); + int status; + + if (!strcmp(var, "tag.sort")) { + if (!value) + return config_error_nonbool(var); + parse_sort_string(var, value, &tag_sort); + return 0; + } + + status = git_gpg_config(var, value, cb); if (status) return status; if (starts_with(var, "column.")) @@ -378,9 +482,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); } @@ -462,24 +566,8 @@ static int parse_opt_points_at(const struct option *opt __attribute__((unused)), static int parse_opt_sort(const struct option *opt, const char *arg, int unset) { int *sort = opt->value; - int flags = 0; - if (*arg == '-') { - flags |= REVERSE_SORT; - arg++; - } - if (starts_with(arg, "version:")) { - *sort = VERCMP_SORT; - arg += 8; - } else if (starts_with(arg, "v:")) { - *sort = VERCMP_SORT; - arg += 2; - } else - *sort = STRCMP_SORT; - if (strcmp(arg, "refname")) - die(_("unsupported sort specification %s"), arg); - *sort |= flags; - return 0; + return parse_sort_string(NULL, arg, sort); } int cmd_tag(int argc, const char **argv, const char *prefix) @@ -492,7 +580,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct create_tag_options opt; char *cleanup_arg = NULL; int annotate = 0, force = 0, lines = -1; - int cmdmode = 0, sort = 0; + int cmdmode = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; struct commit_list *with_commit = NULL; @@ -518,7 +606,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT__FORCE(&force, N_("replace the tag if exists")), OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), { - OPTION_CALLBACK, 0, "sort", &sort, N_("type"), N_("sort tags"), + OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"), PARSE_OPT_NONEG, parse_opt_sort }, @@ -574,9 +662,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix) copts.padding = 2; run_column_filter(colopts, &copts); } - if (lines != -1 && sort) + if (lines != -1 && tag_sort) die(_("--sort and -n are incompatible")); - ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, sort); + ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort); if (column_active(colopts)) stop_column_filter(); return ret; diff --git a/builtin/update-index.c b/builtin/update-index.c index ebea285e1b..e8c7fd4d49 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -13,6 +13,7 @@ #include "parse-options.h" #include "pathspec.h" #include "dir.h" +#include "split-index.h" /* * Default to not allowing changes to the list of files. The @@ -55,8 +56,9 @@ static int mark_ce_flags(const char *path, int flag, int mark) active_cache[pos]->ce_flags |= flag; else active_cache[pos]->ce_flags &= ~flag; - cache_tree_invalidate_path(active_cache_tree, path); - active_cache_changed = 1; + active_cache[pos]->ce_flags |= CE_UPDATE_IN_BASE; + cache_tree_invalidate_path(&the_index, path); + active_cache_changed |= CE_ENTRY_CHANGED; return 0; } return -1; @@ -267,8 +269,9 @@ static void chmod_path(int flip, const char *path) default: goto fail; } - cache_tree_invalidate_path(active_cache_tree, path); - active_cache_changed = 1; + cache_tree_invalidate_path(&the_index, path); + ce->ce_flags |= CE_UPDATE_IN_BASE; + active_cache_changed |= CE_ENTRY_CHANGED; report("chmod %cx '%s'", flip, path); return; fail: @@ -743,6 +746,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) char set_executable_bit = 0; struct refresh_params refresh_args = {0, &has_errors}; int lock_error = 0; + int split_index = -1; struct lock_file *lock_file; struct parse_opt_ctx_t ctx; int parseopt_state = PARSE_OPT_UNKNOWN; @@ -825,6 +829,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) resolve_undo_clear_callback}, OPT_INTEGER(0, "index-version", &preferred_index_format, N_("write index in this format")), + OPT_BOOL(0, "split-index", &split_index, + N_("enable or disable split index")), OPT_END() }; @@ -892,7 +898,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) INDEX_FORMAT_LB, INDEX_FORMAT_UB); if (the_index.version != preferred_index_format) - active_cache_changed = 1; + active_cache_changed |= SOMETHING_CHANGED; the_index.version = preferred_index_format; } @@ -918,14 +924,27 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) strbuf_release(&buf); } + if (split_index > 0) { + init_split_index(&the_index); + the_index.cache_changed |= SPLIT_INDEX_ORDERED; + } else if (!split_index && the_index.split_index) { + /* + * can't discard_split_index(&the_index); because that + * will destroy split_index->base->cache[], which may + * be shared with the_index.cache[]. So yeah we're + * leaking a bit here. + */ + the_index.split_index = NULL; + the_index.cache_changed |= SOMETHING_CHANGED; + } + if (active_cache_changed) { if (newfd < 0) { if (refresh_args.flags & REFRESH_QUIET) exit(128); unable_to_lock_index_die(get_index_file(), lock_error); } - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die("Unable to write new index file"); } diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 5c208bb1fc..3067b11310 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -12,238 +12,332 @@ 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; -} - +static struct strbuf err = STRBUF_INIT; + +/* + * 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 != line_termination) + die("update %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("update %s missing [<oldvalue>] NUL", ref.buf); + if (ref_transaction_update(transaction, refname, new_sha1, old_sha1, + update_flags, have_old, &err)) + die("%s", err.buf); + + update_flags = 0; + free(refname); - if (next && *next) - die("update %s has extra input: %s", ref.buf, next); + 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); + + ref_transaction_create(transaction, refname, new_sha1, update_flags); + + update_flags = 0; + free(refname); - if (next && *next) - die("create %s has extra input: %s", ref.buf, next); + 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 = 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); + if (*next != line_termination) + die("delete %s: extra input: %s", refname, next); - if (next && *next) - die("delete %s has extra input: %s", ref.buf, next); + ref_transaction_delete(transaction, refname, old_sha1, + update_flags, have_old); + + 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>"); + if (ref_transaction_update(transaction, refname, new_sha1, old_sha1, + update_flags, have_old, &err)) + die("%s", err.buf); - 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 +362,16 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) die("Refusing to perform update with empty message."); if (read_stdin) { + 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); + if (ref_transaction_commit(transaction, msg, &err)) + die("%s", err.buf); + ref_transaction_free(transaction); + return 0; } if (end_null) @@ -305,5 +403,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; +} diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c index 66cd2df0f8..972579f33c 100644 --- a/builtin/verify-pack.c +++ b/builtin/verify-pack.c @@ -27,10 +27,9 @@ static int verify_one_pack(const char *path, unsigned int flags) * normalize these forms to "foo.pack" for "index-pack --verify". */ strbuf_addstr(&arg, path); - if (has_extension(arg.buf, ".idx")) - strbuf_splice(&arg, arg.len - 3, 3, "pack", 4); - else if (!has_extension(arg.buf, ".pack")) - strbuf_add(&arg, ".pack", 5); + if (strbuf_strip_suffix(&arg, ".idx") || + !ends_with(arg.buf, ".pack")) + strbuf_addstr(&arg, ".pack"); argv[2] = arg.buf; memset(&index_pack, 0, sizeof(index_pack)); |