diff options
Diffstat (limited to 'builtin')
82 files changed, 5456 insertions, 2745 deletions
diff --git a/builtin/add.c b/builtin/add.c index 459208a326..ee370b059a 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -5,6 +5,7 @@ */ #include "cache.h" #include "builtin.h" +#include "lockfile.h" #include "dir.h" #include "pathspec.h" #include "exec_cmd.h" @@ -18,7 +19,7 @@ #include "argv-array.h" static const char * const builtin_add_usage[] = { - N_("git add [options] [--] <pathspec>..."), + N_("git add [<options>] [--] <pathspec>..."), NULL }; static int patch_interactive, add_interactive, edit_interactive; @@ -62,6 +63,7 @@ static void update_callback(struct diff_queue_struct *q, switch (fix_unmerged_status(p, data)) { default: die(_("unexpected diff status %c"), p->status); + case DIFF_STATUS_ADDED: case DIFF_STATUS_MODIFIED: case DIFF_STATUS_TYPE_CHANGED: if (add_file_to_index(&the_index, path, data->flags)) { @@ -180,7 +182,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) char *file = git_pathdup("ADD_EDIT.patch"); const char *apply_argv[] = { "apply", "--recount", "--cached", NULL, NULL }; - struct child_process child; + struct child_process child = CHILD_PROCESS_INIT; struct rev_info rev; int out; struct stat st; @@ -214,7 +216,6 @@ static int edit_patch(int argc, const char **argv, const char *prefix) if (!st.st_size) die(_("Empty patch. Aborted.")); - memset(&child, 0, sizeof(child)); child.git_cmd = 1; child.argv = apply_argv; if (run_command(&child)) @@ -284,7 +285,7 @@ static int add_files(struct dir_struct *dir, int flags) for (i = 0; i < dir->ignored_nr; i++) fprintf(stderr, "%s\n", dir->ignored[i]->name); fprintf(stderr, _("Use -f if you really want to add them.\n")); - die(_("no files added")); + exit_status = 1; } for (i = 0; i < dir->nr; i++) @@ -299,7 +300,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 +345,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 +443,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..146be97a1a 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -7,6 +7,7 @@ * */ #include "cache.h" +#include "lockfile.h" #include "cache-tree.h" #include "quote.h" #include "blob.h" @@ -50,11 +51,12 @@ static int apply_verbosely; static int allow_overlap; static int no_add; static int threeway; +static int unsafe_paths; static const char *fake_ancestor; static int line_termination = '\n'; static unsigned int p_context = UINT_MAX; static const char * const apply_usage[] = { - N_("git apply [options] [<patch>...]"), + N_("git apply [<options>] [<patch>...]"), NULL }; @@ -206,7 +208,7 @@ struct patch { struct patch *next; /* three-way fallback result */ - unsigned char threeway_stage[3][20]; + struct object_id threeway_stage[3]; }; static void free_fragment_list(struct fragment *list) @@ -435,7 +437,7 @@ static unsigned long linelen(const char *buffer, unsigned long size) static int is_dev_null(const char *str) { - return !memcmp("/dev/null", str, 9) && isspace(str[9]); + return skip_prefix(str, "/dev/null", &str) && isspace(*str); } #define TERM_SPACE 1 @@ -656,11 +658,6 @@ static size_t diff_timestamp_len(const char *line, size_t len) return line + len - end; } -static char *null_strdup(const char *s) -{ - return s ? xstrdup(s) : NULL; -} - static char *find_name_common(const char *line, const char *def, int p_value, const char *end, int terminate) { @@ -683,10 +680,10 @@ static char *find_name_common(const char *line, const char *def, start = line; } if (!start) - return squash_slash(null_strdup(def)); + return squash_slash(xstrdup_or_null(def)); len = line - start; if (!len) - return squash_slash(null_strdup(def)); + return squash_slash(xstrdup_or_null(def)); /* * Generally we prefer the shorter name, especially @@ -908,7 +905,7 @@ static void parse_traditional_patch(const char *first, const char *second, struc patch->old_name = name; } else { patch->old_name = name; - patch->new_name = null_strdup(name); + patch->new_name = xstrdup_or_null(name); } } if (!name) @@ -997,7 +994,7 @@ static int gitdiff_delete(const char *line, struct patch *patch) { patch->is_delete = 1; free(patch->old_name); - patch->old_name = null_strdup(patch->def_name); + patch->old_name = xstrdup_or_null(patch->def_name); return gitdiff_oldmode(line, patch); } @@ -1005,7 +1002,7 @@ static int gitdiff_newfile(const char *line, struct patch *patch) { patch->is_new = 1; free(patch->new_name); - patch->new_name = null_strdup(patch->def_name); + patch->new_name = xstrdup_or_null(patch->def_name); return gitdiff_newmode(line, patch); } @@ -1075,7 +1072,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 +1278,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; } @@ -1606,6 +1601,9 @@ static int parse_fragment(const char *line, unsigned long size, if (!deleted && !added) leading++; trailing++; + if (!apply_in_reverse && + ws_error_action == correct_ws_error) + check_whitespace(line, len, patch->ws_rule); break; case '-': if (apply_in_reverse && @@ -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); @@ -2171,6 +2234,12 @@ static void update_pre_post_images(struct image *preimage, ctx++; } + if (postlen + ? postlen < new - postimage->buf + : postimage->len < new - postimage->buf) + die("BUG: caller miscounted postlen: asked %d, orig = %d, used = %d", + (int)postlen, (int) postimage->len, (int)(new - postimage->buf)); + /* Fix the length of the whole thing */ postimage->len = new - postimage->buf; postimage->nr -= reduced; @@ -2326,10 +2395,27 @@ static int match_fragment(struct image *img, /* * The hunk does not apply byte-by-byte, but the hash says - * it might with whitespace fuzz. We haven't been asked to + * it might with whitespace fuzz. We weren't asked to * ignore whitespace, we were asked to correct whitespace * errors, so let's try matching after whitespace correction. * + * While checking the preimage against the target, whitespace + * errors in both fixed, we count how large the corresponding + * postimage needs to be. The postimage prepared by + * apply_one_fragment() has whitespace errors fixed on added + * lines already, but the common lines were propagated as-is, + * which may become longer when their whitespace errors are + * fixed. + */ + + /* First count added lines in postimage */ + postlen = 0; + for (i = 0; i < postimage->nr; i++) { + if (!(postimage->line[i].flag & LINE_COMMON)) + postlen += postimage->line[i].len; + } + + /* * The preimage may extend beyond the end of the file, * but in this loop we will only handle the part of the * preimage that falls within the file. @@ -2337,7 +2423,6 @@ static int match_fragment(struct image *img, strbuf_init(&fixed, preimage->len + 1); orig = preimage->buf; target = img->buf + try; - postlen = 0; for (i = 0; i < preimage_limit; i++) { size_t oldlen = preimage->line[i].len; size_t tgtlen = img->line[try_lno + i].len; @@ -2365,7 +2450,10 @@ static int match_fragment(struct image *img, match = (tgtfix.len == fixed.len - fixstart && !memcmp(tgtfix.buf, fixed.buf + fixstart, fixed.len - fixstart)); - postlen += tgtfix.len; + + /* Add the length if this is common with the postimage */ + if (preimage->line[i].flag & LINE_COMMON) + postlen += tgtfix.len; strbuf_release(&tgtfix); if (!match) @@ -2563,7 +2651,7 @@ static void update_image(struct image *img, * NOTE: this knows that we never call remove_first_line() * on anything other than pre/post image. */ - img->line = xrealloc(img->line, nr * sizeof(*img->line)); + REALLOC_ARRAY(img->line, nr); img->line_allocated = img->line; } if (preimage_limit != postimage->nr) @@ -2688,7 +2776,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, default: if (apply_verbosely) error(_("invalid start of line: '%c'"), first); - return -1; + applied_pos = -1; + goto out; } if (added_blank_line) { if (!new_blank_lines_at_end) @@ -2827,6 +2916,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, (int)(old - oldlines), oldlines); } +out: free(oldlines); strbuf_release(&newlines); free(preimage.line_allocated); @@ -3084,13 +3174,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; @@ -3135,7 +3227,7 @@ static int load_patch_target(struct strbuf *buf, const char *name, unsigned expected_mode) { - if (cached) { + if (cached || check_index) { if (read_file_or_gitlink(ce, buf)) return error(_("read of %s failed"), name); } else if (name) { @@ -3144,6 +3236,8 @@ static int load_patch_target(struct strbuf *buf, return read_file_or_gitlink(ce, buf); else return SUBMODULE_PATCH_WITHOUT_INDEX; + } else if (has_symlink_leading_path(name, strlen(name))) { + return error(_("reading from '%s' beyond a symbolic link"), name); } else { if (read_old_data(st, name, buf)) return error(_("read of %s failed"), name); @@ -3257,7 +3351,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)) @@ -3332,11 +3426,11 @@ static int try_threeway(struct image *image, struct patch *patch, if (status) { patch->conflicted_threeway = 1; if (patch->is_new) - hashclr(patch->threeway_stage[0]); + oidclr(&patch->threeway_stage[0]); else - hashcpy(patch->threeway_stage[0], pre_sha1); - hashcpy(patch->threeway_stage[1], our_sha1); - hashcpy(patch->threeway_stage[2], post_sha1); + hashcpy(patch->threeway_stage[0].hash, pre_sha1); + hashcpy(patch->threeway_stage[1].hash, our_sha1); + hashcpy(patch->threeway_stage[2].hash, post_sha1); fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name); } else { fprintf(stderr, "Applied patch to '%s' cleanly.\n", patch->new_name); @@ -3411,7 +3505,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)) @@ -3484,6 +3578,121 @@ static int check_to_create(const char *new_name, int ok_if_exists) } /* + * We need to keep track of how symlinks in the preimage are + * manipulated by the patches. A patch to add a/b/c where a/b + * is a symlink should not be allowed to affect the directory + * the symlink points at, but if the same patch removes a/b, + * it is perfectly fine, as the patch removes a/b to make room + * to create a directory a/b so that a/b/c can be created. + */ +static struct string_list symlink_changes; +#define SYMLINK_GOES_AWAY 01 +#define SYMLINK_IN_RESULT 02 + +static uintptr_t register_symlink_changes(const char *path, uintptr_t what) +{ + struct string_list_item *ent; + + ent = string_list_lookup(&symlink_changes, path); + if (!ent) { + ent = string_list_insert(&symlink_changes, path); + ent->util = (void *)0; + } + ent->util = (void *)(what | ((uintptr_t)ent->util)); + return (uintptr_t)ent->util; +} + +static uintptr_t check_symlink_changes(const char *path) +{ + struct string_list_item *ent; + + ent = string_list_lookup(&symlink_changes, path); + if (!ent) + return 0; + return (uintptr_t)ent->util; +} + +static void prepare_symlink_changes(struct patch *patch) +{ + for ( ; patch; patch = patch->next) { + if ((patch->old_name && S_ISLNK(patch->old_mode)) && + (patch->is_rename || patch->is_delete)) + /* the symlink at patch->old_name is removed */ + register_symlink_changes(patch->old_name, SYMLINK_GOES_AWAY); + + if (patch->new_name && S_ISLNK(patch->new_mode)) + /* the symlink at patch->new_name is created or remains */ + register_symlink_changes(patch->new_name, SYMLINK_IN_RESULT); + } +} + +static int path_is_beyond_symlink_1(struct strbuf *name) +{ + do { + unsigned int change; + + while (--name->len && name->buf[name->len] != '/') + ; /* scan backwards */ + if (!name->len) + break; + name->buf[name->len] = '\0'; + change = check_symlink_changes(name->buf); + if (change & SYMLINK_IN_RESULT) + return 1; + if (change & SYMLINK_GOES_AWAY) + /* + * This cannot be "return 0", because we may + * see a new one created at a higher level. + */ + continue; + + /* otherwise, check the preimage */ + if (check_index) { + struct cache_entry *ce; + + ce = cache_file_exists(name->buf, name->len, ignore_case); + if (ce && S_ISLNK(ce->ce_mode)) + return 1; + } else { + struct stat st; + if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode)) + return 1; + } + } while (1); + return 0; +} + +static int path_is_beyond_symlink(const char *name_) +{ + int ret; + struct strbuf name = STRBUF_INIT; + + assert(*name_ != '\0'); + strbuf_addstr(&name, name_); + ret = path_is_beyond_symlink_1(&name); + strbuf_release(&name); + + return ret; +} + +static void die_on_unsafe_path(struct patch *patch) +{ + const char *old_name = NULL; + const char *new_name = NULL; + if (patch->is_delete) + old_name = patch->old_name; + else if (!patch->is_new && !patch->is_copy) + old_name = patch->old_name; + if (!patch->is_delete) + new_name = patch->new_name; + + if (old_name && !verify_path(old_name)) + die(_("invalid path '%s'"), old_name); + if (new_name && !verify_path(new_name)) + die(_("invalid path '%s'"), new_name); +} + +/* * Check and apply the patch in-core; leave the result in patch->result * for the caller to write it out to the final destination. */ @@ -3570,6 +3779,22 @@ static int check_patch(struct patch *patch) } } + if (!unsafe_paths) + die_on_unsafe_path(patch); + + /* + * An attempt to read from or delete a path that is beyond a + * symbolic link will be prevented by load_patch_target() that + * is called at the beginning of apply_data() so we do not + * have to worry about a patch marked with "is_delete" bit + * here. We however need to make sure that the patch result + * is not deposited to a path that is beyond a symbolic link + * here. + */ + if (!patch->is_delete && path_is_beyond_symlink(patch->new_name)) + return error(_("affected file '%s' is beyond a symbolic link"), + patch->new_name); + if (apply_data(patch, &st, ce) < 0) return error(_("%s: patch does not apply"), name); patch->rejected = 0; @@ -3580,6 +3805,7 @@ static int check_patch_list(struct patch *patch) { int err = 0; + prepare_symlink_changes(patch); prepare_fn_table(patch); while (patch) { if (apply_verbosely) @@ -3644,7 +3870,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... @@ -3662,7 +3888,7 @@ static void build_fake_ancestor(struct patch *list, const char *filename) if (!preimage_sha1_in_gitlink_patch(patch, sha1)) ; /* ok, the textual part looks sane */ else - die("sha1 information is lacking or useless for submoule %s", + die("sha1 information is lacking or useless for submodule %s", name); } else if (!get_sha1_blob(patch->old_sha1_prefix, sha1)) { ; /* ok */ @@ -3682,8 +3908,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 +4071,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) { @@ -3959,14 +4186,14 @@ static void add_conflicted_stages_file(struct patch *patch) remove_file_from_cache(patch->new_name); for (stage = 1; stage < 4; stage++) { - if (is_null_sha1(patch->threeway_stage[stage - 1])) + if (is_null_oid(&patch->threeway_stage[stage - 1])) continue; ce = xcalloc(1, ce_size); memcpy(ce->name, patch->new_name, namelen); ce->ce_mode = create_ce_mode(mode); ce->ce_flags = create_ce_flags(stage); ce->ce_namelen = namelen; - hashcpy(ce->sha1, patch->threeway_stage[stage - 1]); + hashcpy(ce->sha1, patch->threeway_stage[stage - 1].hash); if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) die(_("unable to add cache entry for %s"), patch->new_name); } @@ -4113,7 +4340,7 @@ static int write_out_results(struct patch *list) if (cpath.nr) { struct string_list_item *item; - sort_string_list(&cpath); + string_list_sort(&cpath); for_each_string_list_item(item, &cpath) fprintf(stderr, "U %s\n", item->string); string_list_clear(&cpath, 0); @@ -4126,64 +4353,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 +4378,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; @@ -4268,13 +4435,11 @@ static int apply_patch(int fd, const char *filename, int options) return 0; } -static int git_apply_config(const char *var, const char *value, void *cb) +static void git_apply_config(void) { - if (!strcmp(var, "apply.whitespace")) - return git_config_string(&apply_default_whitespace, var, value); - else if (!strcmp(var, "apply.ignorewhitespace")) - return git_config_string(&apply_default_ignorewhitespace, var, value); - return git_default_config(var, value, cb); + git_config_get_string_const("apply.whitespace", &apply_default_whitespace); + git_config_get_string_const("apply.ignorewhitespace", &apply_default_ignorewhitespace); + git_config(git_default_config, NULL); } static int option_parse_exclude(const struct option *opt, @@ -4379,6 +4544,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) N_("make sure the patch is applicable to the current index")), OPT_BOOL(0, "cached", &cached, N_("apply a patch without touching the working tree")), + OPT_BOOL(0, "unsafe-paths", &unsafe_paths, + N_("accept a patch that touches outside the working area")), OPT_BOOL(0, "apply", &force_apply, N_("also apply the patch (use with --stat/--summary/--check)")), OPT_BOOL('3', "3way", &threeway, @@ -4422,7 +4589,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) prefix = prefix_; prefix_length = prefix ? strlen(prefix) : 0; - git_config(git_apply_config, NULL); + git_apply_config(); if (apply_default_whitespace) parse_whitespace_option(apply_default_whitespace); if (apply_default_ignorewhitespace) @@ -4451,6 +4618,9 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) die(_("--cached outside a repository")); check_index = 1; } + if (check_index) + unsafe_paths = 0; + for (i = 0; i < argc; i++) { const char *arg = argv[i]; int fd; @@ -4501,8 +4671,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..8d70623cb8 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,18 +19,20 @@ #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" #include "line-log.h" -static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file"); +static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] file"); static const char *blame_opt_usage[] = { blame_usage, "", - N_("[rev-opts] are documented in git-rev-list(1)"), + N_("<rev-opts> are documented in git-rev-list(1)"), NULL }; @@ -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; + int num = 0; - for (p = buf;;) { - p = memchr(p, '\n', end - p); - if (p) { - p++; - num++; - continue; - } - break; - } + for (p = buf; p < end; p = get_next_line(p, end)) + num++; - if (len && end[-1] != '\n') - incomplete++; /* incomplete line at the end */ + sb->lineno = lineno = xmalloc(sizeof(*sb->lineno) * (num + 1)); - sb->lineno = xmalloc(sizeof(*sb->lineno) * (num + incomplete + 1)); - lineno = sb->lineno; + for (p = buf; p < end; p = get_next_line(p, end)) + *lineno++ = p - buf; - *lineno++ = 0; - for (p = buf;;) { - p = memchr(p, '\n', end - p); - if (p) { - p++; - *lineno++ = p - buf; - continue; - } - break; - } - - if (incomplete) - *lineno++ = len; + *lineno = len; - sb->num_lines = num + incomplete; + sb->num_lines = num; return sb->num_lines; } @@ -1842,7 +2085,6 @@ static void find_alignment(struct scoreboard *sb, int *option) for (e = sb->ent; e; e = e->next) { struct origin *suspect = e->suspect; - struct commit_info ci; int num; if (compute_auto_abbrev) @@ -1853,6 +2095,7 @@ static void find_alignment(struct scoreboard *sb, int *option) if (longest_file < num) longest_file = num; if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { + struct commit_info ci; suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); if (*option & OUTPUT_SHOW_EMAIL) @@ -1861,6 +2104,7 @@ static void find_alignment(struct scoreboard *sb, int *option) num = utf8_strwidth(ci.author.buf); if (longest_author < num) longest_author = num; + commit_info_destroy(&ci); } num = e->s_lno + e->num_lines; if (longest_src_lines < num) @@ -1870,8 +2114,6 @@ static void find_alignment(struct scoreboard *sb, int *option) longest_dst_lines = num; if (largest_score < ent_score(sb, e)) largest_score = ent_score(sb, e); - - commit_info_destroy(&ci); } max_orig_digits = decimal_width(longest_src_lines); max_digits = decimal_width(longest_dst_lines); @@ -2043,7 +2285,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, commit->date = now; parent_tail = &commit->parents; - if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL)) + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL)) die("no such ref: HEAD"); parent_tail = append_parent(parent_tail, head_sha1); @@ -2106,10 +2348,10 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, if (strbuf_read(&buf, 0, 0) < 0) die_errno("failed to read from stdin"); } + convert_to_git(path, buf.buf, buf.len, &buf, 0); 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,12 +2385,12 @@ 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; } -static const char *prepare_final(struct scoreboard *sb) +static char *prepare_final(struct scoreboard *sb) { int i; const char *final_commit_name = NULL; @@ -2173,10 +2415,10 @@ static const char *prepare_final(struct scoreboard *sb) sb->final = (struct commit *) obj; final_commit_name = revs->pending.objects[i].name; } - return final_commit_name; + return xstrdup_or_null(final_commit_name); } -static const char *prepare_initial(struct scoreboard *sb) +static char *prepare_initial(struct scoreboard *sb) { int i; const char *final_commit_name = NULL; @@ -2203,7 +2445,7 @@ static const char *prepare_initial(struct scoreboard *sb) } if (!final_commit_name) die("No commit to dig down to?"); - return final_commit_name; + return xstrdup(final_commit_name); } static int blame_copy_callback(const struct option *option, const char *arg, int unset) @@ -2247,7 +2489,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) struct origin *o; struct blame_entry *ent = NULL; long dashdash_pos, lno; - const char *final_commit_name = NULL; + char *final_commit_name = NULL; enum object_type type; static struct string_list range_list; @@ -2338,6 +2580,9 @@ parse_done: case DATE_RFC2822: blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700"); break; + case DATE_ISO8601_STRICT: + blame_date_width = sizeof("2006-10-19T16:00:04-07:00"); + break; case DATE_ISO8601: blame_date_width = sizeof("2006-10-19 16:00:04 -0700"); break; @@ -2427,12 +2672,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) { /* @@ -2454,7 +2703,7 @@ parse_done: * uninteresting. */ if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); if (is_null_sha1(sb.final->object.sha1)) { o = sb.final->util; @@ -2518,12 +2767,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); @@ -2533,9 +2786,13 @@ parse_done: assign_blame(&sb, opt); + free(final_commit_name); + 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..9cbab189f5 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -21,10 +21,10 @@ #include "wt-status.h" static const char * const builtin_branch_usage[] = { - N_("git branch [options] [-r | -a] [--merged | --no-merged]"), - N_("git branch [options] [-l] [-f] <branchname> [<start-point>]"), - N_("git branch [options] [-r] (-d | -D) <branchname>..."), - N_("git branch [options] (-m | -M) [<oldbranch>] <newbranch>"), + N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"), + N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"), + N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."), + N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"), NULL }; @@ -62,39 +62,40 @@ static unsigned char merge_filter_ref[20]; static struct string_list output = STRING_LIST_INIT_DUP; static unsigned int colopts; -static int parse_branch_color_slot(const char *var, int ofs) +static int parse_branch_color_slot(const char *slot) { - if (!strcasecmp(var+ofs, "plain")) + if (!strcasecmp(slot, "plain")) return BRANCH_COLOR_PLAIN; - if (!strcasecmp(var+ofs, "reset")) + if (!strcasecmp(slot, "reset")) return BRANCH_COLOR_RESET; - if (!strcasecmp(var+ofs, "remote")) + if (!strcasecmp(slot, "remote")) return BRANCH_COLOR_REMOTE; - if (!strcasecmp(var+ofs, "local")) + if (!strcasecmp(slot, "local")) return BRANCH_COLOR_LOCAL; - if (!strcasecmp(var+ofs, "current")) + if (!strcasecmp(slot, "current")) return BRANCH_COLOR_CURRENT; - if (!strcasecmp(var+ofs, "upstream")) + if (!strcasecmp(slot, "upstream")) return BRANCH_COLOR_UPSTREAM; return -1; } static int git_branch_config(const char *var, const char *value, void *cb) { + const char *slot_name; + if (starts_with(var, "column.")) return git_column_config(var, value, "branch", &colopts); if (!strcmp(var, "color.branch")) { branch_use_color = git_config_colorbool(var, value); return 0; } - if (starts_with(var, "color.branch.")) { - int slot = parse_branch_color_slot(var, 13); + if (skip_prefix(var, "color.branch.", &slot_name)) { + int slot = parse_branch_color_slot(slot_name); if (slot < 0) return 0; if (!value) return config_error_nonbool(var); - color_parse(value, var, branch_colors[slot]); - return 0; + return color_parse(value, branch_colors[slot]); } return git_color_default_config(var, value, cb); } @@ -129,7 +130,8 @@ static int branch_merged(int kind, const char *name, branch->merge[0] && branch->merge[0]->dst && (reference_name = reference_name_to_free = - resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL) + resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING, + sha1, NULL)) != NULL) reference_rev = lookup_commit_reference(sha1); } if (!reference_rev) @@ -233,17 +235,20 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, free(name); name = mkpathdup(fmt, bname.buf); - target = resolve_ref_unsafe(name, sha1, 0, &flags); - if (!target || - (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) { + target = resolve_ref_unsafe(name, + RESOLVE_REF_READING + | RESOLVE_REF_NO_RECURSE + | RESOLVE_REF_ALLOW_BAD_NAME, + sha1, &flags); + if (!target) { error(remote_branch - ? _("remote branch '%s' not found.") + ? _("remote-tracking branch '%s' not found.") : _("branch '%s' not found."), bname.buf); ret = 1; continue; } - if (!(flags & REF_ISSYMREF) && + if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) && check_branch_commit(bname.buf, name, sha1, head_rev, kinds, force)) { ret = 1; @@ -252,7 +257,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, if (delete_ref(name, sha1, REF_NODEREF)) { error(remote_branch - ? _("Error deleting remote branch '%s'") + ? _("Error deleting remote-tracking branch '%s'") : _("Error deleting branch '%s'"), bname.buf); ret = 1; @@ -260,11 +265,11 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, } if (!quiet) { printf(remote_branch - ? _("Deleted remote branch %s (was %s).\n") + ? _("Deleted remote-tracking branch %s (was %s).\n") : _("Deleted branch %s (was %s).\n"), bname.buf, - (flags & REF_ISSYMREF) - ? target + (flags & REF_ISBROKEN) ? "broken" + : (flags & REF_ISSYMREF) ? target : find_unique_abbrev(sha1, DEFAULT_ABBREV)); } delete_branch_config(bname.buf); @@ -280,6 +285,7 @@ struct ref_item { char *dest; unsigned int kind, width; struct commit *commit; + int ignore; }; struct ref_list { @@ -294,13 +300,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); + dst = resolve_ref_unsafe(src, 0, sha1, &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); } @@ -334,20 +340,18 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, static struct { int kind; const char *prefix; - int pfxlen; } ref_kind[] = { - { REF_LOCAL_BRANCH, "refs/heads/", 11 }, - { REF_REMOTE_BRANCH, "refs/remotes/", 13 }, + { REF_LOCAL_BRANCH, "refs/heads/" }, + { REF_REMOTE_BRANCH, "refs/remotes/" }, }; /* Detect kind */ for (i = 0; i < ARRAY_SIZE(ref_kind); i++) { prefix = ref_kind[i].prefix; - if (strncmp(refname, prefix, ref_kind[i].pfxlen)) - continue; - kind = ref_kind[i].kind; - refname += ref_kind[i].pfxlen; - break; + if (skip_prefix(refname, prefix, &refname)) { + kind = ref_kind[i].kind; + break; + } } if (ARRAY_SIZE(ref_kind) <= i) return 0; @@ -385,6 +389,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, newitem->commit = commit; newitem->width = utf8_strwidth(refname); newitem->dest = resolve_symref(orig_refname, prefix); + newitem->ignore = 0; /* adjust for "remotes/" */ if (newitem->kind == REF_REMOTE_BRANCH && ref_list->kinds != REF_REMOTE_BRANCH) @@ -484,17 +489,6 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, free(ref); } -static int matches_merge_filter(struct commit *commit) -{ - int is_merged; - - if (merge_filter == NO_FILTER) - return 1; - - is_merged = !!(commit->object.flags & UNINTERESTING); - return (is_merged == (merge_filter == SHOW_MERGED)); -} - static void add_verbose_info(struct strbuf *out, struct ref_item *item, int verbose, int abbrev) { @@ -522,10 +516,9 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, { char c; int color; - struct commit *commit = item->commit; struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; - if (!matches_merge_filter(commit)) + if (item->ignore) return; switch (item->kind) { @@ -575,7 +568,7 @@ static int calc_maxwidth(struct ref_list *refs) { int i, w = 0; for (i = 0; i < refs->index; i++) { - if (!matches_merge_filter(refs->list[i].commit)) + if (refs->list[i].ignore) continue; if (refs->list[i].width > w) w = refs->list[i].width; @@ -596,9 +589,16 @@ static char *get_head_description(void) else if (state.bisect_in_progress) strbuf_addf(&desc, _("(no branch, bisect started on %s)"), state.branch); - else if (state.detached_from) - strbuf_addf(&desc, _("(detached from %s)"), - state.detached_from); + else if (state.detached_from) { + /* TRANSLATORS: make sure these match _("HEAD detached at ") + and _("HEAD detached from ") in wt-status.c */ + if (state.detached_at) + strbuf_addf(&desc, _("(HEAD detached at %s)"), + state.detached_from); + else + strbuf_addf(&desc, _("(HEAD detached from %s)"), + state.detached_from); + } else strbuf_addstr(&desc, _("(no branch)")); free(state.branch); @@ -618,6 +618,7 @@ static void show_detached(struct ref_list *ref_list) item.kind = REF_LOCAL_BRANCH; item.dest = NULL; item.commit = head_commit; + item.ignore = 0; if (item.width > ref_list->maxwidth) ref_list->maxwidth = item.width; print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, ""); @@ -653,7 +654,23 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru add_pending_object(&ref_list.revs, (struct object *) filter, ""); ref_list.revs.limited = 1; - prepare_revision_walk(&ref_list.revs); + + if (prepare_revision_walk(&ref_list.revs)) + die(_("revision walk setup failed")); + + for (i = 0; i < ref_list.index; i++) { + struct ref_item *item = &ref_list.list[i]; + struct commit *commit = item->commit; + int is_merged = !!(commit->object.flags & UNINTERESTING); + item->ignore = is_merged != (merge_filter == SHOW_MERGED); + } + + for (i = 0; i < ref_list.index; i++) { + struct ref_item *item = &ref_list.list[i]; + clear_commit_marks(item->commit, ALL_REV_FLAGS); + } + clear_commit_marks(filter, ALL_REV_FLAGS); + if (verbose) ref_list.maxwidth = calc_maxwidth(&ref_list); } @@ -754,7 +771,6 @@ static const char edit_description[] = "BRANCH_DESCRIPTION"; static int edit_branch_description(const char *branch_name) { - FILE *fp; int status; struct strbuf buf = STRBUF_INIT; struct strbuf name = STRBUF_INIT; @@ -767,8 +783,7 @@ static int edit_branch_description(const char *branch_name) " %s\n" "Lines starting with '%c' will be stripped.\n", branch_name, comment_line_char); - fp = fopen(git_path(edit_description), "w"); - if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) { + if (write_file(git_path(edit_description), 0, "%s", buf.buf)) { strbuf_release(&buf); return error(_("could not write branch description template: %s"), strerror(errno)); @@ -790,7 +805,7 @@ static int edit_branch_description(const char *branch_name) int cmd_branch(int argc, const char **argv, const char *prefix) { - int delete = 0, rename = 0, force_create = 0, list = 0; + int delete = 0, rename = 0, force = 0, list = 0; int verbose = 0, abbrev = -1, detached = 0; int reflog = 0, edit_description = 0; int quiet = 0, unset_upstream = 0; @@ -838,7 +853,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BOOL('l', "create-reflog", &reflog, N_("create the branch's reflog")), OPT_BOOL(0, "edit-description", &edit_description, N_("edit the description for the branch")), - OPT__FORCE(&force_create, N_("force creation (when already exists)")), + OPT__FORCE(&force, N_("force creation, move/rename, deletion")), { OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, N_("commit"), N_("print only not merged branches"), @@ -862,16 +877,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix) track = git_branch_track; - head = resolve_refdup("HEAD", head_sha1, 0, NULL); + head = resolve_refdup("HEAD", 0, head_sha1, NULL); if (!head) die(_("Failed to resolve HEAD as a valid ref.")); - if (!strcmp(head, "HEAD")) { + if (!strcmp(head, "HEAD")) detached = 1; - } else { - if (!starts_with(head, "refs/heads/")) - die(_("HEAD not found below refs/heads!")); - head += 11; - } + else if (!skip_prefix(head, "refs/heads/", &head)) + die(_("HEAD not found below refs/heads!")); hashcpy(merge_filter_ref, head_sha1); @@ -884,7 +896,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (with_commit || merge_filter != NO_FILTER) list = 1; - if (!!delete + !!rename + !!force_create + !!new_upstream + + if (!!delete + !!rename + !!new_upstream + list + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); @@ -897,6 +909,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix) colopts = 0; } + if (force) { + delete *= 2; + rename *= 2; + } + if (delete) { if (!argc) die(_("branch name required")); @@ -1013,7 +1030,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) branch_existed = ref_exists(branch->refname); create_branch(head, argv[0], (argc == 2) ? argv[1] : head, - force_create, reflog, 0, quiet, track); + force, reflog, 0, quiet, track); /* * We only show the instructions if the user gave us diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 707330499f..ecb488822f 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -4,22 +4,25 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" -#include "exec_cmd.h" -#include "tag.h" -#include "tree.h" #include "builtin.h" #include "parse-options.h" -#include "diff.h" #include "userdiff.h" #include "streaming.h" -static int cat_one_file(int opt, const char *exp_type, const char *obj_name) +static int cat_one_file(int opt, const char *exp_type, const char *obj_name, + int unknown_type) { unsigned char sha1[20]; enum object_type type; char *buf; unsigned long size; struct object_context obj_context; + struct object_info oi = {NULL}; + struct strbuf sb = STRBUF_INIT; + unsigned flags = LOOKUP_REPLACE_OBJECT; + + if (unknown_type) + flags |= LOOKUP_UNKNOWN_OBJECT; if (get_sha1_with_context(obj_name, 0, sha1, &obj_context)) die("Not a valid object name %s", obj_name); @@ -27,20 +30,22 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) buf = NULL; switch (opt) { case 't': - type = sha1_object_info(sha1, NULL); - if (type > 0) { - printf("%s\n", typename(type)); + oi.typename = &sb; + if (sha1_object_info_extended(sha1, &oi, flags) < 0) + die("git cat-file: could not get object info"); + if (sb.len) { + printf("%s\n", sb.buf); + strbuf_release(&sb); return 0; } break; case 's': - type = sha1_object_info(sha1, &size); - if (type > 0) { - printf("%lu\n", size); - return 0; - } - break; + oi.sizep = &size; + if (sha1_object_info_extended(sha1, &oi, flags) < 0) + die("git cat-file: could not get object info"); + printf("%lu\n", size); + return 0; case 'e': return !has_sha1_file(sha1); @@ -79,11 +84,10 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) if (type_from_string(exp_type) == OBJ_BLOB) { unsigned char blob_sha1[20]; if (sha1_object_info(sha1, NULL) == OBJ_TAG) { - enum object_type type; - unsigned long size; char *buffer = read_sha1_file(sha1, &type, &size); - if (memcmp(buffer, "object ", 7) || - get_sha1_hex(buffer + 7, blob_sha1)) + const char *target; + if (!skip_prefix(buffer, "object ", &target) || + get_sha1_hex(target, blob_sha1)) die("%s not a valid tag", sha1_to_hex(sha1)); free(buffer); } else @@ -328,8 +332,8 @@ static int batch_objects(struct batch_options *opt) } static const char * const cat_file_usage[] = { - N_("git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>"), - N_("git cat-file (--batch|--batch-check) < <list_of_objects>"), + N_("git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<type>|--textconv) <object>"), + N_("git cat-file (--batch | --batch-check) < <list-of-objects>"), NULL }; @@ -364,16 +368,19 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) int opt = 0; const char *exp_type = NULL, *obj_name = NULL; struct batch_options batch = {0}; + int unknown_type = 0; const struct option options[] = { OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")), - OPT_SET_INT('t', NULL, &opt, N_("show object type"), 't'), - OPT_SET_INT('s', NULL, &opt, N_("show object size"), 's'), - OPT_SET_INT('e', NULL, &opt, + OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'), + OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), + OPT_CMDMODE('e', NULL, &opt, N_("exit with zero when there's no error"), 'e'), - OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'), - OPT_SET_INT(0, "textconv", &opt, + OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'), + OPT_CMDMODE(0, "textconv", &opt, N_("for blob objects, run textconv on object's content"), 'c'), + OPT_BOOL( 0, "allow-unknown-type", &unknown_type, + N_("allow -s and -t to work with broken/corrupt objects")), { OPTION_CALLBACK, 0, "batch", &batch, "format", N_("show info and content of objects fed from the standard input"), PARSE_OPT_OPTARG, batch_option_callback }, @@ -385,9 +392,6 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) git_config(git_cat_file_config, NULL); - if (argc != 3 && argc != 2) - usage_with_options(cat_file_usage, options); - argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0); if (opt) { @@ -410,5 +414,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) if (batch.enabled) return batch_objects(&batch); - return cat_one_file(opt, exp_type, obj_name); + if (unknown_type && opt != 't' && opt != 's') + die("git cat-file --allow-unknown-type: use with -s or -t"); + return cat_one_file(opt, exp_type, obj_name, unknown_type); } diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 5600ec3f61..21d2bedcc9 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -8,8 +8,8 @@ static int all_attrs; static int cached_attrs; static int stdin_paths; static const char * const check_attr_usage[] = { -N_("git check-attr [-a | --all | attr...] [--] pathname..."), -N_("git check-attr --stdin [-z] [-a | --all | attr...] < <list-of-paths>"), +N_("git check-attr [-a | --all | <attr>...] [--] <pathname>..."), +N_("git check-attr --stdin [-z] [-a | --all | <attr>...] < <list-of-paths>"), NULL }; diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index 594463a11b..dc8d97c56c 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -7,8 +7,8 @@ static int quiet, verbose, stdin_paths, show_non_matching, no_index; static const char * const check_ignore_usage[] = { -"git check-ignore [options] pathname...", -"git check-ignore [options] --stdin < <list-of-paths>", +"git check-ignore [<options>] <pathname>...", +"git check-ignore [<options>] --stdin < <list-of-paths>", NULL }; diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c index 8f4d809bd8..eaaea546d3 100644 --- a/builtin/check-mailmap.c +++ b/builtin/check-mailmap.c @@ -5,7 +5,7 @@ static int use_stdin; static const char * const check_mailmap_usage[] = { -N_("git check-mailmap [options] <contact>..."), +N_("git check-mailmap [<options>] <contact>..."), NULL }; diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index 28a7320271..fd915d5984 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -8,7 +8,7 @@ #include "strbuf.h" static const char builtin_check_ref_format_usage[] = -"git check-ref-format [--normalize] [options] <refname>\n" +"git check-ref-format [--normalize] [<options>] <refname>\n" " or: git check-ref-format --branch <branchname-shorthand>"; /* diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 61e75eb60c..8028c3768f 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -5,7 +5,7 @@ * */ #include "builtin.h" -#include "cache.h" +#include "lockfile.h" #include "quote.h" #include "cache-tree.h" #include "parse-options.h" @@ -18,7 +18,7 @@ static char topath[4][TEMPORARY_FILENAME_LENGTH + 1]; static struct checkout state; -static void write_tempfile_record(const char *name, int prefix_length) +static void write_tempfile_record(const char *name, const char *prefix) { int i; @@ -35,14 +35,14 @@ static void write_tempfile_record(const char *name, int prefix_length) fputs(topath[checkout_stage], stdout); putchar('\t'); - write_name_quoted(name + prefix_length, stdout, line_termination); + write_name_quoted_relative(name, prefix, stdout, line_termination); for (i = 0; i < 4; i++) { topath[i][0] = 0; } } -static int checkout_file(const char *name, int prefix_length) +static int checkout_file(const char *name, const char *prefix) { int namelen = strlen(name); int pos = cache_name_pos(name, namelen); @@ -71,7 +71,7 @@ static int checkout_file(const char *name, int prefix_length) if (did_checkout) { if (to_tempfile) - write_tempfile_record(name, prefix_length); + write_tempfile_record(name, prefix); return errs > 0 ? -1 : 0; } @@ -106,7 +106,7 @@ static void checkout_all(const char *prefix, int prefix_length) if (last_ce && to_tempfile) { if (ce_namelen(last_ce) != ce_namelen(ce) || memcmp(last_ce->name, ce->name, ce_namelen(ce))) - write_tempfile_record(last_ce->name, prefix_length); + write_tempfile_record(last_ce->name, prefix); } if (checkout_entry(ce, &state, to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) @@ -114,7 +114,7 @@ static void checkout_all(const char *prefix, int prefix_length) last_ce = ce; } if (last_ce && to_tempfile) - write_tempfile_record(last_ce->name, prefix_length); + write_tempfile_record(last_ce->name, prefix); if (errs) /* we have already done our error reporting. * exit with the same code as die(). @@ -123,7 +123,7 @@ static void checkout_all(const char *prefix, int prefix_length) } static const char * const builtin_checkout_index_usage[] = { - N_("git checkout-index [options] [--] [<file>...]"), + N_("git checkout-index [<options>] [--] [<file>...]"), NULL }; @@ -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; @@ -240,16 +241,15 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) /* Check out named files first */ for (i = 0; i < argc; i++) { const char *arg = argv[i]; - const char *p; + char *p; if (all) die("git checkout-index: don't mix '--all' and explicit filenames"); if (read_from_stdin) die("git checkout-index: don't mix '--stdin' and explicit filenames"); p = prefix_path(prefix, prefix_length, arg); - checkout_file(p, prefix_length); - if (p < arg || p > arg + strlen(arg)) - free((char *)p); + checkout_file(p, prefix); + free(p); } if (read_from_stdin) { @@ -259,7 +259,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) die("git checkout-index: don't mix '--all' and '--stdin'"); while (strbuf_getline(&buf, stdin, line_termination) != EOF) { - const char *p; + char *p; if (line_termination && buf.buf[0] == '"') { strbuf_reset(&nbuf); if (unquote_c_style(&nbuf, buf.buf, NULL)) @@ -267,9 +267,8 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) strbuf_swap(&buf, &nbuf); } p = prefix_path(prefix, prefix_length, buf.buf); - checkout_file(p, prefix_length); - if (p < buf.buf || p > buf.buf + buf.len) - free((char *)p); + checkout_file(p, prefix); + free(p); } strbuf_release(&nbuf); strbuf_release(&buf); @@ -279,8 +278,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..2f92328db4 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1,5 +1,5 @@ -#include "cache.h" #include "builtin.h" +#include "lockfile.h" #include "parse-options.h" #include "refs.h" #include "commit.h" @@ -20,10 +20,11 @@ #include "resolve-undo.h" #include "submodule.h" #include "argv-array.h" +#include "sigchain.h" static const char * const checkout_usage[] = { - N_("git checkout [options] <branch>"), - N_("git checkout [options] [<branch>] -- <file>..."), + N_("git checkout [<options>] <branch>"), + N_("git checkout [<options>] [<branch>] -- <file>..."), NULL, }; @@ -36,6 +37,7 @@ struct checkout_opts { int writeout_stage; int overwrite_ignore; int ignore_skipworktree; + int ignore_other_worktrees; const char *new_branch; const char *new_branch_force; @@ -48,6 +50,10 @@ struct checkout_opts { const char *prefix; struct pathspec pathspec; struct tree *source_tree; + + const char *new_worktree; + const char **saved_argv; + int new_worktree_mode; }; static int post_checkout_hook(struct commit *old, struct commit *new, @@ -62,23 +68,41 @@ static int post_checkout_hook(struct commit *old, struct commit *new, } -static int update_some(const unsigned char *sha1, const char *base, int baselen, +static int update_some(const unsigned char *sha1, struct strbuf *base, const char *pathname, unsigned mode, int stage, void *context) { int len; struct cache_entry *ce; + int pos; if (S_ISDIR(mode)) return READ_TREE_RECURSIVE; - len = baselen + strlen(pathname); + len = base->len + strlen(pathname); ce = xcalloc(1, cache_entry_size(len)); hashcpy(ce->sha1, sha1); - memcpy(ce->name, base, baselen); - memcpy(ce->name + baselen, pathname, len - baselen); + memcpy(ce->name, base->buf, base->len); + memcpy(ce->name + base->len, pathname, len - base->len); ce->ce_flags = create_ce_flags(0) | CE_UPDATE; ce->ce_namelen = len; ce->ce_mode = create_ce_mode(mode); + + /* + * If the entry is the same as the current index, we can leave the old + * entry in place. Whether it is UPTODATE or not, checkout_entry will + * do the right thing. + */ + pos = cache_name_pos(ce->name, ce->ce_namelen); + if (pos >= 0) { + struct cache_entry *old = active_cache[pos]; + if (ce->ce_mode == old->ce_mode && + !hashcmp(ce->sha1, old->sha1)) { + old->ce_flags |= CE_UPDATE; + free(ce); + return 0; + } + } + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); return 0; } @@ -225,7 +249,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) @@ -250,13 +273,16 @@ static int checkout_paths(const struct checkout_opts *opts, die(_("Cannot update paths and switch to branch '%s' at the same time."), opts->new_branch); + if (opts->new_worktree) + die(_("'%s' cannot be used with updating paths"), "--to"); + if (opts->patch_mode) return run_add_interactive(revision, "--patch=checkout", &opts->pathspec); 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 +363,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,11 +379,10 @@ 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); + read_ref_full("HEAD", 0, rev, &flag); head = lookup_commit_reference_gently(rev, 1); errs |= post_checkout_hook(head, head, 0); @@ -424,6 +450,11 @@ struct branch_info { const char *name; /* The short name used */ const char *path; /* The full name of a real branch */ struct commit *commit; /* The named commit */ + /* + * if not null the branch is detached because it's already + * checked out in this checkout + */ + char *checkout; }; static void setup_branch_path(struct branch_info *branch) @@ -444,8 +475,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")); @@ -485,7 +516,7 @@ static int merge_working_tree(const struct checkout_opts *opts, topts.dir->flags |= DIR_SHOW_IGNORED; setup_standard_excludes(topts.dir); } - tree = parse_tree_indirect(old->commit ? + tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ? old->commit->object.sha1 : EMPTY_TREE_SHA1_BIN); init_tree_desc(&trees[0], tree->buffer, tree->size); @@ -553,8 +584,13 @@ static int merge_working_tree(const struct checkout_opts *opts, } } - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + if (!cache_tree_fully_valid(active_cache_tree)) + cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); + + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); if (!opts->force && !opts->quiet) @@ -584,18 +620,21 @@ static void update_refs_for_switch(const struct checkout_opts *opts, if (opts->new_orphan_branch) { if (opts->new_branch_log && !log_all_ref_updates) { int temp; - char log_file[PATH_MAX]; - char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch); + struct strbuf log_file = STRBUF_INIT; + int ret; + const char *ref_name; + ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch); temp = log_all_ref_updates; log_all_ref_updates = 1; - if (log_ref_setup(ref_name, log_file, sizeof(log_file))) { + ret = log_ref_setup(ref_name, &log_file); + log_all_ref_updates = temp; + strbuf_release(&log_file); + if (ret) { fprintf(stderr, _("Can not do reflog for '%s'\n"), opts->new_orphan_branch); - log_all_ref_updates = temp; return; } - log_all_ref_updates = temp; } } else @@ -624,7 +663,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 +690,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(); @@ -725,10 +760,17 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) if (advice_detached_head) fprintf(stderr, - _( + Q_( + /* The singular version */ + "If you want to keep it by creating a new branch, " + "this may be a good time\nto do so with:\n\n" + " git branch <new-branch-name> %s\n\n", + /* The plural version */ "If you want to keep them by creating a new branch, " "this may be a good time\nto do so with:\n\n" - " git branch new_branch_name %s\n\n"), + " git branch <new-branch-name> %s\n\n", + /* Give ngettext() the count */ + lost), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); } @@ -775,13 +817,13 @@ static int switch_branches(const struct checkout_opts *opts, unsigned char rev[20]; int flag, writeout_error = 0; memset(&old, 0, sizeof(old)); - old.path = path_to_free = resolve_refdup("HEAD", rev, 0, &flag); + old.path = path_to_free = resolve_refdup("HEAD", 0, rev, &flag); old.commit = lookup_commit_reference_gently(rev, 1); 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"; @@ -797,7 +839,8 @@ static int switch_branches(const struct checkout_opts *opts, return ret; } - if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) + if (!opts->quiet && !old.path && old.commit && + new->commit != old.commit && !opts->new_worktree_mode) orphaned_commit_warning(old.commit, new->commit); update_refs_for_switch(opts, &old, new); @@ -807,6 +850,138 @@ static int switch_branches(const struct checkout_opts *opts, return ret || writeout_error; } +static char *junk_work_tree; +static char *junk_git_dir; +static int is_junk; +static pid_t junk_pid; + +static void remove_junk(void) +{ + struct strbuf sb = STRBUF_INIT; + if (!is_junk || getpid() != junk_pid) + return; + if (junk_git_dir) { + strbuf_addstr(&sb, junk_git_dir); + remove_dir_recursively(&sb, 0); + strbuf_reset(&sb); + } + if (junk_work_tree) { + strbuf_addstr(&sb, junk_work_tree); + remove_dir_recursively(&sb, 0); + } + strbuf_release(&sb); +} + +static void remove_junk_on_signal(int signo) +{ + remove_junk(); + sigchain_pop(signo); + raise(signo); +} + +static int prepare_linked_checkout(const struct checkout_opts *opts, + struct branch_info *new) +{ + struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + const char *path = opts->new_worktree, *name; + struct stat st; + struct child_process cp; + int counter = 0, len, ret; + + if (!new->commit) + die(_("no branch specified")); + if (file_exists(path) && !is_empty_dir(path)) + die(_("'%s' already exists"), path); + + len = strlen(path); + while (len && is_dir_sep(path[len - 1])) + len--; + + for (name = path + len - 1; name > path; name--) + if (is_dir_sep(*name)) { + name++; + break; + } + strbuf_addstr(&sb_repo, + git_path("worktrees/%.*s", (int)(path + len - name), name)); + len = sb_repo.len; + if (safe_create_leading_directories_const(sb_repo.buf)) + die_errno(_("could not create leading directories of '%s'"), + sb_repo.buf); + while (!stat(sb_repo.buf, &st)) { + counter++; + strbuf_setlen(&sb_repo, len); + strbuf_addf(&sb_repo, "%d", counter); + } + name = strrchr(sb_repo.buf, '/') + 1; + + junk_pid = getpid(); + atexit(remove_junk); + sigchain_push_common(remove_junk_on_signal); + + if (mkdir(sb_repo.buf, 0777)) + die_errno(_("could not create directory of '%s'"), sb_repo.buf); + junk_git_dir = xstrdup(sb_repo.buf); + is_junk = 1; + + /* + * lock the incomplete repo so prune won't delete it, unlock + * after the preparation is over. + */ + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + write_file(sb.buf, 1, "initializing\n"); + + strbuf_addf(&sb_git, "%s/.git", path); + if (safe_create_leading_directories_const(sb_git.buf)) + die_errno(_("could not create leading directories of '%s'"), + sb_git.buf); + junk_work_tree = xstrdup(path); + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); + write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf)); + write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n", + real_path(get_git_common_dir()), name); + /* + * This is to keep resolve_ref() happy. We need a valid HEAD + * or is_git_directory() will reject the directory. Any valid + * value would do because this value will be ignored and + * replaced at the next (real) checkout. + */ + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); + write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1)); + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/commondir", sb_repo.buf); + write_file(sb.buf, 1, "../..\n"); + + if (!opts->quiet) + fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name); + + setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1); + setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1); + setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1); + memset(&cp, 0, sizeof(cp)); + cp.git_cmd = 1; + cp.argv = opts->saved_argv; + ret = run_command(&cp); + if (!ret) { + is_junk = 0; + free(junk_work_tree); + free(junk_git_dir); + junk_work_tree = NULL; + junk_git_dir = NULL; + } + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + unlink_or_warn(sb.buf); + strbuf_release(&sb); + strbuf_release(&sb_repo); + strbuf_release(&sb_git); + return ret; +} + static int git_checkout_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "diff.ignoresubmodules")) { @@ -862,13 +1037,80 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1) return NULL; } +static void check_linked_checkout(struct branch_info *new, const char *id) +{ + struct strbuf sb = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; + struct strbuf gitdir = STRBUF_INIT; + const char *start, *end; + + if (id) + strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id); + else + strbuf_addf(&path, "%s/HEAD", get_git_common_dir()); + + if (strbuf_read_file(&sb, path.buf, 0) < 0 || + !skip_prefix(sb.buf, "ref:", &start)) + goto done; + while (isspace(*start)) + start++; + end = start; + while (*end && !isspace(*end)) + end++; + if (strncmp(start, new->path, end - start) || new->path[end - start] != '\0') + goto done; + if (id) { + strbuf_reset(&path); + strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id); + if (strbuf_read_file(&gitdir, path.buf, 0) <= 0) + goto done; + strbuf_rtrim(&gitdir); + } else + strbuf_addstr(&gitdir, get_git_common_dir()); + die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf); +done: + strbuf_release(&path); + strbuf_release(&sb); + strbuf_release(&gitdir); +} + +static void check_linked_checkouts(struct branch_info *new) +{ + struct strbuf path = STRBUF_INIT; + DIR *dir; + struct dirent *d; + + strbuf_addf(&path, "%s/worktrees", get_git_common_dir()); + if ((dir = opendir(path.buf)) == NULL) { + strbuf_release(&path); + return; + } + + /* + * $GIT_COMMON_DIR/HEAD is practically outside + * $GIT_DIR so resolve_ref_unsafe() won't work (it + * uses git_path). Parse the ref ourselves. + */ + check_linked_checkout(new, NULL); + + while ((d = readdir(dir)) != NULL) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + check_linked_checkout(new, d->d_name); + } + strbuf_release(&path); + closedir(dir); +} + static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new, - struct tree **source_tree, - unsigned char rev[20], - const char **new_branch) + struct checkout_opts *opts, + unsigned char rev[20]) { + struct tree **source_tree = &opts->source_tree; + const char **new_branch = &opts->new_branch; + int force_detach = opts->force_detach; int argcount = 0; unsigned char branch_rev[20]; const char *arg; @@ -989,6 +1231,17 @@ static int parse_branchname_arg(int argc, const char **argv, else new->path = NULL; /* not an existing branch */ + if (new->path && !force_detach && !*new_branch) { + unsigned char sha1[20]; + int flag; + char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag); + if (head_ref && + (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) && + !opts->ignore_other_worktrees) + check_linked_checkouts(new); + free(head_ref); + } + new->commit = lookup_commit_reference_gently(rev, 1); if (!new->commit) { /* not a commit */ @@ -1068,11 +1321,14 @@ static int checkout_branch(struct checkout_opts *opts, die(_("Cannot switch branch to a non-commit '%s'"), new->name); + if (opts->new_worktree) + return prepare_linked_checkout(opts, new); + if (!new->commit && opts->new_branch) { unsigned char rev[20]; int flag; - if (!read_ref_full("HEAD", rev, 0, &flag) && + if (!read_ref_full("HEAD", 0, rev, &flag) && (flag & REF_ISSYMREF) && is_null_sha1(rev)) return switch_unborn_to_new_branch(opts); } @@ -1109,7 +1365,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree, N_("do not limit pathspecs to sparse entries only")), OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch, - N_("second guess 'git checkout no-such-branch'")), + N_("second guess 'git checkout <no-such-branch>'")), + OPT_FILENAME(0, "to", &opts.new_worktree, + N_("check a branch out in a separate working directory")), + OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees, + N_("do not check if another worktree is holding the given ref")), OPT_END(), }; @@ -1118,6 +1378,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.overwrite_ignore = 1; opts.prefix = prefix; + opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2)); + memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1)); + gitmodules_config(); git_config(git_checkout_config, &opts); @@ -1126,6 +1389,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); + /* recursive execution from checkout_new_worktree() */ + opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL; + if (opts.new_worktree_mode) + opts.new_worktree = NULL; + + if (!opts.new_worktree) + setup_work_tree(); + if (conflict_style) { opts.merge = 1; /* implied */ git_xmerge_config("merge.conflictstyle", conflict_style, NULL); @@ -1150,10 +1421,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) const char *argv0 = argv[0]; if (!argc || !strcmp(argv0, "--")) die (_("--track needs a branch name")); - if (starts_with(argv0, "refs/")) - argv0 += 5; - if (starts_with(argv0, "remotes/")) - argv0 += 8; + skip_prefix(argv0, "refs/", &argv0); + skip_prefix(argv0, "remotes/", &argv0); argv0 = strchr(argv0, '/'); if (!argv0 || !argv0[1]) die (_("Missing branch name; try -b")); @@ -1181,8 +1450,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.track == BRANCH_TRACK_UNSPECIFIED && !opts.new_branch; int n = parse_branchname_arg(argc, argv, dwim_ok, - &new, &opts.source_tree, - rev, &opts.new_branch); + &new, &opts, rev); argv += n; argc -= n; } diff --git a/builtin/clean.c b/builtin/clean.c index 1032563e5f..98c103fa8b 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 { @@ -100,6 +100,8 @@ static int parse_clean_color_slot(const char *var) static int git_clean_config(const char *var, const char *value, void *cb) { + const char *slot_name; + if (starts_with(var, "column.")) return git_column_config(var, value, "clean", &colopts); @@ -109,15 +111,13 @@ static int git_clean_config(const char *var, const char *value, void *cb) clean_use_color = git_config_colorbool(var, value); return 0; } - if (starts_with(var, "color.interactive.")) { - int slot = parse_clean_color_slot(var + - strlen("color.interactive.")); + if (skip_prefix(var, "color.interactive.", &slot_name)) { + int slot = parse_clean_color_slot(slot_name); if (slot < 0) return 0; if (!value) return config_error_nonbool(var); - color_parse(value, var, clean_colors[slot]); - return 0; + return color_parse(value, clean_colors[slot]); } if (!strcmp(var, "clean.requireforce")) { @@ -321,7 +321,7 @@ static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen) switch (stuff->type) { default: - die("Bad type of menu_staff when print menu"); + die("Bad type of menu_stuff when print menu"); case MENU_STUFF_TYPE_MENU_ITEM: menu_item = (struct menu_item *)stuff->stuff; for (i = 0; i < stuff->nr; i++, menu_item++) { @@ -754,7 +754,8 @@ static int ask_each_cmd(void) /* Ctrl-D should stop removing files */ if (!eof) { qname = quote_path_relative(item->string, NULL, &buf); - printf(_("remove %s? "), qname); + /* TRANSLATORS: Make sure to keep [y/N] as is */ + printf(_("Remove %s [y/N]? "), qname); if (strbuf_getline(&confirm, stdin, '\n') != EOF) { strbuf_trim(&confirm); } else { diff --git a/builtin/clone.c b/builtin/clone.c index 545105a86f..166a645e2d 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -9,6 +9,7 @@ */ #include "builtin.h" +#include "lockfile.h" #include "parse-options.h" #include "fetch-pack.h" #include "refs.h" @@ -33,7 +34,7 @@ * */ static const char * const builtin_clone_usage[] = { - N_("git clone [options] [--] <repo> [<dir>]"), + N_("git clone [<options>] [--] <repo> [<dir>]"), NULL }; @@ -48,6 +49,7 @@ static int option_verbosity; static int option_progress = -1; static struct string_list option_config; static struct string_list option_reference; +static int option_dissociate; static int opt_parse_reference(const struct option *opt, const char *arg, int unset) { @@ -93,6 +95,8 @@ static struct option builtin_clone_options[] = { N_("create a shallow clone of that depth")), OPT_BOOL(0, "single-branch", &option_single_branch, N_("clone only one branch, HEAD or --branch")), + OPT_BOOL(0, "dissociate", &option_dissociate, + N_("use --reference only while cloning")), OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), N_("separate git dir from working tree")), OPT_STRING_LIST('c', "config", &option_config, N_("key=value"), @@ -289,16 +293,17 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst, struct strbuf line = STRBUF_INIT; while (strbuf_getline(&line, in, '\n') != EOF) { - char *abs_path, abs_buf[PATH_MAX]; + char *abs_path; if (!line.len || line.buf[0] == '#') continue; if (is_absolute_path(line.buf)) { add_to_alternates_file(line.buf); continue; } - abs_path = mkpath("%s/objects/%s", src_repo, line.buf); - normalize_path_copy(abs_buf, abs_path); - add_to_alternates_file(abs_buf); + abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf); + normalize_path_copy(abs_path, abs_path); + add_to_alternates_file(abs_path); + free(abs_path); } strbuf_release(&line); fclose(in); @@ -390,7 +395,6 @@ static void clone_local(const char *src_repo, const char *dest_repo) static const char *junk_work_tree; static const char *junk_git_dir; -static pid_t junk_pid; static enum { JUNK_LEAVE_NONE, JUNK_LEAVE_REPO, @@ -417,8 +421,6 @@ static void remove_junk(void) break; } - if (getpid() != junk_pid) - return; if (junk_git_dir) { strbuf_addstr(&sb, junk_git_dir); remove_dir_recursively(&sb, 0); @@ -521,7 +523,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 +586,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 +607,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,12 +619,12 @@ 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; - head = resolve_refdup("HEAD", sha1, 1, NULL); + head = resolve_refdup("HEAD", RESOLVE_REF_READING, sha1, NULL); if (!head) { warning(_("remote HEAD refers to nonexistent ref, " "unable to checkout.\n")); @@ -640,7 +643,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 +659,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), @@ -685,9 +687,10 @@ static void write_config(struct string_list *config) } } -static void write_refspec_config(const char* src_ref_prefix, - const struct ref* our_head_points_at, - const struct ref* remote_head_points_at, struct strbuf* branch_top) +static void write_refspec_config(const char *src_ref_prefix, + const struct ref *our_head_points_at, + const struct ref *remote_head_points_at, + struct strbuf *branch_top) { struct strbuf key = STRBUF_INIT; struct strbuf value = STRBUF_INIT; @@ -702,9 +705,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 @@ -733,6 +739,16 @@ static void write_refspec_config(const char* src_ref_prefix, strbuf_release(&value); } +static void dissociate_from_references(void) +{ + static const char* argv[] = { "repack", "-a", "-d", NULL }; + + if (run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDIN)) + die(_("cannot repack to clean up")); + if (unlink(git_path("objects/info/alternates")) && errno != ENOENT) + die_errno(_("cannot unlink temporary alternates file")); +} + int cmd_clone(int argc, const char **argv, const char *prefix) { int is_bundle = 0, is_local; @@ -755,8 +771,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) struct refspec *refspec; const char *fetch_pattern; - junk_pid = getpid(); - packet_trace_identity("clone"); argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); @@ -796,18 +810,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) @@ -841,20 +843,21 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_dir = mkpathdup("%s/.git", dir); } + atexit(remove_junk); + sigchain_push_common(remove_junk_on_signal); + if (!option_bare) { - junk_work_tree = work_tree; if (safe_create_leading_directories_const(work_tree) < 0) die_errno(_("could not create leading directories of '%s'"), work_tree); if (!dest_exists && mkdir(work_tree, 0777)) - die_errno(_("could not create work tree dir '%s'."), + die_errno(_("could not create work tree dir '%s'"), work_tree); + junk_work_tree = work_tree; set_git_work_tree(work_tree); } - junk_git_dir = git_dir; - atexit(remove_junk); - sigchain_push_common(remove_junk_on_signal); + junk_git_dir = git_dir; if (safe_create_leading_directories_const(git_dir) < 0) die(_("could not create leading directories of '%s'"), git_dir); @@ -892,6 +895,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_reference.nr) setup_reference(); + else if (option_dissociate) { + warning(_("--dissociate given, but there is no --reference")); + option_dissociate = 0; + } fetch_pattern = value.buf; refspec = parse_fetch_refspec(1, &fetch_pattern); @@ -900,6 +907,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)) @@ -992,6 +1012,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport_unlock_pack(transport); transport_disconnect(transport); + if (option_dissociate) + dissociate_from_references(); + junk_mode = JUNK_LEAVE_REPO; err = checkout(); @@ -1000,5 +1023,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) strbuf_release(&key); strbuf_release(&value); junk_mode = JUNK_LEAVE_ALL; + + free(refspec); return err; } diff --git a/builtin/column.c b/builtin/column.c index 75818520e1..449413c8a8 100644 --- a/builtin/column.c +++ b/builtin/column.c @@ -6,7 +6,7 @@ #include "column.h" static const char * const builtin_column_usage[] = { - N_("git column [options]"), + N_("git column [<options>]"), NULL }; static unsigned int colopts; diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 8a66c74e0f..25aa2cdef3 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -66,10 +66,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) continue; } - if (!memcmp(arg, "-S", 2)) { - sign_commit = arg + 2; + if (skip_prefix(arg, "-S", &sign_commit)) continue; - } if (!strcmp(arg, "--no-gpg-sign")) { sign_commit = NULL; diff --git a/builtin/commit.c b/builtin/commit.c index 39cf8976e3..d6515a2a50 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -6,6 +6,7 @@ */ #include "cache.h" +#include "lockfile.h" #include "cache-tree.h" #include "color.h" #include "dir.h" @@ -33,16 +34,29 @@ #include "mailmap.h" static const char * const builtin_commit_usage[] = { - N_("git commit [options] [--] <pathspec>..."), + N_("git commit [<options>] [--] <pathspec>..."), NULL }; static const char * const builtin_status_usage[] = { - N_("git status [options] [--] <pathspec>..."), + N_("git status [<options>] [--] <pathspec>..."), NULL }; -static const char implicit_ident_advice[] = +static const char implicit_ident_advice_noconfig[] = +N_("Your name and email address were configured automatically based\n" +"on your username and hostname. Please check that they are accurate.\n" +"You can suppress this message by setting them explicitly. Run the\n" +"following command and follow the instructions in your editor to edit\n" +"your configuration file:\n" +"\n" +" git config --global --edit\n" +"\n" +"After doing this, you may fix the identity used for this commit with:\n" +"\n" +" git commit --amend --reset-author\n"); + +static const char implicit_ident_advice_config[] = N_("Your name and email address were configured automatically based\n" "on your username and hostname. Please check that they are accurate.\n" "You can suppress this message by setting them explicitly:\n" @@ -156,7 +170,7 @@ static void determine_whence(struct wt_status *s) whence = FROM_MERGE; else if (file_exists(git_path("CHERRY_PICK_HEAD"))) { whence = FROM_CHERRY_PICK; - if (file_exists(git_path("sequencer"))) + if (file_exists(git_path(SEQ_DIR))) sequencer_in_use = 1; } else @@ -215,7 +229,7 @@ static int commit_index_files(void) static int list_paths(struct string_list *list, const char *with_tree, const char *prefix, const struct pathspec *pattern) { - int i; + int i, ret; char *m; if (!pattern->nr) @@ -242,7 +256,9 @@ static int list_paths(struct string_list *list, const char *with_tree, item->util = item; /* better a valid pointer than a fake one */ } - return report_path_error(m, pattern, prefix); + ret = report_path_error(m, pattern, prefix); + free(m); + return ret; } static void add_remove_files(struct string_list *list) @@ -302,10 +318,9 @@ static void refresh_cache_or_die(int refresh_flags) die_resolve_conflict("commit"); } -static char *prepare_index(int argc, const char **argv, const char *prefix, - const struct commit *current_head, int is_status) +static const 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,16 +336,15 @@ 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); - setenv(INDEX_ENVIRONMENT, index_lock.filename, 1); + setenv(INDEX_ENVIRONMENT, index_lock.filename.buf, 1); if (interactive_add(argc, argv, prefix, patch_interactive) != 0) die(_("interactive add failed")); @@ -341,10 +355,17 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, unsetenv(INDEX_ENVIRONMENT); discard_cache(); - read_cache_from(index_lock.filename); + read_cache_from(index_lock.filename.buf); + if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) { + if (reopen_lock_file(&index_lock) < 0) + die(_("unable to write index file")); + if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) + die(_("unable to update temporary index")); + } else + warning(_("Failed to update main cache tree")); commit_style = COMMIT_NORMAL; - return index_lock.filename; + return index_lock.filename.buf; } /* @@ -360,15 +381,14 @@ 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; + return index_lock.filename.buf; } /* @@ -381,12 +401,16 @@ 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) { + if (active_cache_changed + || !cache_tree_fully_valid(active_cache_tree)) { update_main_cache_tree(WRITE_TREE_SILENT); - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(&index_lock)) + active_cache_changed = 1; + } + if (active_cache_changed) { + 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 +447,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,30 +455,29 @@ 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)) + update_main_cache_tree(WRITE_TREE_SILENT); + 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(); - read_cache_from(false_lock.filename); + read_cache_from(false_lock.filename.buf); - return false_lock.filename; + return false_lock.filename.buf; } static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, @@ -502,6 +524,12 @@ static int is_a_merge(const struct commit *current_head) return !!(current_head->parents && current_head->parents->next); } +static void assert_split_ident(struct ident_split *id, const struct strbuf *buf) +{ + if (split_ident_line(id, buf->buf, buf->len) || !id->date_begin) + die("BUG: unable to parse our own ident: %s", buf->buf); +} + static void export_one(const char *var, const char *s, const char *e, int hack) { struct strbuf buf = STRBUF_INIT; @@ -512,18 +540,25 @@ static void export_one(const char *var, const char *s, const char *e, int hack) strbuf_release(&buf); } -static int sane_ident_split(struct ident_split *person) +static int parse_force_date(const char *in, struct strbuf *out) { - if (!person->name_begin || !person->name_end || - person->name_begin == person->name_end) - return 0; /* no human readable name */ - if (!person->mail_begin || !person->mail_end || - person->mail_begin == person->mail_end) - return 0; /* no usable mail */ - if (!person->date_begin || !person->date_end || - !person->tz_begin || !person->tz_end) - return 0; - return 1; + strbuf_addch(out, '@'); + + if (parse_date(in, out) < 0) { + int errors = 0; + unsigned long t = approxidate_careful(in, &errors); + if (errors) + return -1; + strbuf_addf(out, "%lu", t); + } + + return 0; +} + +static void set_ident_var(char **buf, char *val) +{ + free(*buf); + *buf = val; } static void determine_author_info(struct strbuf *author_ident) @@ -531,67 +566,93 @@ static void determine_author_info(struct strbuf *author_ident) char *name, *email, *date; struct ident_split author; - name = getenv("GIT_AUTHOR_NAME"); - email = getenv("GIT_AUTHOR_EMAIL"); - date = getenv("GIT_AUTHOR_DATE"); + name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME")); + email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL")); + date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE")); if (author_message) { - const char *a, *lb, *rb, *eol; + struct ident_split ident; size_t len; + const char *a; - a = strstr(author_message_buffer, "\nauthor "); + a = find_commit_header(author_message_buffer, "author", &len); if (!a) - die(_("invalid commit: %s"), author_message); - - lb = strchrnul(a + strlen("\nauthor "), '<'); - rb = strchrnul(lb, '>'); - eol = strchrnul(rb, '\n'); - if (!*lb || !*rb || !*eol) - die(_("invalid commit: %s"), author_message); - - if (lb == a + strlen("\nauthor ")) - /* \nauthor <foo@example.com> */ - name = xcalloc(1, 1); - else - name = xmemdupz(a + strlen("\nauthor "), - (lb - strlen(" ") - - (a + strlen("\nauthor ")))); - email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<"))); - len = eol - (rb + strlen("> ")); - date = xmalloc(len + 2); - *date = '@'; - memcpy(date + 1, rb + strlen("> "), len); - date[len + 1] = '\0'; + die(_("commit '%s' lacks author header"), author_message); + if (split_ident_line(&ident, a, len) < 0) + die(_("commit '%s' has malformed author line"), author_message); + + set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); + set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); + + if (ident.date_begin) { + struct strbuf date_buf = STRBUF_INIT; + strbuf_addch(&date_buf, '@'); + strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); + strbuf_addch(&date_buf, ' '); + strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); + } } if (force_author) { - const char *lb = strstr(force_author, " <"); - const char *rb = strchr(force_author, '>'); + struct ident_split ident; - if (!lb || !rb) + if (split_ident_line(&ident, force_author, strlen(force_author)) < 0) die(_("malformed --author parameter")); - name = xstrndup(force_author, lb - force_author); - email = xstrndup(lb + 2, rb - (lb + 2)); + set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); + set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); } - if (force_date) - date = force_date; - 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)) { - export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0); - export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0); - export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@'); + if (force_date) { + struct strbuf date_buf = STRBUF_INIT; + if (parse_force_date(force_date, &date_buf)) + die(_("invalid date format: %s"), force_date); + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); } + + strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT)); + assert_split_ident(&author, author_ident); + export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0); + export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0); + export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@'); + free(name); + free(email); + free(date); +} + +static int author_date_is_interesting(void) +{ + return author_message || force_date; } -static char *cut_ident_timestamp_part(char *string) +static void adjust_comment_line_char(const struct strbuf *sb) { - char *ket = strrchr(string, '>'); - if (!ket || ket[1] != ' ') - die(_("Malformed ident string: '%s'"), string); - *++ket = '\0'; - return ket; + char candidates[] = "#;@!$%^&|:"; + char *candidate; + const char *p; + + comment_line_char = candidates[0]; + if (!memchr(sb->buf, comment_line_char, sb->len)) + return; + + p = sb->buf; + candidate = strchr(candidates, *p); + if (candidate) + *candidate = ' '; + for (p = sb->buf; *p; p++) { + if ((p[0] == '\n' || p[0] == '\r') && p[1]) { + candidate = strchr(candidates, p[1]); + if (candidate) + *candidate = ' '; + } + } + + for (p = candidates; *p == ' '; p++) + ; + if (!*p) + die(_("unable to select a comment character that is not used\n" + "in the current commit message")); + comment_line_char = *p; } static int prepare_to_commit(const char *index_file, const char *prefix, @@ -651,7 +712,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) { @@ -717,36 +778,14 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (clean_message_contents) stripspace(&sb, 0); - if (signoff) { - /* - * See if we have a Conflicts: block at the end. If yes, count - * its size, so we can ignore it. - */ - int ignore_footer = 0; - int i, eol, previous = 0; - const char *nl; - - for (i = 0; i < sb.len; i++) { - nl = memchr(sb.buf + i, '\n', sb.len - i); - if (nl) - eol = nl - sb.buf; - else - eol = sb.len; - if (starts_with(sb.buf + previous, "\nConflicts:\n")) { - ignore_footer = sb.len - previous; - break; - } - while (i < eol) - i++; - previous = eol; - } - - append_signoff(&sb, ignore_footer, 0); - } + if (signoff) + append_signoff(&sb, ignore_non_trailer(&sb), 0); 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 +793,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 +834,45 @@ 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)) + /* + * These should never fail because they come from our own + * fmt_ident. They may fail the sane_ident test, but we know + * that the name and mail pointers will at least be valid, + * which is enough for our tests and printing here. + */ + assert_split_ident(&ai, author_ident); + assert_split_ident(&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 +1007,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 +1016,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); @@ -989,7 +1041,8 @@ static const char *find_author_by_nickname(const char *name) revs.mailmap = &mailmap; read_mailmap(revs.mailmap, NULL); - prepare_revision_walk(&revs); + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed")); commit = get_revision(&revs); if (commit) { struct pretty_print_context ctx = {0}; @@ -999,7 +1052,7 @@ static const char *find_author_by_nickname(const char *name) clear_mailmap(&mailmap); return strbuf_detach(&buf, NULL); } - die(_("No existing author found with '%s'"), name); + die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name); } @@ -1179,22 +1232,21 @@ static int dry_run_commit(int argc, const char **argv, const char *prefix, return commitable ? 0 : 1; } -static int parse_status_slot(const char *var, int offset) +static int parse_status_slot(const char *slot) { - if (!strcasecmp(var+offset, "header")) + if (!strcasecmp(slot, "header")) return WT_STATUS_HEADER; - if (!strcasecmp(var+offset, "branch")) + if (!strcasecmp(slot, "branch")) return WT_STATUS_ONBRANCH; - if (!strcasecmp(var+offset, "updated") - || !strcasecmp(var+offset, "added")) + if (!strcasecmp(slot, "updated") || !strcasecmp(slot, "added")) return WT_STATUS_UPDATED; - if (!strcasecmp(var+offset, "changed")) + if (!strcasecmp(slot, "changed")) return WT_STATUS_CHANGED; - if (!strcasecmp(var+offset, "untracked")) + if (!strcasecmp(slot, "untracked")) return WT_STATUS_UNTRACKED; - if (!strcasecmp(var+offset, "nobranch")) + if (!strcasecmp(slot, "nobranch")) return WT_STATUS_NOBRANCH; - if (!strcasecmp(var+offset, "unmerged")) + if (!strcasecmp(slot, "unmerged")) return WT_STATUS_UNMERGED; return -1; } @@ -1202,6 +1254,7 @@ static int parse_status_slot(const char *var, int offset) static int git_status_config(const char *k, const char *v, void *cb) { struct wt_status *s = cb; + const char *slot_name; if (starts_with(k, "column.")) return git_column_config(k, v, "status", &s->colopts); @@ -1231,14 +1284,14 @@ static int git_status_config(const char *k, const char *v, void *cb) s->display_comment_prefix = git_config_bool(k, v); return 0; } - if (starts_with(k, "status.color.") || starts_with(k, "color.status.")) { - int slot = parse_status_slot(k, 13); + if (skip_prefix(k, "status.color.", &slot_name) || + skip_prefix(k, "color.status.", &slot_name)) { + int slot = parse_status_slot(slot_name); if (slot < 0) return 0; if (!v) return config_error_nonbool(k); - color_parse(v, k, s->color_palette[slot]); - return 0; + return color_parse(v, s->color_palette[slot]); } if (!strcmp(k, "status.relativepaths")) { s->relative_paths = git_config_bool(k, v); @@ -1343,6 +1396,22 @@ int cmd_status(int argc, const char **argv, const char *prefix) return 0; } +static const char *implicit_ident_advice(void) +{ + char *user_config = expand_user_path("~/.gitconfig"); + char *xdg_config = xdg_config_home("config"); + int config_exists = file_exists(user_config) || file_exists(xdg_config); + + free(user_config); + free(xdg_config); + + if (config_exists) + return _(implicit_ident_advice_config); + else + return _(implicit_ident_advice_noconfig); + +} + static void print_summary(const char *prefix, const unsigned char *sha1, int initial_commit) { @@ -1369,12 +1438,19 @@ 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); if (advice_implicit_identity) { strbuf_addch(&format, '\n'); - strbuf_addstr(&format, _(implicit_ident_advice)); + strbuf_addstr(&format, implicit_ident_advice()); } } strbuf_release(&author_ident); @@ -1395,14 +1471,12 @@ static void print_summary(const char *prefix, const unsigned char *sha1, rev.diffopt.break_opt = 0; diff_setup_done(&rev.diffopt); - head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL); - printf("[%s%s ", - starts_with(head, "refs/heads/") ? - head + 11 : - !strcmp(head, "HEAD") ? - _("detached HEAD") : - head, - initial_commit ? _(" (root-commit)") : ""); + head = resolve_ref_unsafe("HEAD", 0, junk_sha1, NULL); + if (!strcmp(head, "HEAD")) + head = _("detached HEAD"); + else + skip_prefix(head, "refs/heads/", &head); + printf("[%s%s ", head, initial_commit ? _(" (root-commit)") : ""); if (!log_tree_commit(&rev, commit)) { rev.always_show_header = 1; @@ -1442,7 +1516,7 @@ static int run_rewrite_hook(const unsigned char *oldsha1, { /* oldsha1 SP newsha1 LF NUL */ static char buf[2*40 + 3]; - struct child_process proc; + struct child_process proc = CHILD_PROCESS_INIT; const char *argv[3]; int code; size_t n; @@ -1454,7 +1528,6 @@ static int run_rewrite_hook(const unsigned char *oldsha1, argv[1] = "amend"; argv[2] = NULL; - memset(&proc, 0, sizeof(proc)); proc.argv = argv; proc.in = -1; proc.stdout_to_stderr = 1; @@ -1554,11 +1627,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) const char *index_file, *reflog_msg; char *nl; unsigned char sha1[20]; - struct ref_lock *ref_lock; struct commit_list *parents = NULL, **pptr = &parents; struct stat statbuf; struct commit *current_head = NULL; struct commit_extra_header *extra = NULL; + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); @@ -1680,12 +1754,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix) strbuf_release(&author_ident); free_commit_extra_headers(extra); - ref_lock = lock_any_ref_for_update("HEAD", - !current_head - ? NULL - : current_head->object.sha1, - 0, NULL); - nl = strchr(sb.buf, '\n'); if (nl) strbuf_setlen(&sb, nl + 1 - sb.buf); @@ -1694,14 +1762,17 @@ 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) { + transaction = ref_transaction_begin(&err); + if (!transaction || + ref_transaction_update(transaction, "HEAD", sha1, + current_head + ? current_head->object.sha1 : null_sha1, + 0, sb.buf, &err) || + ref_transaction_commit(transaction, &err)) { rollback_index_files(); - die(_("cannot update HEAD ref")); + die("%s", err.buf); } + ref_transaction_free(transaction); unlink(git_path("CHERRY_PICK_HEAD")); unlink(git_path("REVERT_HEAD")); @@ -1712,7 +1783,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (commit_index_files()) die (_("Repository has been updated, but unable to write\n" - "new_index file. Check that disk is not full or quota is\n" + "new_index file. Check that disk is not full and quota is\n" "not exceeded, and then \"git reset HEAD\" to recover.")); rerere(0); @@ -1730,5 +1801,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!quiet) print_summary(prefix, sha1, !current_head); + strbuf_release(&err); return 0; } diff --git a/builtin/config.c b/builtin/config.c index 5677c942b6..7188405f7e 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -5,7 +5,7 @@ #include "urlmatch.h" static const char *const builtin_config_usage[] = { - N_("git config [options]"), + N_("git config [<options>]"), NULL }; @@ -69,8 +69,8 @@ static struct option builtin_config_options[] = { OPT_BIT(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION), OPT_BIT('l', "list", &actions, N_("list all"), ACTION_LIST), OPT_BIT('e', "edit", &actions, N_("open an editor"), ACTION_EDIT), - OPT_STRING(0, "get-color", &get_color_slot, N_("slot"), N_("find the color configured: [default]")), - OPT_STRING(0, "get-colorbool", &get_colorbool_slot, N_("slot"), N_("find the color setting: [stdout-is-tty]")), + OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR), + OPT_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL), OPT_GROUP(N_("Type")), OPT_BIT(0, "bool", &types, N_("value is \"true\" or \"false\""), TYPE_BOOL), OPT_BIT(0, "int", &types, N_("value is decimal number"), TYPE_INT), @@ -193,7 +193,7 @@ static int get_value(const char *key_, const char *regex_) key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(key_regexp, key, REG_EXTENDED)) { - fprintf(stderr, "Invalid key pattern: %s\n", key_); + error("invalid key pattern: %s", key_); free(key_regexp); key_regexp = NULL; ret = CONFIG_INVALID_PATTERN; @@ -214,7 +214,7 @@ static int get_value(const char *key_, const char *regex_) regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(regexp, regex_, REG_EXTENDED)) { - fprintf(stderr, "Invalid pattern: %s\n", regex_); + error("invalid pattern: %s", regex_); free(regexp); regexp = NULL; ret = CONFIG_INVALID_PATTERN; @@ -296,21 +296,25 @@ static int git_get_color_config(const char *var, const char *value, void *cb) if (!strcmp(var, get_color_slot)) { if (!value) config_error_nonbool(var); - color_parse(value, var, parsed_color); + if (color_parse(value, parsed_color) < 0) + return -1; get_color_found = 1; } return 0; } -static void get_color(const char *def_color) +static void get_color(const char *var, const char *def_color) { + get_color_slot = var; get_color_found = 0; parsed_color[0] = '\0'; git_config_with_options(git_get_color_config, NULL, &given_config_source, respect_includes); - if (!get_color_found && def_color) - color_parse(def_color, "command line", parsed_color); + if (!get_color_found && def_color) { + if (color_parse(def_color, parsed_color) < 0) + die(_("unable to parse default color value")); + } fputs(parsed_color, stdout); } @@ -330,8 +334,9 @@ static int git_get_colorbool_config(const char *var, const char *value, return 0; } -static int get_colorbool(int print) +static int get_colorbool(const char *var, int print) { + get_colorbool_slot = var; get_colorbool_found = -1; get_diff_color_found = -1; get_color_ui_found = -1; @@ -395,19 +400,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 +414,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'; @@ -458,6 +450,20 @@ static int get_urlmatch(const char *var, const char *url) return 0; } +static char *default_user_config(void) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, + _("# This is Git's per-user configuration file.\n" + "[user]\n" + "# Please adapt and uncomment the following lines:\n" + "# name = %s\n" + "# email = %s\n"), + ident_default_name(), + ident_default_email()); + return strbuf_detach(&buf, NULL); +} + int cmd_config(int argc, const char **argv, const char *prefix) { int nongit = !startup_info->have_repository; @@ -482,10 +488,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) } if (use_global_config) { - char *user_config = NULL; - char *xdg_config = NULL; - - home_config_paths(&user_config, &xdg_config, "config"); + char *user_config = expand_user_path("~/.gitconfig"); + char *xdg_config = xdg_config_home("config"); if (!user_config) /* @@ -528,12 +532,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) usage_with_options(builtin_config_usage, builtin_config_options); } - if (get_color_slot) - actions |= ACTION_GET_COLOR; - if (get_colorbool_slot) - actions |= ACTION_GET_COLORBOOL; - - if ((get_color_slot || get_colorbool_slot) && types) { + if ((actions & (ACTION_GET_COLOR|ACTION_GET_COLORBOOL)) && types) { error("--get-color and variable type are incoherent"); usage_with_options(builtin_config_usage, builtin_config_options); } @@ -564,6 +563,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) } } else if (actions == ACTION_EDIT) { + char *config_file; + check_argc(argc, 0, 0); if (!given_config_source.file && nongit) die("not in a git directory"); @@ -572,9 +573,21 @@ int cmd_config(int argc, const char **argv, const char *prefix) if (given_config_source.blob) die("editing blobs is not supported"); git_config(git_default_config, NULL); - launch_editor(given_config_source.file ? - given_config_source.file : git_path("config"), - NULL, NULL); + config_file = xstrdup(given_config_source.file ? + given_config_source.file : git_path("config")); + if (use_global_config) { + int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd) { + char *content = default_user_config(); + write_str_in_full(fd, content); + free(content); + close(fd); + } + else if (errno != EEXIST) + die_errno(_("cannot create configuration file %s"), config_file); + } + launch_editor(config_file, NULL, NULL); + free(config_file); } else if (actions == ACTION_SET) { int ret; @@ -599,7 +612,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(); @@ -667,12 +681,14 @@ int cmd_config(int argc, const char **argv, const char *prefix) die("No such section!"); } else if (actions == ACTION_GET_COLOR) { - get_color(argv[0]); + check_argc(argc, 1, 2); + get_color(argv[0], argv[1]); } else if (actions == ACTION_GET_COLORBOOL) { - if (argc == 1) - color_stdout_is_tty = git_config_bool("command line", argv[0]); - return get_colorbool(argc != 0); + check_argc(argc, 1, 2); + if (argc == 2) + color_stdout_is_tty = git_config_bool("command line", argv[1]); + return get_colorbool(argv[0], argc == 2); } return 0; diff --git a/builtin/count-objects.c b/builtin/count-objects.c index a7f70cb858..ad0c79954a 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -11,6 +11,9 @@ static unsigned long garbage; static off_t size_garbage; +static int verbose; +static unsigned long loose, packed, packed_loose; +static off_t loose_size; static void real_report_garbage(const char *desc, const char *path) { @@ -21,61 +24,31 @@ static void real_report_garbage(const char *desc, const char *path) garbage++; } -static void count_objects(DIR *d, char *path, int len, int verbose, - unsigned long *loose, - off_t *loose_size, - unsigned long *packed_loose) +static void loose_garbage(const char *path) { - struct dirent *ent; - while ((ent = readdir(d)) != NULL) { - char hex[41]; - unsigned char sha1[20]; - const char *cp; - int bad = 0; + if (verbose) + report_garbage("garbage found", path); +} - if (is_dot_or_dotdot(ent->d_name)) - continue; - for (cp = ent->d_name; *cp; cp++) { - int ch = *cp; - if (('0' <= ch && ch <= '9') || - ('a' <= ch && ch <= 'f')) - continue; - bad = 1; - break; - } - if (cp - ent->d_name != 38) - bad = 1; - else { - struct stat st; - memcpy(path + len + 3, ent->d_name, 38); - path[len + 2] = '/'; - path[len + 41] = 0; - if (lstat(path, &st) || !S_ISREG(st.st_mode)) - bad = 1; - else - (*loose_size) += xsize_t(on_disk_bytes(st)); - } - if (bad) { - if (verbose) { - struct strbuf sb = STRBUF_INIT; - strbuf_addf(&sb, "%.*s/%s", - len + 2, path, ent->d_name); - report_garbage("garbage found", sb.buf); - strbuf_release(&sb); - } - continue; - } - (*loose)++; - if (!verbose) - continue; - memcpy(hex, path+len, 2); - memcpy(hex+2, ent->d_name, 38); - hex[40] = 0; - if (get_sha1_hex(hex, sha1)) - die("internal error"); - if (has_sha1_pack(sha1)) - (*packed_loose)++; +static int count_loose(const unsigned char *sha1, const char *path, void *data) +{ + struct stat st; + + if (lstat(path, &st) || !S_ISREG(st.st_mode)) + loose_garbage(path); + else { + loose_size += on_disk_bytes(st); + loose++; + if (verbose && has_sha1_pack(sha1)) + packed_loose++; } + return 0; +} + +static int count_cruft(const char *basename, const char *path, void *data) +{ + loose_garbage(path); + return 0; } static char const * const count_objects_usage[] = { @@ -85,12 +58,7 @@ static char const * const count_objects_usage[] = { int cmd_count_objects(int argc, const char **argv, const char *prefix) { - int i, verbose = 0, human_readable = 0; - const char *objdir = get_object_directory(); - int len = strlen(objdir); - char *path = xmalloc(len + 50); - unsigned long loose = 0, packed = 0, packed_loose = 0; - off_t loose_size = 0; + int human_readable = 0; struct option opts[] = { OPT__VERBOSE(&verbose, N_("be verbose")), OPT_BOOL('H', "human-readable", &human_readable, @@ -102,21 +70,14 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) /* we do not take arguments other than flags for now */ if (argc) usage_with_options(count_objects_usage, opts); - if (verbose) + if (verbose) { report_garbage = real_report_garbage; - memcpy(path, objdir, len); - if (len && objdir[len-1] != '/') - path[len++] = '/'; - for (i = 0; i < 256; i++) { - DIR *d; - sprintf(path + len, "%02x", i); - d = opendir(path); - if (!d) - continue; - count_objects(d, path, len, verbose, - &loose, &loose_size, &packed_loose); - closedir(d); + report_linked_checkout_garbage(); } + + for_each_loose_file_in_objdir(get_object_directory(), + count_loose, count_cruft, NULL, NULL); + if (verbose) { struct packed_git *p; unsigned long num_pack = 0; diff --git a/builtin/describe.c b/builtin/describe.c index 24d740c8b1..e00a75b121 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "lockfile.h" #include "commit.h" #include "tag.h" #include "refs.h" @@ -13,8 +14,8 @@ #define MAX_TAGS (FLAG_BITS - 1) static const char * const describe_usage[] = { - N_("git describe [options] <commit-ish>*"), - N_("git describe [options] --dirty"), + N_("git describe [<options>] [<commit-ish>...]"), + N_("git describe [<options>] --dirty"), NULL }; @@ -56,18 +57,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 +106,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-files.c b/builtin/diff-files.c index 9200069363..8ed2eb8813 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -11,7 +11,7 @@ #include "submodule.h" static const char diff_files_usage[] = -"git diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]" +"git diff-files [-q] [-0 | -1 | -2 | -3 | -c | --cc] [<common-diff-options>] [<path>...]" COMMON_DIFF_OPTIONS_HELP; int cmd_diff_files(int argc, const char **argv, const char *prefix) diff --git a/builtin/diff-index.c b/builtin/diff-index.c index ce15b23042..d979824f93 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -7,7 +7,7 @@ static const char diff_cache_usage[] = "git diff-index [-m] [--cached] " -"[<common diff options>] <tree-ish> [<path>...]" +"[<common-diff-options>] <tree-ish> [<path>...]" COMMON_DIFF_OPTIONS_HELP; int cmd_diff_index(int argc, const char **argv, const char *prefix) diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index dddd0f9188..12b683d021 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)) { @@ -86,7 +82,7 @@ static int diff_tree_stdin(char *line) static const char diff_tree_usage[] = "git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] " -"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n" +"[<common-diff-options>] <tree-ish> [<tree-ish>] [<path>...]\n" " -r diff recursively\n" " --root include the initial commit as diff against /dev/null\n" COMMON_DIFF_OPTIONS_HELP; diff --git a/builtin/diff.c b/builtin/diff.c index 0f247d2400..4326fa56bf 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -4,6 +4,7 @@ * Copyright (c) 2006 Junio C Hamano */ #include "cache.h" +#include "lockfile.h" #include "color.h" #include "commit.h" #include "blob.h" diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 05d161f19f..b8182c241d 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -17,6 +17,8 @@ #include "utf8.h" #include "parse-options.h" #include "quote.h" +#include "remote.h" +#include "blob.h" static const char *fast_export_usage[] = { N_("git fast-export [rev-list-opts]"), @@ -31,6 +33,9 @@ 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 anonymize; static int parse_opt_signed_tag_mode(const struct option *opt, const char *arg, int unset) @@ -78,6 +83,76 @@ static int has_unshown_parent(struct commit *commit) return 0; } +struct anonymized_entry { + struct hashmap_entry hash; + const char *orig; + size_t orig_len; + const char *anon; + size_t anon_len; +}; + +static int anonymized_entry_cmp(const void *va, const void *vb, + const void *data) +{ + const struct anonymized_entry *a = va, *b = vb; + return a->orig_len != b->orig_len || + memcmp(a->orig, b->orig, a->orig_len); +} + +/* + * Basically keep a cache of X->Y so that we can repeatedly replace + * the same anonymized string with another. The actual generation + * is farmed out to the generate function. + */ +static const void *anonymize_mem(struct hashmap *map, + void *(*generate)(const void *, size_t *), + const void *orig, size_t *len) +{ + struct anonymized_entry key, *ret; + + if (!map->cmpfn) + hashmap_init(map, anonymized_entry_cmp, 0); + + hashmap_entry_init(&key, memhash(orig, *len)); + key.orig = orig; + key.orig_len = *len; + ret = hashmap_get(map, &key, NULL); + + if (!ret) { + ret = xmalloc(sizeof(*ret)); + hashmap_entry_init(&ret->hash, key.hash.hash); + ret->orig = xstrdup(orig); + ret->orig_len = *len; + ret->anon = generate(orig, len); + ret->anon_len = *len; + hashmap_put(map, ret); + } + + *len = ret->anon_len; + return ret->anon; +} + +/* + * We anonymize each component of a path individually, + * so that paths a/b and a/c will share a common root. + * The paths are cached via anonymize_mem so that repeated + * lookups for "a" will yield the same value. + */ +static void anonymize_path(struct strbuf *out, const char *path, + struct hashmap *map, + void *(*generate)(const void *, size_t *)) +{ + while (*path) { + const char *end_of_component = strchrnul(path, '/'); + size_t len = end_of_component - path; + const char *c = anonymize_mem(map, generate, path, &len); + strbuf_add(out, c, len); + path = end_of_component; + if (*path) + strbuf_addch(out, *path++); + } +} + /* Since intptr_t is C99, we do not use it here */ static inline uint32_t *mark_to_ptr(uint32_t mark) { @@ -116,6 +191,26 @@ static void show_progress(void) printf("progress %d objects\n", counter); } +/* + * Ideally we would want some transformation of the blob data here + * that is unreversible, but would still be the same size and have + * the same data relationship to other blobs (so that we get the same + * delta and packing behavior as the original). But the first and last + * requirements there are probably mutually exclusive, so let's take + * the easy way out for now, and just generate arbitrary content. + * + * There's no need to cache this result with anonymize_mem, since + * we already handle blob content caching with marks. + */ +static char *anonymize_blob(unsigned long *size) +{ + static int counter; + struct strbuf out = STRBUF_INIT; + strbuf_addf(&out, "anonymous blob %d", counter++); + *size = out.len; + return strbuf_detach(&out, NULL); +} + static void export_blob(const unsigned char *sha1) { unsigned long size; @@ -134,12 +229,19 @@ static void export_blob(const unsigned char *sha1) if (object && object->flags & SHOWN) return; - buf = read_sha1_file(sha1, &type, &size); - if (!buf) - die ("Could not read blob %s", sha1_to_hex(sha1)); - if (check_sha1_signature(sha1, buf, size, typename(type)) < 0) - die("sha1 mismatch in blob %s", sha1_to_hex(sha1)); - object = parse_object_buffer(sha1, type, size, buf, &eaten); + if (anonymize) { + buf = anonymize_blob(&size); + object = (struct object *)lookup_blob(sha1); + eaten = 0; + } else { + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + die ("Could not read blob %s", sha1_to_hex(sha1)); + if (check_sha1_signature(sha1, buf, size, typename(type)) < 0) + die("sha1 mismatch in blob %s", sha1_to_hex(sha1)); + object = parse_object_buffer(sha1, type, size, buf, &eaten); + } + if (!object) die("Could not read blob %s", sha1_to_hex(sha1)); @@ -187,7 +289,7 @@ static int depth_first(const void *a_, const void *b_) return (a->status == 'R') - (b->status == 'R'); } -static void print_path(const char *path) +static void print_path_1(const char *path) { int need_quote = quote_c_style(path, NULL, NULL, 0); if (need_quote) @@ -198,6 +300,43 @@ static void print_path(const char *path) printf("%s", path); } +static void *anonymize_path_component(const void *path, size_t *len) +{ + static int counter; + struct strbuf out = STRBUF_INIT; + strbuf_addf(&out, "path%d", counter++); + return strbuf_detach(&out, len); +} + +static void print_path(const char *path) +{ + if (!anonymize) + print_path_1(path); + else { + static struct hashmap paths; + static struct strbuf anon = STRBUF_INIT; + + anonymize_path(&anon, path, &paths, anonymize_path_component); + print_path_1(anon.buf); + strbuf_reset(&anon); + } +} + +static void *generate_fake_sha1(const void *old, size_t *len) +{ + static uint32_t counter = 1; /* avoid null sha1 */ + unsigned char *out = xcalloc(20, 1); + put_be32(out + 16, counter++); + return out; +} + +static const unsigned char *anonymize_sha1(const unsigned char *sha1) +{ + static struct hashmap sha1s; + size_t len = 20; + return anonymize_mem(&sha1s, generate_fake_sha1, sha1, &len); +} + static void show_filemodify(struct diff_queue_struct *q, struct diff_options *options, void *data) { @@ -242,7 +381,9 @@ static void show_filemodify(struct diff_queue_struct *q, */ if (no_data || S_ISGITLINK(spec->mode)) printf("M %06o %s ", spec->mode, - sha1_to_hex(spec->sha1)); + sha1_to_hex(anonymize ? + anonymize_sha1(spec->sha1) : + spec->sha1)); else { struct object *object = lookup_object(spec->sha1); printf("M %06o :%d ", spec->mode, @@ -276,6 +417,114 @@ static const char *find_encoding(const char *begin, const char *end) return bol; } +static void *anonymize_ref_component(const void *old, size_t *len) +{ + static int counter; + struct strbuf out = STRBUF_INIT; + strbuf_addf(&out, "ref%d", counter++); + return strbuf_detach(&out, len); +} + +static const char *anonymize_refname(const char *refname) +{ + /* + * If any of these prefixes is found, we will leave it intact + * so that tags remain tags and so forth. + */ + static const char *prefixes[] = { + "refs/heads/", + "refs/tags/", + "refs/remotes/", + "refs/" + }; + static struct hashmap refs; + static struct strbuf anon = STRBUF_INIT; + int i; + + /* + * We also leave "master" as a special case, since it does not reveal + * anything interesting. + */ + if (!strcmp(refname, "refs/heads/master")) + return refname; + + strbuf_reset(&anon); + for (i = 0; i < ARRAY_SIZE(prefixes); i++) { + if (skip_prefix(refname, prefixes[i], &refname)) { + strbuf_addstr(&anon, prefixes[i]); + break; + } + } + + anonymize_path(&anon, refname, &refs, anonymize_ref_component); + return anon.buf; +} + +/* + * We do not even bother to cache commit messages, as they are unlikely + * to be repeated verbatim, and it is not that interesting when they are. + */ +static char *anonymize_commit_message(const char *old) +{ + static int counter; + return xstrfmt("subject %d\n\nbody\n", counter++); +} + +static struct hashmap idents; +static void *anonymize_ident(const void *old, size_t *len) +{ + static int counter; + struct strbuf out = STRBUF_INIT; + strbuf_addf(&out, "User %d <user%d@example.com>", counter, counter); + counter++; + return strbuf_detach(&out, len); +} + +/* + * Our strategy here is to anonymize the names and email addresses, + * but keep timestamps intact, as they influence things like traversal + * order (and by themselves should not be too revealing). + */ +static void anonymize_ident_line(const char **beg, const char **end) +{ + static struct strbuf buffers[] = { STRBUF_INIT, STRBUF_INIT }; + static unsigned which_buffer; + + struct strbuf *out; + struct ident_split split; + const char *end_of_header; + + out = &buffers[which_buffer++]; + which_buffer %= ARRAY_SIZE(buffers); + strbuf_reset(out); + + /* skip "committer", "author", "tagger", etc */ + end_of_header = strchr(*beg, ' '); + if (!end_of_header) + die("BUG: malformed line fed to anonymize_ident_line: %.*s", + (int)(*end - *beg), *beg); + end_of_header++; + strbuf_add(out, *beg, end_of_header - *beg); + + if (!split_ident_line(&split, end_of_header, *end - end_of_header) && + split.date_begin) { + const char *ident; + size_t len; + + len = split.mail_end - split.name_begin; + ident = anonymize_mem(&idents, anonymize_ident, + split.name_begin, &len); + strbuf_add(out, ident, len); + strbuf_addch(out, ' '); + strbuf_add(out, split.date_begin, split.tz_end - split.date_begin); + } else { + strbuf_addstr(out, "Malformed Ident <malformed@example.com> 0 -0000"); + } + + *beg = out->buf; + *end = out->buf + out->len; +} + static void handle_commit(struct commit *commit, struct rev_info *rev) { int saved_output_format = rev->diffopt.output_format; @@ -284,6 +533,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) const char *encoding, *message; char *reencoded = NULL; struct commit_list *p; + const char *refname; int i; rev->diffopt.output_format = DIFF_FORMAT_CALLBACK; @@ -323,13 +573,22 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode)) export_blob(diff_queued_diff.queue[i]->two->sha1); + refname = commit->util; + if (anonymize) { + refname = anonymize_refname(refname); + anonymize_ident_line(&committer, &committer_end); + anonymize_ident_line(&author, &author_end); + } + mark_next_object(&commit->object); - if (!is_encoding_utf8(encoding)) + if (anonymize) + reencoded = anonymize_commit_message(message); + else if (!is_encoding_utf8(encoding)) reencoded = reencode_string(message, "UTF-8", encoding); if (!commit->parents) - printf("reset %s\n", (const char*)commit->util); + printf("reset %s\n", refname); printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s", - (const char *)commit->util, last_idnum, + refname, last_idnum, (int)(author_end - author), author, (int)(committer_end - committer), committer, (unsigned)(reencoded @@ -360,6 +619,14 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) show_progress(); } +static void *anonymize_tag(const void *old, size_t *len) +{ + static int counter; + struct strbuf out = STRBUF_INIT; + strbuf_addf(&out, "tag message %d", counter++); + return strbuf_detach(&out, len); +} + static void handle_tail(struct object_array *commits, struct rev_info *revs) { struct commit *commit; @@ -416,6 +683,17 @@ static void handle_tag(const char *name, struct tag *tag) } else { tagger++; tagger_end = strchrnul(tagger, '\n'); + if (anonymize) + anonymize_ident_line(&tagger, &tagger_end); + } + + if (anonymize) { + name = anonymize_refname(name); + if (message) { + static struct hashmap tags; + message = anonymize_mem(&tags, anonymize_tag, + message, &message_size); + } } /* handle signed tags */ @@ -528,6 +806,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.", @@ -572,6 +859,8 @@ static void handle_tags_and_duplicates(void) handle_tag(name, (struct tag *)object); break; case OBJ_COMMIT: + if (anonymize) + name = anonymize_refname(name); /* create refs pointing to already seen commits */ commit = (struct commit *)object; printf("reset %s\nfrom :%d\n\n", name, @@ -664,6 +953,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 +973,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 +994,9 @@ 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_BOOL(0, "anonymize", &anonymize, N_("anonymize output")), OPT_END() }; @@ -704,11 +1010,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 +1058,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 +1066,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-pack.c b/builtin/fetch-pack.c index 1262b405f8..4a6b340ab6 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -6,7 +6,7 @@ #include "sha1-array.h" static const char fetch_pack_usage[] = -"git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] " +"git fetch-pack [--all] [--stdin] [--quiet | -q] [--keep | -k] [--thin] " "[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] " "[--no-progress] [--diag-url] [-v] [<host>:]<directory> [<refs>...]"; diff --git a/builtin/fetch.c b/builtin/fetch.c index 55f457c04f..7910419c93 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -11,7 +11,6 @@ #include "run-command.h" #include "parse-options.h" #include "sigchain.h" -#include "transport.h" #include "submodule.h" #include "connected.h" #include "argv-array.h" @@ -45,6 +44,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) @@ -66,6 +67,19 @@ static int git_fetch_config(const char *k, const char *v, void *cb) fetch_prune_config = git_config_bool(k, v); return 0; } + return git_default_config(k, v, cb); +} + +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; } @@ -107,6 +121,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 +294,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 +326,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; @@ -375,23 +403,39 @@ static int s_update_ref(const char *action, { char msg[1024]; char *rla = getenv("GIT_REFLOG_ACTION"); - static struct ref_lock *lock; + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; + int ret, df_conflict = 0; if (dry_run) return 0; if (!rla) rla = default_rla.buf; snprintf(msg, sizeof(msg), "%s: %s", rla, action); - lock = lock_any_ref_for_update(ref->name, - check_old ? ref->old_sha1 : NULL, - 0, NULL); - if (!lock) - return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : - STORE_REF_ERROR_OTHER; - if (write_ref_sha1(lock, ref->new_sha1, msg) < 0) - return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : - STORE_REF_ERROR_OTHER; + + transaction = ref_transaction_begin(&err); + if (!transaction || + ref_transaction_update(transaction, ref->name, + ref->new_sha1, + check_old ? ref->old_sha1 : NULL, + 0, msg, &err)) + goto fail; + + ret = ref_transaction_commit(transaction, &err); + if (ret) { + df_conflict = (ret == TRANSACTION_NAME_CONFLICT); + goto fail; + } + + ref_transaction_free(transaction); + strbuf_release(&err); return 0; +fail: + ref_transaction_free(transaction); + error("%s", err.buf); + strbuf_release(&err); + return df_conflict ? STORE_REF_ERROR_DF_CONFLICT + : STORE_REF_ERROR_OTHER; } #define REFCOL_WIDTH 10 @@ -544,7 +588,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, struct strbuf note = STRBUF_INIT; const char *what, *kind; struct ref *rm; - char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); + char *url; + const char *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); int want_status; fp = fopen(filename, "a"); @@ -778,7 +823,7 @@ static void check_not_current_branch(struct ref *ref_map) static int truncate_fetch_head(void) { - char *filename = git_path("FETCH_HEAD"); + const char *filename = git_path("FETCH_HEAD"); FILE *fp = fopen(filename, "w"); if (!fp) @@ -1053,16 +1098,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 +1126,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 +1212,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..05f4c26311 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -10,7 +10,7 @@ #include "gpg-interface.h" static const char * const fmt_merge_msg_usage[] = { - N_("git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]"), + N_("git fmt-merge-msg [-m <message>] [--log[=<n>] | --no-log] [--file <file>]"), NULL }; @@ -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; @@ -219,24 +216,21 @@ static void add_branch_desc(struct strbuf *out, const char *name) strbuf_addf(out, " : %.*s", (int)(ep - bp), bp); bp = ep; } - if (out->buf[out->len - 1] != '\n') - strbuf_addch(out, '\n'); + strbuf_complete_line(out); } strbuf_release(&desc); } #define util_as_integral(elem) ((intptr_t)((elem)->util)) -static void record_person(int which, struct string_list *people, - struct commit *commit) +static void record_person_from_buf(int which, struct string_list *people, + const char *buffer) { - const char *buffer; char *name_buf, *name, *name_end; struct string_list_item *elem; const char *field; field = (which == 'a') ? "\nauthor " : "\ncommitter "; - buffer = get_commit_buffer(commit, NULL); name = strstr(buffer, field); if (!name) return; @@ -249,7 +243,6 @@ static void record_person(int which, struct string_list *people, if (name_end < name) return; name_buf = xmemdupz(name, name_end - name + 1); - unuse_commit_buffer(commit, buffer); elem = string_list_lookup(people, name_buf); if (!elem) { @@ -260,6 +253,15 @@ static void record_person(int which, struct string_list *people, free(name_buf); } + +static void record_person(int which, struct string_list *people, + struct commit *commit) +{ + const char *buffer = get_commit_buffer(commit, NULL); + record_person_from_buf(which, people, buffer); + unuse_commit_buffer(commit, buffer); +} + static int cmp_string_list_util_as_integral(const void *a_, const void *b_) { const struct string_list_item *a = a_, *b = b_; @@ -300,8 +302,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); @@ -605,7 +607,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out, /* get current branch */ current_branch = current_branch_to_free = - resolve_refdup("HEAD", head_sha1, 1, NULL); + resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL); if (!current_branch) die("No current branch"); if (starts_with(current_branch, "refs/heads/")) diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 3e1d5c3334..83f9cf9163 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -138,10 +138,8 @@ static int parse_atom(const char *atom, const char *ep) /* Add it in, including the deref prefix */ at = used_atom_cnt; used_atom_cnt++; - used_atom = xrealloc(used_atom, - (sizeof *used_atom) * used_atom_cnt); - used_atom_type = xrealloc(used_atom_type, - (sizeof(*used_atom_type) * used_atom_cnt)); + REALLOC_ARRAY(used_atom, used_atom_cnt); + REALLOC_ARRAY(used_atom_type, used_atom_cnt); used_atom[at] = xmemdupz(atom, ep - atom); used_atom_type[at] = valid_atom[i].cmp_type; if (*atom == '*') @@ -180,11 +178,10 @@ static const char *find_next(const char *cp) static int verify_format(const char *format) { const char *cp, *sp; - static const char color_reset[] = "color:reset"; need_color_reset_at_eol = 0; for (cp = format; *cp && (sp = find_next(cp)); ) { - const char *ep = strchr(sp, ')'); + const char *color, *ep = strchr(sp, ')'); int at; if (!ep) @@ -193,8 +190,8 @@ static int verify_format(const char *format) at = parse_atom(sp + 2, ep); cp = ep + 1; - if (!memcmp(used_atom[at], "color:", 6)) - need_color_reset_at_eol = !!strcmp(used_atom[at], color_reset); + if (skip_prefix(used_atom[at], "color:", &color)) + need_color_reset_at_eol = !!strcmp(color, "reset"); } return 0; } @@ -283,18 +280,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 +300,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); @@ -645,11 +630,12 @@ static void populate_value(struct refinfo *ref) unsigned long size; const unsigned char *tagged; - ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt); + ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value)); if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) { unsigned char unused1[20]; - ref->symref = resolve_refdup(ref->refname, unused1, 1, NULL); + ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING, + unused1, NULL); if (!ref->symref) ref->symref = ""; } @@ -685,7 +671,8 @@ static void populate_value(struct refinfo *ref) } else if (starts_with(name, "color:")) { char color[COLOR_MAXLEN] = ""; - color_parse(name + 6, "--format", color); + if (color_parse(name + 6, color) < 0) + die(_("unable to parse format")); v->s = xstrdup(color); continue; } else if (!strcmp(name, "flag")) { @@ -707,7 +694,8 @@ static void populate_value(struct refinfo *ref) const char *head; unsigned char sha1[20]; - head = resolve_ref_unsafe("HEAD", sha1, 1, NULL); + head = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + sha1, NULL); if (!strcmp(ref->refname, head)) v->s = "*"; else @@ -728,7 +716,10 @@ static void populate_value(struct refinfo *ref) starts_with(name, "upstream")) { char buf[40]; - stat_tracking_info(branch, &num_ours, &num_theirs); + if (stat_tracking_info(branch, &num_ours, + &num_theirs) != 1) + continue; + if (!num_ours && !num_theirs) v->s = ""; else if (!num_ours) { @@ -746,7 +737,11 @@ static void populate_value(struct refinfo *ref) } else if (!strcmp(formatp, "trackshort") && starts_with(name, "upstream")) { assert(branch); - stat_tracking_info(branch, &num_ours, &num_theirs); + + if (stat_tracking_info(branch, &num_ours, + &num_theirs) != 1) + continue; + if (!num_ours && !num_theirs) v->s = "="; else if (!num_ours) @@ -851,6 +846,11 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f struct refinfo *ref; int cnt; + if (flag & REF_BAD_NAME) { + warning("ignoring ref with broken name %s", refname); + return 0; + } + if (*cb->grab_pattern) { const char **pattern; int namelen = strlen(refname); @@ -882,8 +882,7 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f ref->flag = flag; cnt = cb->grab_cnt; - cb->grab_array = xrealloc(cb->grab_array, - sizeof(*cb->grab_array) * (cnt + 1)); + REALLOC_ARRAY(cb->grab_array, cnt + 1); cb->grab_array[cnt++] = ref; cb->grab_cnt = cnt; return 0; @@ -1019,7 +1018,8 @@ static void show_ref(struct refinfo *info, const char *format, int quote_style) struct atom_value resetv; char color[COLOR_MAXLEN] = ""; - color_parse("reset", "--format", color); + if (color_parse("reset", color) < 0) + die("BUG: couldn't parse 'reset' as a color"); resetv.s = color; print_value(&resetv, quote_style); } @@ -1060,7 +1060,7 @@ static int opt_parse_sort(const struct option *opt, const char *arg, int unset) } static char const * const for_each_ref_usage[] = { - N_("git for-each-ref [options] [<pattern>]"), + N_("git for-each-ref [<options>] [<pattern>]"), NULL }; @@ -1081,7 +1081,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) OPT_BIT(0 , "python", "e_style, N_("quote placeholders suitably for python"), QUOTE_PYTHON), OPT_BIT(0 , "tcl", "e_style, - N_("quote placeholders suitably for tcl"), QUOTE_TCL), + N_("quote placeholders suitably for Tcl"), QUOTE_TCL), OPT_GROUP(""), OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")), diff --git a/builtin/fsck.c b/builtin/fsck.c index 8aadca160e..4783896fd6 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -225,12 +225,12 @@ static void check_unreachable_object(struct object *obj) printf("dangling %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); if (write_lost_and_found) { - char *filename = git_path("lost-found/%s/%s", + const char *filename = git_path("lost-found/%s/%s", obj->type == OBJ_COMMIT ? "commit" : "other", sha1_to_hex(obj->sha1)); FILE *f; - if (safe_create_leading_directories(filename)) { + if (safe_create_leading_directories_const(filename)) { error("Could not create lost-found"); return; } @@ -298,7 +298,7 @@ static int fsck_obj(struct object *obj) if (fsck_walk(obj, mark_used, NULL)) objerror(obj, "broken links"); - if (fsck_object(obj, check_strict, fsck_error_func)) + if (fsck_object(obj, NULL, 0, check_strict, fsck_error_func)) return -1; if (obj->type == OBJ_TREE) { @@ -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); @@ -559,7 +556,7 @@ static int fsck_head_link(void) if (verbose) fprintf(stderr, "Checking HEAD link\n"); - head_points_at = resolve_ref_unsafe("HEAD", head_sha1, 0, &flag); + head_points_at = resolve_ref_unsafe("HEAD", 0, head_sha1, &flag); if (!head_points_at) return error("Invalid HEAD"); if (!strcmp(head_points_at, "HEAD")) @@ -603,7 +600,7 @@ static int fsck_cache_tree(struct cache_tree *it) } static char const * const fsck_usage[] = { - N_("git fsck [options] [<object>...]"), + N_("git fsck [<options>] [<object>...]"), NULL }; diff --git a/builtin/gc.c b/builtin/gc.c index 8d219d8c42..36fe33300f 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -11,7 +11,7 @@ */ #include "builtin.h" -#include "cache.h" +#include "lockfile.h" #include "parse-options.h" #include "run-command.h" #include "sigchain.h" @@ -21,7 +21,7 @@ #define FAILED_RUN "failed to run %s" static const char * const builtin_gc_usage[] = { - N_("git gc [options]"), + N_("git gc [<options>]"), NULL }; @@ -33,11 +33,13 @@ static int gc_auto_threshold = 6700; static int gc_auto_pack_limit = 50; static int detach_auto = 1; static const char *prune_expire = "2.weeks.ago"; +static const char *prune_worktrees_expire = "3.months.ago"; static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT; static struct argv_array reflog = ARGV_ARRAY_INIT; static struct argv_array repack = ARGV_ARRAY_INIT; static struct argv_array prune = ARGV_ARRAY_INIT; +static struct argv_array prune_worktrees = ARGV_ARRAY_INIT; static struct argv_array rerere = ARGV_ARRAY_INIT; static char *pidfile; @@ -55,44 +57,36 @@ static void remove_pidfile_on_signal(int signo) raise(signo); } -static int gc_config(const char *var, const char *value, void *cb) +static void git_config_date_string(const char *key, const char **output) { - if (!strcmp(var, "gc.packrefs")) { + if (git_config_get_string_const(key, output)) + return; + if (strcmp(*output, "now")) { + unsigned long now = approxidate("now"); + if (approxidate(*output) >= now) + git_die_config(key, _("Invalid %s: '%s'"), key, *output); + } +} + +static void gc_config(void) +{ + const char *value; + + if (!git_config_get_value("gc.packrefs", &value)) { if (value && !strcmp(value, "notbare")) pack_refs = -1; else - pack_refs = git_config_bool(var, value); - return 0; + pack_refs = git_config_bool("gc.packrefs", value); } - if (!strcmp(var, "gc.aggressivewindow")) { - aggressive_window = git_config_int(var, value); - return 0; - } - if (!strcmp(var, "gc.aggressivedepth")) { - aggressive_depth = git_config_int(var, value); - return 0; - } - if (!strcmp(var, "gc.auto")) { - gc_auto_threshold = git_config_int(var, value); - return 0; - } - if (!strcmp(var, "gc.autopacklimit")) { - gc_auto_pack_limit = git_config_int(var, value); - return 0; - } - if (!strcmp(var, "gc.autodetach")) { - detach_auto = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "gc.pruneexpire")) { - if (value && strcmp(value, "now")) { - unsigned long now = approxidate("now"); - if (approxidate(value) >= now) - return error(_("Invalid %s: '%s'"), var, value); - } - return git_config_string(&prune_expire, var, value); - } - return git_default_config(var, value, cb); + + git_config_get_int("gc.aggressivewindow", &aggressive_window); + git_config_get_int("gc.aggressivedepth", &aggressive_depth); + git_config_get_int("gc.auto", &gc_auto_threshold); + git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit); + git_config_get_bool("gc.autodetach", &detach_auto); + git_config_date_string("gc.pruneexpire", &prune_expire); + git_config_date_string("gc.pruneworktreesexpire", &prune_worktrees_expire); + git_config(git_default_config, NULL); } static int too_many_loose_objects(void) @@ -298,10 +292,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix) argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL); argv_array_pushl(&repack, "repack", "-d", "-l", NULL); - argv_array_pushl(&prune, "prune", "--expire", NULL ); + argv_array_pushl(&prune, "prune", "--expire", NULL); + argv_array_pushl(&prune_worktrees, "prune", "--worktrees", "--expire", NULL); argv_array_pushl(&rerere, "rerere", "gc", NULL); - git_config(gc_config, NULL); + gc_config(); if (pack_refs < 0) pack_refs = !is_bare_repository(); @@ -368,6 +363,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix) return error(FAILED_RUN, prune.argv[0]); } + if (prune_worktrees_expire) { + argv_array_push(&prune_worktrees, prune_worktrees_expire); + if (run_command_v_opt(prune_worktrees.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, prune_worktrees.argv[0]); + } + if (run_command_v_opt(rerere.argv, RUN_GIT_CMD)) return error(FAILED_RUN, rerere.argv[0]); diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c index aa72596083..6f4147ad02 100644 --- a/builtin/get-tar-commit-id.c +++ b/builtin/get-tar-commit-id.c @@ -19,6 +19,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) char buffer[HEADERSIZE]; struct ustar_header *header = (struct ustar_header *)buffer; char *content = buffer + RECORDSIZE; + const char *comment; ssize_t n; if (argc != 1) @@ -29,10 +30,10 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) die("git get-tar-commit-id: read error"); if (header->typeflag[0] != 'g') return 1; - if (memcmp(content, "52 comment=", 11)) + if (!skip_prefix(content, "52 comment=", &comment)) return 1; - n = write_in_full(1, content + 11, 41); + n = write_in_full(1, comment, 41); if (n < 41) die_errno("git get-tar-commit-id: write error"); diff --git a/builtin/grep.c b/builtin/grep.c index b8d440d0e0..d04f4400d9 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -20,7 +20,7 @@ #include "pathspec.h" static char const * const grep_usage[] = { - N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"), + N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"), NULL }; @@ -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); @@ -458,10 +456,10 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, } static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, - struct object *obj, const char *name, struct object_context *oc) + struct object *obj, const char *name, const char *path) { if (obj->type == OBJ_BLOB) - return grep_sha1(opt, obj->sha1, name, 0, oc ? oc->path : NULL); + return grep_sha1(opt, obj->sha1, name, 0, path); if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { struct tree_desc tree; void *data; @@ -503,7 +501,7 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, for (i = 0; i < nr; i++) { struct object *real_obj; real_obj = deref_tag(list->objects[i].item, NULL, 0); - if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].context)) { + if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path)) { hit = 1; if (opt->status_only) break; @@ -643,7 +641,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "untracked", &untracked, N_("search in both tracked and untracked files")), OPT_SET_INT(0, "exclude-standard", &opt_exclude, - N_("search also in ignored files"), 1), + N_("ignore files specified via '.gitignore'"), 1), OPT_GROUP(""), OPT_BOOL('v', "invert-match", &opt.invert, N_("show non-matching lines")), @@ -740,7 +738,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, NULL, (intptr_t)default_pager }, OPT_BOOL(0, "ext-grep", &external_grep_allowed__ignored, N_("allow calling of grep(1) (ignored by this build)")), - { OPTION_CALLBACK, 0, "help-all", &options, NULL, N_("show usage"), + { OPTION_CALLBACK, 0, "help-all", NULL, NULL, N_("show usage"), PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, OPT_END() }; @@ -823,7 +821,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) struct object *object = parse_object_or_die(sha1, arg); if (!seen_dashdash) verify_non_filename(prefix, arg); - add_object_array_with_context(object, arg, &list, xmemdupz(&oc, sizeof(struct object_context))); + add_object_array_with_path(object, arg, &list, oc.mode, oc.path); continue; } if (!strcmp(arg, "--")) { @@ -887,7 +885,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } } - if (!show_in_pager) + if (!show_in_pager && !opt.status_only) setup_pager(); if (!use_index && (untracked || cached)) diff --git a/builtin/hash-object.c b/builtin/hash-object.c index d7fcf4c13c..07fef3cc6b 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -10,35 +10,53 @@ #include "parse-options.h" #include "exec_cmd.h" -static void hash_fd(int fd, const char *type, int write_object, const char *path) +/* + * This is to create corrupt objects for debugging and as such it + * needs to bypass the data conversion performed by, and the type + * limitation imposed by, index_fd() and its callees. + */ +static int hash_literally(unsigned char *sha1, int fd, const char *type, unsigned flags) +{ + struct strbuf buf = STRBUF_INIT; + int ret; + + if (strbuf_read(&buf, fd, 4096) < 0) + ret = -1; + else + ret = hash_sha1_file_literally(buf.buf, buf.len, type, sha1, flags); + strbuf_release(&buf); + return ret; +} + +static void hash_fd(int fd, const char *type, const char *path, unsigned flags, + int literally) { struct stat st; unsigned char sha1[20]; - unsigned flags = (HASH_FORMAT_CHECK | - (write_object ? HASH_WRITE_OBJECT : 0)); if (fstat(fd, &st) < 0 || - index_fd(sha1, fd, &st, type_from_string(type), path, flags)) - die(write_object + (literally + ? hash_literally(sha1, fd, type, flags) + : index_fd(sha1, fd, &st, type_from_string(type), path, flags))) + die((flags & HASH_WRITE_OBJECT) ? "Unable to add %s to database" : "Unable to hash %s", path); printf("%s\n", sha1_to_hex(sha1)); maybe_flush_or_die(stdout, "hash to stdout"); } -static void hash_object(const char *path, const char *type, int write_object, - const char *vpath) +static void hash_object(const char *path, const char *type, const char *vpath, + unsigned flags, int literally) { int fd; fd = open(path, O_RDONLY); if (fd < 0) die_errno("Cannot open '%s'", path); - hash_fd(fd, type, write_object, vpath); + hash_fd(fd, type, vpath, flags, literally); } -static int no_filters; - -static void hash_stdin_paths(const char *type, int write_objects) +static void hash_stdin_paths(const char *type, int no_filters, unsigned flags, + int literally) { struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT; @@ -49,47 +67,46 @@ static void hash_stdin_paths(const char *type, int write_objects) die("line is badly quoted"); strbuf_swap(&buf, &nbuf); } - hash_object(buf.buf, type, write_objects, - no_filters ? NULL : buf.buf); + hash_object(buf.buf, type, no_filters ? NULL : buf.buf, flags, + literally); } strbuf_release(&buf); strbuf_release(&nbuf); } -static const char * const hash_object_usage[] = { - N_("git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>..."), - N_("git hash-object --stdin-paths < <list-of-paths>"), - NULL -}; - -static const char *type; -static int write_object; -static int hashstdin; -static int stdin_paths; -static const char *vpath; - -static const struct option hash_object_options[] = { - OPT_STRING('t', NULL, &type, N_("type"), N_("object type")), - OPT_BOOL('w', NULL, &write_object, N_("write the object into the object database")), - OPT_COUNTUP( 0 , "stdin", &hashstdin, N_("read the object from stdin")), - OPT_BOOL( 0 , "stdin-paths", &stdin_paths, N_("read file names from stdin")), - OPT_BOOL( 0 , "no-filters", &no_filters, N_("store file as is without filters")), - OPT_STRING( 0 , "path", &vpath, N_("file"), N_("process file as it were from this path")), - OPT_END() -}; - int cmd_hash_object(int argc, const char **argv, const char *prefix) { + static const char * const hash_object_usage[] = { + N_("git hash-object [-t <type>] [-w] [--path=<file> | --no-filters] [--stdin] [--] <file>..."), + N_("git hash-object --stdin-paths < <list-of-paths>"), + NULL + }; + const char *type = blob_type; + int hashstdin = 0; + int stdin_paths = 0; + int no_filters = 0; + int literally = 0; + unsigned flags = HASH_FORMAT_CHECK; + const char *vpath = NULL; + const struct option hash_object_options[] = { + OPT_STRING('t', NULL, &type, N_("type"), N_("object type")), + OPT_BIT('w', NULL, &flags, N_("write the object into the object database"), + HASH_WRITE_OBJECT), + OPT_COUNTUP( 0 , "stdin", &hashstdin, N_("read the object from stdin")), + OPT_BOOL( 0 , "stdin-paths", &stdin_paths, N_("read file names from stdin")), + OPT_BOOL( 0 , "no-filters", &no_filters, N_("store file as is without filters")), + OPT_BOOL( 0, "literally", &literally, N_("just hash any random garbage to create corrupt objects for debugging Git")), + OPT_STRING( 0 , "path", &vpath, N_("file"), N_("process file as it were from this path")), + OPT_END() + }; int i; int prefix_length = -1; const char *errstr = NULL; - type = blob_type; - argc = parse_options(argc, argv, NULL, hash_object_options, hash_object_usage, 0); - if (write_object) { + if (flags & HASH_WRITE_OBJECT) { prefix = setup_git_directory(); prefix_length = prefix ? strlen(prefix) : 0; if (vpath && prefix) @@ -119,19 +136,19 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) } if (hashstdin) - hash_fd(0, type, write_object, vpath); + hash_fd(0, type, vpath, flags, literally); for (i = 0 ; i < argc; i++) { const char *arg = argv[i]; if (0 <= prefix_length) arg = prefix_filename(prefix, prefix_length, arg); - hash_object(arg, type, write_object, - no_filters ? NULL : vpath ? vpath : arg); + hash_object(arg, type, no_filters ? NULL : vpath ? vpath : arg, + flags, literally); } if (stdin_paths) - hash_stdin_paths(type, write_object); + hash_stdin_paths(type, no_filters, flags, literally); return 0; } diff --git a/builtin/help.c b/builtin/help.c index 1fdefeb686..3422e73079 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -49,7 +49,7 @@ static struct option builtin_help_options[] = { }; static const char * const builtin_help_usage[] = { - N_("git help [--all] [--guides] [--man|--web|--info] [command]"), + N_("git help [--all] [--guides] [--man | --web | --info] [<command>]"), NULL }; @@ -79,12 +79,11 @@ static const char *get_man_viewer_info(const char *name) static int check_emacsclient_version(void) { struct strbuf buffer = STRBUF_INIT; - struct child_process ec_process; + struct child_process ec_process = CHILD_PROCESS_INIT; const char *argv_ec[] = { "emacsclient", "--version", NULL }; int version; /* emacsclient prints its version number on stderr */ - memset(&ec_process, 0, sizeof(ec_process)); ec_process.argv = argv_ec; ec_process.err = -1; ec_process.stdout_to_stderr = 1; @@ -172,7 +171,7 @@ static void exec_man_cmd(const char *cmd, const char *page) { struct strbuf shell_cmd = STRBUF_INIT; strbuf_addf(&shell_cmd, "%s %s", cmd, page); - execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL); + execl(SHELL_PATH, SHELL_PATH, "-c", shell_cmd.buf, (char *)NULL); warning(_("failed to exec '%s': %s"), cmd, strerror(errno)); } @@ -322,16 +321,18 @@ static void setup_man_path(void) { struct strbuf new_path = STRBUF_INIT; const char *old_path = getenv("MANPATH"); + char *git_man_path = system_path(GIT_MAN_PATH); /* We should always put ':' after our path. If there is no * old_path, the ':' at the end will let 'man' to try * system-wide paths after ours to find the manual page. If * there is old_path, we need ':' as delimiter. */ - strbuf_addstr(&new_path, system_path(GIT_MAN_PATH)); + strbuf_addstr(&new_path, git_man_path); strbuf_addch(&new_path, ':'); if (old_path) strbuf_addstr(&new_path, old_path); + free(git_man_path); setenv("MANPATH", new_path.buf, 1); strbuf_release(&new_path); @@ -381,8 +382,10 @@ static void show_info_page(const char *git_cmd) static void get_html_page_path(struct strbuf *page_path, const char *page) { struct stat st; + char *to_free = NULL; + if (!html_path) - html_path = system_path(GIT_HTML_PATH); + html_path = to_free = system_path(GIT_HTML_PATH); /* Check that we have a git documentation directory. */ if (!strstr(html_path, "://")) { @@ -393,6 +396,7 @@ static void get_html_page_path(struct strbuf *page_path, const char *page) strbuf_init(page_path, 0); strbuf_addf(page_path, "%s/%s.html", html_path, page); + free(to_free); } /* @@ -422,6 +426,7 @@ static struct { const char *help; } common_guides[] = { { "attributes", N_("Defining attributes per path") }, + { "everyday", N_("Everyday Git With 20 Commands Or So") }, { "glossary", N_("A Git glossary") }, { "ignore", N_("Specifies intentionally untracked files to ignore") }, { "modules", N_("Defining submodule properties") }, @@ -451,7 +456,7 @@ static void list_common_guides_help(void) int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; - const char *alias; + char *alias; enum help_format parsed_help_format; argc = parse_options(argc, argv, prefix, builtin_help_options, @@ -494,6 +499,7 @@ int cmd_help(int argc, const char **argv, const char *prefix) alias = alias_lookup(argv[0]); if (alias && !is_git_command(argv[0])) { printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias); + free(alias); return 0; } diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 9ca0203922..7ea2020d82 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -18,16 +18,14 @@ static const char index_pack_usage[] = struct object_entry { struct pack_idx_entry idx; unsigned long size; - unsigned int hdr_size; - enum object_type type; - enum object_type real_type; - unsigned delta_depth; - int base_object_no; + unsigned char hdr_size; + signed char type; + signed char real_type; }; -union delta_base { - unsigned char sha1[20]; - off_t offset; +struct object_stat { + unsigned delta_depth; + int base_object_no; }; struct base_data { @@ -49,25 +47,28 @@ struct thread_local { int pack_fd; }; -/* - * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want - * to memcmp() only the first 20 bytes. - */ -#define UNION_BASE_SZ 20 - #define FLAG_LINK (1u<<20) #define FLAG_CHECKED (1u<<21) -struct delta_entry { - union delta_base base; +struct ofs_delta_entry { + off_t offset; + int obj_no; +}; + +struct ref_delta_entry { + unsigned char sha1[20]; int obj_no; }; static struct object_entry *objects; -static struct delta_entry *deltas; +static struct object_stat *obj_stat; +static struct ofs_delta_entry *ofs_deltas; +static struct ref_delta_entry *ref_deltas; static struct thread_local nothread_data; static int nr_objects; -static int nr_deltas; +static int nr_ofs_deltas; +static int nr_ref_deltas; +static int ref_deltas_alloc; static int nr_resolved_deltas; static int nr_threads; @@ -112,6 +113,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 +140,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 +163,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++) @@ -179,6 +186,9 @@ static void cleanup_thread(void) #define deepest_delta_lock() #define deepest_delta_unlock() +#define type_cas_lock() +#define type_cas_unlock() + #endif @@ -438,7 +448,7 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size, if (type == OBJ_BLOB && size > big_file_threshold) buf = fixed_buf; else - buf = xmalloc(size); + buf = xmallocz(size); memset(&stream, 0, sizeof(stream)); git_inflate_init(&stream); @@ -467,7 +477,8 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size, } static void *unpack_raw_entry(struct object_entry *obj, - union delta_base *delta_base, + off_t *ofs_offset, + unsigned char *ref_sha1, unsigned char *sha1) { unsigned char *p; @@ -496,11 +507,10 @@ static void *unpack_raw_entry(struct object_entry *obj, switch (obj->type) { case OBJ_REF_DELTA: - hashcpy(delta_base->sha1, fill(20)); + hashcpy(ref_sha1, fill(20)); use(20); break; case OBJ_OFS_DELTA: - memset(delta_base, 0, sizeof(*delta_base)); p = fill(1); c = *p; use(1); @@ -514,8 +524,8 @@ static void *unpack_raw_entry(struct object_entry *obj, use(1); base_offset = (base_offset << 7) + (c & 127); } - delta_base->offset = obj->idx.offset - base_offset; - if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset) + *ofs_offset = obj->idx.offset - base_offset; + if (*ofs_offset <= 0 || *ofs_offset >= obj->idx.offset) bad_object(obj->idx.offset, _("delta base offset is out of bound")); break; case OBJ_COMMIT: @@ -543,7 +553,7 @@ static void *unpack_data(struct object_entry *obj, git_zstream stream; int status; - data = xmalloc(consume ? 64*1024 : obj->size); + data = xmallocz(consume ? 64*1024 : obj->size); inbuf = xmalloc((len < 64*1024) ? len : 64*1024); memset(&stream, 0, sizeof(stream)); @@ -599,55 +609,108 @@ static void *get_data_from_pack(struct object_entry *obj) return unpack_data(obj, NULL, NULL); } -static int compare_delta_bases(const union delta_base *base1, - const union delta_base *base2, - enum object_type type1, - enum object_type type2) +static int compare_ofs_delta_bases(off_t offset1, off_t offset2, + enum object_type type1, + enum object_type type2) +{ + int cmp = type1 - type2; + if (cmp) + return cmp; + return offset1 - offset2; +} + +static int find_ofs_delta(const off_t offset, enum object_type type) +{ + int first = 0, last = nr_ofs_deltas; + + while (first < last) { + int next = (first + last) / 2; + struct ofs_delta_entry *delta = &ofs_deltas[next]; + int cmp; + + cmp = compare_ofs_delta_bases(offset, delta->offset, + type, objects[delta->obj_no].type); + if (!cmp) + return next; + if (cmp < 0) { + last = next; + continue; + } + first = next+1; + } + return -first-1; +} + +static void find_ofs_delta_children(off_t offset, + int *first_index, int *last_index, + enum object_type type) +{ + int first = find_ofs_delta(offset, type); + int last = first; + int end = nr_ofs_deltas - 1; + + if (first < 0) { + *first_index = 0; + *last_index = -1; + return; + } + while (first > 0 && ofs_deltas[first - 1].offset == offset) + --first; + while (last < end && ofs_deltas[last + 1].offset == offset) + ++last; + *first_index = first; + *last_index = last; +} + +static int compare_ref_delta_bases(const unsigned char *sha1, + const unsigned char *sha2, + enum object_type type1, + enum object_type type2) { int cmp = type1 - type2; if (cmp) return cmp; - return memcmp(base1, base2, UNION_BASE_SZ); + return hashcmp(sha1, sha2); } -static int find_delta(const union delta_base *base, enum object_type type) +static int find_ref_delta(const unsigned char *sha1, enum object_type type) { - int first = 0, last = nr_deltas; - - while (first < last) { - int next = (first + last) / 2; - struct delta_entry *delta = &deltas[next]; - int cmp; - - cmp = compare_delta_bases(base, &delta->base, - type, objects[delta->obj_no].type); - if (!cmp) - return next; - if (cmp < 0) { - last = next; - continue; - } - first = next+1; - } - return -first-1; + int first = 0, last = nr_ref_deltas; + + while (first < last) { + int next = (first + last) / 2; + struct ref_delta_entry *delta = &ref_deltas[next]; + int cmp; + + cmp = compare_ref_delta_bases(sha1, delta->sha1, + type, objects[delta->obj_no].type); + if (!cmp) + return next; + if (cmp < 0) { + last = next; + continue; + } + first = next+1; + } + return -first-1; } -static void find_delta_children(const union delta_base *base, - int *first_index, int *last_index, - enum object_type type) +static void find_ref_delta_children(const unsigned char *sha1, + int *first_index, int *last_index, + enum object_type type) { - int first = find_delta(base, type); + int first = find_ref_delta(sha1, type); int last = first; - int end = nr_deltas - 1; + int end = nr_ref_deltas - 1; if (first < 0) { *first_index = 0; *last_index = -1; return; } - while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ)) + while (first > 0 && !hashcmp(ref_deltas[first - 1].sha1, sha1)) --first; - while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ)) + while (last < end && !hashcmp(ref_deltas[last + 1].sha1, sha1)) ++last; *first_index = first; *last_index = last; @@ -773,7 +836,8 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, if (!obj) die(_("invalid %s"), typename(type)); if (do_fsck_object && - fsck_object(obj, 1, fsck_error_function)) + fsck_object(obj, buf, size, 1, + fsck_error_function)) die(_("Error in object")); if (fsck_walk(obj, mark_link, NULL)) die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1)); @@ -862,15 +926,16 @@ 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; + int i = delta_obj - objects; + int j = base->obj - objects; + obj_stat[i].delta_depth = obj_stat[j].delta_depth + 1; deepest_delta_lock(); - if (deepest_delta < delta_obj->delta_depth) - deepest_delta = delta_obj->delta_depth; + if (deepest_delta < obj_stat[i].delta_depth) + deepest_delta = obj_stat[i].delta_depth; deepest_delta_unlock(); + obj_stat[i].base_object_no = j; } - delta_obj->base_object_no = base->obj - objects; delta_data = get_data_from_pack(delta_obj); base_data = get_base_data(base); result->obj = delta_obj; @@ -888,20 +953,37 @@ 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(signed char *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) { if (base->ref_last == -1 && base->ofs_last == -1) { - union delta_base base_spec; - - hashcpy(base_spec.sha1, base->obj->idx.sha1); - find_delta_children(&base_spec, - &base->ref_first, &base->ref_last, OBJ_REF_DELTA); + find_ref_delta_children(base->obj->idx.sha1, + &base->ref_first, &base->ref_last, + OBJ_REF_DELTA); - memset(&base_spec, 0, sizeof(base_spec)); - base_spec.offset = base->obj->idx.offset; - find_delta_children(&base_spec, - &base->ofs_first, &base->ofs_last, OBJ_OFS_DELTA); + find_ofs_delta_children(base->obj->idx.offset, + &base->ofs_first, &base->ofs_last, + OBJ_OFS_DELTA); if (base->ref_last == -1 && base->ofs_last == -1) { free(base->data); @@ -912,10 +994,13 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base, } if (base->ref_first <= base->ref_last) { - struct object_entry *child = objects + deltas[base->ref_first].obj_no; + struct object_entry *child = objects + ref_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); @@ -925,10 +1010,11 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base, } if (base->ofs_first <= base->ofs_last) { - struct object_entry *child = objects + deltas[base->ofs_first].obj_no; + struct object_entry *child = objects + ofs_deltas[base->ofs_first].obj_no; 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); @@ -960,15 +1046,20 @@ static void find_unresolved_deltas(struct base_data *base) } } -static int compare_delta_entry(const void *a, const void *b) +static int compare_ofs_delta_entry(const void *a, const void *b) +{ + const struct ofs_delta_entry *delta_a = a; + const struct ofs_delta_entry *delta_b = b; + + return delta_a->offset - delta_b->offset; +} + +static int compare_ref_delta_entry(const void *a, const void *b) { - const struct delta_entry *delta_a = a; - const struct delta_entry *delta_b = b; + const struct ref_delta_entry *delta_a = a; + const struct ref_delta_entry *delta_b = b; - /* group by type (ref vs ofs) and then by value (sha-1 or offset) */ - return compare_delta_bases(&delta_a->base, &delta_b->base, - objects[delta_a->obj_no].type, - objects[delta_b->obj_no].type); + return hashcmp(delta_a->sha1, delta_b->sha1); } static void resolve_base(struct object_entry *obj) @@ -1014,7 +1105,8 @@ static void *threaded_second_pass(void *data) static void parse_pack_objects(unsigned char *sha1) { int i, nr_delays = 0; - struct delta_entry *delta = deltas; + struct ofs_delta_entry *ofs_delta = ofs_deltas; + unsigned char ref_delta_sha1[20]; struct stat st; if (verbose) @@ -1023,12 +1115,18 @@ static void parse_pack_objects(unsigned char *sha1) nr_objects); for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; - void *data = unpack_raw_entry(obj, &delta->base, obj->idx.sha1); + void *data = unpack_raw_entry(obj, &ofs_delta->offset, + ref_delta_sha1, obj->idx.sha1); obj->real_type = obj->type; - if (is_delta_type(obj->type)) { - nr_deltas++; - delta->obj_no = i; - delta++; + if (obj->type == OBJ_OFS_DELTA) { + nr_ofs_deltas++; + ofs_delta->obj_no = i; + ofs_delta++; + } else if (obj->type == OBJ_REF_DELTA) { + ALLOC_GROW(ref_deltas, nr_ref_deltas + 1, ref_deltas_alloc); + hashcpy(ref_deltas[nr_ref_deltas].sha1, ref_delta_sha1); + ref_deltas[nr_ref_deltas].obj_no = i; + nr_ref_deltas++; } else if (!data) { /* large blobs, check later */ obj->real_type = OBJ_BAD; @@ -1079,15 +1177,18 @@ static void resolve_deltas(void) { int i; - if (!nr_deltas) + if (!nr_ofs_deltas && !nr_ref_deltas) return; /* Sort deltas by base SHA1/offset for fast searching */ - qsort(deltas, nr_deltas, sizeof(struct delta_entry), - compare_delta_entry); + qsort(ofs_deltas, nr_ofs_deltas, sizeof(struct ofs_delta_entry), + compare_ofs_delta_entry); + qsort(ref_deltas, nr_ref_deltas, sizeof(struct ref_delta_entry), + compare_ref_delta_entry); if (verbose) - progress = start_progress(_("Resolving deltas"), nr_deltas); + progress = start_progress(_("Resolving deltas"), + nr_ref_deltas + nr_ofs_deltas); #ifndef NO_PTHREADS nr_dispatched = 0; @@ -1125,7 +1226,7 @@ static void resolve_deltas(void) static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved); static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1) { - if (nr_deltas == nr_resolved_deltas) { + if (nr_ref_deltas + nr_ofs_deltas == nr_resolved_deltas) { stop_progress(&progress); /* Flush remaining pack final 20-byte SHA1. */ flush(); @@ -1136,13 +1237,11 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha struct sha1file *f; unsigned char read_sha1[20], tail_sha1[20]; struct strbuf msg = STRBUF_INIT; - int nr_unresolved = nr_deltas - nr_resolved_deltas; + int nr_unresolved = nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas; int nr_objects_initial = nr_objects; if (nr_unresolved <= 0) die(_("confusion beyond insanity")); - objects = xrealloc(objects, - (nr_objects + nr_unresolved + 1) - * sizeof(*objects)); + REALLOC_ARRAY(objects, nr_objects + nr_unresolved + 1); memset(objects + nr_objects + 1, 0, nr_unresolved * sizeof(*objects)); f = sha1fd(output_fd, curr_pack); @@ -1160,11 +1259,11 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha die(_("Unexpected tail checksum for %s " "(disk corruption?)"), curr_pack); } - if (nr_deltas != nr_resolved_deltas) + if (nr_ofs_deltas + nr_ref_deltas != nr_resolved_deltas) die(Q_("pack has %d unresolved delta", "pack has %d unresolved deltas", - nr_deltas - nr_resolved_deltas), - nr_deltas - nr_resolved_deltas); + nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas), + nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas); } static int write_compressed(struct sha1file *f, void *in, unsigned int size) @@ -1173,7 +1272,6 @@ static int write_compressed(struct sha1file *f, void *in, unsigned int size) int status; unsigned char outbuf[4096]; - memset(&stream, 0, sizeof(stream)); git_deflate_init(&stream, zlib_compression_level); stream.next_in = in; stream.avail_in = size; @@ -1224,14 +1322,14 @@ static struct object_entry *append_obj_to_pack(struct sha1file *f, static int delta_pos_compare(const void *_a, const void *_b) { - struct delta_entry *a = *(struct delta_entry **)_a; - struct delta_entry *b = *(struct delta_entry **)_b; + struct ref_delta_entry *a = *(struct ref_delta_entry **)_a; + struct ref_delta_entry *b = *(struct ref_delta_entry **)_b; return a->obj_no - b->obj_no; } static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) { - struct delta_entry **sorted_by_pos; + struct ref_delta_entry **sorted_by_pos; int i, n = 0; /* @@ -1245,28 +1343,25 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) * resolving deltas in the same order as their position in the pack. */ sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos)); - for (i = 0; i < nr_deltas; i++) { - if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA) - continue; - sorted_by_pos[n++] = &deltas[i]; - } + for (i = 0; i < nr_ref_deltas; i++) + sorted_by_pos[n++] = &ref_deltas[i]; qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare); for (i = 0; i < n; i++) { - struct delta_entry *d = sorted_by_pos[i]; + struct ref_delta_entry *d = sorted_by_pos[i]; enum object_type type; struct base_data *base_obj = alloc_base_data(); if (objects[d->obj_no].real_type != OBJ_REF_DELTA) continue; - base_obj->data = read_sha1_file(d->base.sha1, &type, &base_obj->size); + base_obj->data = read_sha1_file(d->sha1, &type, &base_obj->size); if (!base_obj->data) continue; - if (check_sha1_signature(d->base.sha1, base_obj->data, + if (check_sha1_signature(d->sha1, base_obj->data, base_obj->size, typename(type))) - die(_("local object %s is corrupt"), sha1_to_hex(d->base.sha1)); - base_obj->obj = append_obj_to_pack(f, d->base.sha1, + die(_("local object %s is corrupt"), sha1_to_hex(d->sha1)); + base_obj->obj = append_obj_to_pack(f, d->sha1, base_obj->data, base_obj->size, type); find_unresolved_deltas(base_obj); display_progress(progress, nr_resolved_deltas); @@ -1458,7 +1553,7 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name) static void show_pack_info(int stat_only) { - int i, baseobjects = nr_objects - nr_deltas; + int i, baseobjects = nr_objects - nr_ref_deltas - nr_ofs_deltas; unsigned long *chain_histogram = NULL; if (deepest_delta) @@ -1468,7 +1563,7 @@ static void show_pack_info(int stat_only) struct object_entry *obj = &objects[i]; if (is_delta_type(obj->type)) - chain_histogram[obj->delta_depth - 1]++; + chain_histogram[obj_stat[i].delta_depth - 1]++; if (stat_only) continue; printf("%s %-6s %lu %lu %"PRIuMAX, @@ -1477,8 +1572,8 @@ static void show_pack_info(int stat_only) (unsigned long)(obj[1].idx.offset - obj->idx.offset), (uintmax_t)obj->idx.offset); if (is_delta_type(obj->type)) { - struct object_entry *bobj = &objects[obj->base_object_no]; - printf(" %u %s", obj->delta_depth, sha1_to_hex(bobj->idx.sha1)); + struct object_entry *bobj = &objects[obj_stat[i].base_object_no]; + printf(" %u %s", obj_stat[i].delta_depth, sha1_to_hex(bobj->idx.sha1)); } putchar('\n'); } @@ -1505,7 +1600,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 +1698,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) @@ -1642,11 +1736,14 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) curr_pack = open_pack_file(pack_name); parse_pack_header(); objects = xcalloc(nr_objects + 1, sizeof(struct object_entry)); - deltas = xcalloc(nr_objects, sizeof(struct delta_entry)); + if (show_stat) + obj_stat = xcalloc(nr_objects + 1, sizeof(struct object_stat)); + ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry)); parse_pack_objects(pack_sha1); resolve_deltas(); conclude_pack(fix_thin_pack, curr_pack, pack_sha1); - free(deltas); + free(ofs_deltas); + free(ref_deltas); if (strict) foreign_nr = check_objects(); @@ -1667,8 +1764,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/init-db.c b/builtin/init-db.c index 56f85e239a..4335738135 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -119,15 +119,18 @@ static void copy_templates(const char *template_dir) DIR *dir; const char *git_dir = get_git_dir(); int len = strlen(git_dir); + char *to_free = NULL; if (!template_dir) template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT); if (!template_dir) template_dir = init_db_template_dir; if (!template_dir) - template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR); - if (!template_dir[0]) + template_dir = to_free = system_path(DEFAULT_GIT_TEMPLATE_DIR); + if (!template_dir[0]) { + free(to_free); return; + } template_len = strlen(template_dir); if (PATH_MAX <= (template_len+strlen("/config"))) die(_("insanely long template path %s"), template_dir); @@ -139,7 +142,7 @@ static void copy_templates(const char *template_dir) dir = opendir(template_path); if (!dir) { warning(_("templates not found %s"), template_dir); - return; + goto free_return; } /* Make sure that template is from the correct vintage */ @@ -155,8 +158,7 @@ static void copy_templates(const char *template_dir) "a wrong format version %d from '%s'"), repository_format_version, template_dir); - closedir(dir); - return; + goto close_free_return; } memcpy(path, git_dir, len); @@ -166,7 +168,10 @@ static void copy_templates(const char *template_dir) copy_templates_1(path, len, template_path, template_len, dir); +close_free_return: closedir(dir); +free_return: + free(to_free); } static int git_init_db_config(const char *k, const char *v, void *cb) @@ -177,6 +182,20 @@ static int git_init_db_config(const char *k, const char *v, void *cb) return 0; } +/* + * If the git_dir is not directly inside the working tree, then git will not + * find it by default, and we need to set the worktree explicitly. + */ +static int needs_work_tree_config(const char *git_dir, const char *work_tree) +{ + if (!strcmp(work_tree, "/") && !strcmp(git_dir, "/.git")) + return 0; + if (skip_prefix(git_dir, work_tree, &git_dir) && + !strcmp(git_dir, "/.git")) + return 0; + return 1; +} + static int create_default_files(const char *template_path) { const char *git_dir = get_git_dir(); @@ -254,7 +273,10 @@ static int create_default_files(const char *template_path) struct stat st2; filemode = (!chmod(path, st1.st_mode ^ S_IXUSR) && !lstat(path, &st2) && - st1.st_mode != st2.st_mode); + st1.st_mode != st2.st_mode && + !chmod(path, st1.st_mode)); + if (filemode && !reinit && (st1.st_mode & S_IXUSR)) + filemode = 0; } git_config_set("core.filemode", filemode ? "true" : "false"); @@ -266,10 +288,8 @@ static int create_default_files(const char *template_path) /* allow template config file to override the default */ if (log_all_ref_updates == -1) git_config_set("core.logallrefupdates", "true"); - if (!starts_with(git_dir, work_tree) || - strcmp(git_dir + strlen(work_tree), "/.git")) { + if (needs_work_tree_config(git_dir, work_tree)) git_config_set("core.worktree", work_tree); - } } if (!reinit) { @@ -330,19 +350,18 @@ int set_git_dir_init(const char *git_dir, const char *real_git_dir, * moving the target repo later on in separate_git_dir() */ git_link = xstrdup(real_path(git_dir)); + set_git_dir(real_path(real_git_dir)); } else { - real_git_dir = real_path(git_dir); + set_git_dir(real_path(git_dir)); git_link = NULL; } - set_git_dir(real_path(real_git_dir)); return 0; } static void separate_git_dir(const char *git_dir) { struct stat st; - FILE *fp; if (!stat(git_link, &st)) { const char *src; @@ -358,11 +377,7 @@ static void separate_git_dir(const char *git_dir) die_errno(_("unable to move %s to %s"), src, git_dir); } - fp = fopen(git_link, "w"); - if (!fp) - die(_("Could not create git link %s"), git_link); - fprintf(fp, "gitdir: %s\n", git_dir); - fclose(fp); + write_file(git_link, 1, "gitdir: %s\n", git_dir); } int init_db(const char *template_dir, unsigned int flags) @@ -426,8 +441,9 @@ int init_db(const char *template_dir, unsigned int flags) static int guess_repository_type(const char *git_dir) { - char cwd[PATH_MAX]; const char *slash; + char *cwd; + int cwd_is_git_dir; /* * "GIT_DIR=. git init" is always bare. @@ -435,9 +451,10 @@ static int guess_repository_type(const char *git_dir) */ if (!strcmp(".", git_dir)) return 1; - if (!getcwd(cwd, sizeof(cwd))) - die_errno(_("cannot tell cwd")); - if (!strcmp(git_dir, cwd)) + cwd = xgetcwd(); + cwd_is_git_dir = !strcmp(git_dir, cwd); + free(cwd); + if (cwd_is_git_dir) return 1; /* * "GIT_DIR=.git or GIT_DIR=something/.git is usually not. @@ -462,7 +479,7 @@ static int shared_callback(const struct option *opt, const char *arg, int unset) } static const char *const init_db_usage[] = { - N_("git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [directory]"), + N_("git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [<directory>]"), NULL }; @@ -535,10 +552,9 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) usage(init_db_usage[0]); } if (is_bare_repository_cfg == 1) { - static char git_dir[PATH_MAX+1]; - - setenv(GIT_DIR_ENVIRONMENT, - getcwd(git_dir, sizeof(git_dir)), argc > 0); + char *cwd = xgetcwd(); + setenv(GIT_DIR_ENVIRONMENT, cwd, argc > 0); + free(cwd); } if (init_shared_repository != -1) @@ -572,13 +588,10 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) git_work_tree_cfg = xstrdup(real_path(rel)); free(rel); } - if (!git_work_tree_cfg) { - git_work_tree_cfg = xcalloc(PATH_MAX, 1); - if (!getcwd(git_work_tree_cfg, PATH_MAX)) - die_errno (_("Cannot access current working directory")); - } + if (!git_work_tree_cfg) + git_work_tree_cfg = xgetcwd(); if (work_tree) - set_git_work_tree(real_path(work_tree)); + set_git_work_tree(work_tree); else set_git_work_tree(git_work_tree_cfg); if (access(get_git_work_tree(), X_OK)) @@ -587,7 +600,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) } else { if (work_tree) - set_git_work_tree(real_path(work_tree)); + set_git_work_tree(work_tree); } set_git_dir_init(git_dir, real_git_dir, 1); diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c new file mode 100644 index 0000000000..46838d24a9 --- /dev/null +++ b/builtin/interpret-trailers.c @@ -0,0 +1,44 @@ +/* + * Builtin "git interpret-trailers" + * + * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org> + * + */ + +#include "cache.h" +#include "builtin.h" +#include "parse-options.h" +#include "string-list.h" +#include "trailer.h" + +static const char * const git_interpret_trailers_usage[] = { + N_("git interpret-trailers [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"), + NULL +}; + +int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) +{ + int trim_empty = 0; + struct string_list trailers = STRING_LIST_INIT_DUP; + + struct option options[] = { + OPT_BOOL(0, "trim-empty", &trim_empty, N_("trim empty trailers")), + OPT_STRING_LIST(0, "trailer", &trailers, N_("trailer"), + N_("trailer(s) to add")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_interpret_trailers_usage, 0); + + if (argc) { + int i; + for (i = 0; i < argc; i++) + process_trailers(argv[i], trim_empty, &trailers); + } else + process_trailers(NULL, trim_empty, &trailers); + + string_list_clear(&trailers, 0); + + return 0; +} diff --git a/builtin/log.c b/builtin/log.c index 4f678136d1..dd8f3fcfc4 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -38,8 +38,8 @@ static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; static const char * const builtin_log_usage[] = { - N_("git log [<options>] [<revision range>] [[--] <path>...]\n") - N_(" or: git show [options] <object>..."), + N_("git log [<options>] [<revision range>] [[--] <path>...]"), + N_("git show [<options>] <object>..."), NULL }; @@ -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; } @@ -76,7 +78,7 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse decoration_style = DECORATE_SHORT_REFS; if (decoration_style < 0) - die("invalid --decorate option: %s", arg); + die(_("invalid --decorate option: %s"), arg); decoration_given = 1; @@ -128,7 +130,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback}, OPT_CALLBACK('L', NULL, &line_cb, "n,m:file", - "Process line range n,m in file, counting from 1", + N_("Process line range n,m in file, counting from 1"), log_line_range_callback), OPT_END() }; @@ -148,7 +150,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, /* Any arguments at this point are not recognized */ if (argc > 1) - die("unrecognized argument: %s", argv[1]); + die(_("unrecognized argument: %s"), argv[1]); memset(&w, 0, sizeof(w)); userformat_find_requirements(NULL, &w); @@ -366,6 +368,8 @@ static int cmd_log_walk(struct rev_info *rev) static int git_log_config(const char *var, const char *value, void *cb) { + const char *slot_name; + if (!strcmp(var, "format.pretty")) return git_config_string(&fmt_pretty, var, value); if (!strcmp(var, "format.subjectprefix")) @@ -386,8 +390,8 @@ static int git_log_config(const char *var, const char *value, void *cb) default_show_root = git_config_bool(var, value); return 0; } - if (starts_with(var, "color.decorate.")) - return parse_decorate_color_config(var, 15, value); + if (skip_prefix(var, "color.decorate.", &slot_name)) + return parse_decorate_color_config(var, slot_name, value); if (!strcmp(var, "log.mailmap")) { use_mailmap_config = git_config_bool(var, value); return 0; @@ -445,13 +449,13 @@ static int show_blob_object(const unsigned char *sha1, struct rev_info *rev, con return stream_blob_to_fd(1, sha1, NULL, 0); if (get_sha1_with_context(obj_name, 0, sha1c, &obj_context)) - die("Not a valid object name %s", obj_name); + die(_("Not a valid object name %s"), obj_name); if (!obj_context.path[0] || !textconv_object(obj_context.path, obj_context.mode, sha1c, 1, &buf, &size)) return stream_blob_to_fd(1, sha1, NULL, 0); if (!buf) - die("git show %s: bad file", obj_name); + die(_("git show %s: bad file"), obj_name); write_or_die(1, buf, size); return 0; @@ -485,7 +489,7 @@ static int show_tag_object(const unsigned char *sha1, struct rev_info *rev) } static int show_tree_object(const unsigned char *sha1, - const char *base, int baselen, + struct strbuf *base, const char *pathname, unsigned mode, int stage, void *context) { printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : ""); @@ -668,6 +672,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 { @@ -700,7 +705,7 @@ static int git_format_config(const char *var, const char *value, void *cb) return 0; } if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff") || - !strcmp(var, "color.ui")) { + !strcmp(var, "color.ui") || !strcmp(var, "diff.submodule")) { return 0; } if (!strcmp(var, "format.numbered")) { @@ -737,6 +742,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 +846,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,9 +863,10 @@ 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'); } + strbuf_release(&desc); } static char *find_branch_name(struct rev_info *rev) @@ -861,7 +874,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 +890,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; } @@ -1010,7 +1023,7 @@ static const char *set_outdir(const char *prefix, const char *output_directory) } static const char * const builtin_format_patch_usage[] = { - N_("git format-patch [options] [<since> | <revision range>]"), + N_("git format-patch [<options>] [<since> | <revision-range>]"), NULL }; @@ -1228,6 +1241,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 +1399,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (check_head) { unsigned char sha1[20]; - const char *ref; - ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL); - if (ref && starts_with(ref, "refs/heads/")) - branch_name = xstrdup(ref + strlen("refs/heads/")); + const char *ref, *v; + ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + sha1, NULL); + if (ref && skip_prefix(ref, "refs/heads/", &v)) + branch_name = xstrdup(v); else branch_name = xstrdup(""); /* no branch */ } @@ -1427,7 +1443,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) continue; nr++; - list = xrealloc(list, nr * sizeof(list[0])); + REALLOC_ARRAY(list, nr); list[nr - 1] = commit; } if (nr == 0) @@ -1445,6 +1461,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/ls-files.c b/builtin/ls-files.c index 47c38808a2..6fa2205734 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -354,51 +354,8 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) } } -int report_path_error(const char *ps_matched, - const struct pathspec *pathspec, - const char *prefix) -{ - /* - * Make sure all pathspec matched; otherwise it is an error. - */ - struct strbuf sb = STRBUF_INIT; - int num, errors = 0; - for (num = 0; num < pathspec->nr; num++) { - int other, found_dup; - - if (ps_matched[num]) - continue; - /* - * The caller might have fed identical pathspec - * twice. Do not barf on such a mistake. - * FIXME: parse_pathspec should have eliminated - * duplicate pathspec. - */ - for (found_dup = other = 0; - !found_dup && other < pathspec->nr; - other++) { - if (other == num || !ps_matched[other]) - continue; - if (!strcmp(pathspec->items[other].original, - pathspec->items[num].original)) - /* - * Ok, we have a match already. - */ - found_dup = 1; - } - if (found_dup) - continue; - - error("pathspec '%s' did not match any file(s) known to git.", - pathspec->items[num].original); - errors++; - } - strbuf_release(&sb); - return errors; -} - static const char * const ls_files_usage[] = { - N_("git ls-files [options] [<file>...]"), + N_("git ls-files [<options>] [<file>...]"), NULL }; @@ -474,7 +431,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) OPT_BOOL('k', "killed", &show_killed, N_("show files on the filesystem that need to be removed")), OPT_BIT(0, "directory", &dir.flags, - N_("show 'other' directories' name only"), + N_("show 'other' directories' names only"), DIR_SHOW_OTHER_DIRECTORIES), OPT_NEGBIT(0, "empty-directory", &dir.flags, N_("don't show empty directories"), diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index b2a4b92992..4554dbc8a9 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -5,7 +5,7 @@ static const char ls_remote_usage[] = "git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>]\n" -" [-q|--quiet] [--exit-code] [--get-url] [<repository> [<refs>...]]"; +" [-q | --quiet] [--exit-code] [--get-url] [<repository> [<refs>...]]"; /* * Is there one among the list of patterns that match the tail part diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 51184dfa2e..3b04a0f082 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -61,10 +61,11 @@ static int show_recursive(const char *base, int baselen, const char *pathname) } } -static int show_tree(const unsigned char *sha1, const char *base, int baselen, +static int show_tree(const unsigned char *sha1, struct strbuf *base, const char *pathname, unsigned mode, int stage, void *context) { int retval = 0; + int baselen; const char *type = blob_type; if (S_ISGITLINK(mode)) { @@ -79,7 +80,7 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen, */ type = commit_type; } else if (S_ISDIR(mode)) { - if (show_recursive(base, baselen, pathname)) { + if (show_recursive(base->buf, base->len, pathname)) { retval = READ_TREE_RECURSIVE; if (!(ls_options & LS_SHOW_TREES)) return retval; @@ -89,10 +90,6 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen, else if (ls_options & LS_TREE_ONLY) return 0; - if (chomp_prefix && - (baselen < chomp_prefix || memcmp(ls_tree_prefix, base, chomp_prefix))) - return 0; - if (!(ls_options & LS_NAME_ONLY)) { if (ls_options & LS_SHOW_SIZE) { char size_text[24]; @@ -112,8 +109,12 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen, printf("%06o %s %s\t", mode, type, find_unique_abbrev(sha1, abbrev)); } - write_name_quotedpfx(base + chomp_prefix, baselen - chomp_prefix, - pathname, stdout, line_termination); + baselen = base->len; + strbuf_addstr(base, pathname); + write_name_quoted_relative(base->buf, + chomp_prefix ? ls_tree_prefix : NULL, + stdout, line_termination); + strbuf_setlen(base, baselen); return retval; } @@ -173,7 +174,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) * cannot be lifted until it is converted to use * match_pathspec() or tree_entry_interesting() */ - parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE, + parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE | + PATHSPEC_EXCLUDE, PATHSPEC_PREFER_CWD, prefix, argv + 1); for (i = 0; i < pathspec.nr; i++) diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index cf11c8d607..999a5250fb 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -15,6 +15,7 @@ static const char *metainfo_charset; static struct strbuf line = STRBUF_INIT; static struct strbuf name = STRBUF_INIT; static struct strbuf email = STRBUF_INIT; +static char *message_id; static enum { TE_DONTCARE, TE_QP, TE_BASE64 @@ -24,6 +25,7 @@ static struct strbuf charset = STRBUF_INIT; static int patch_lines; static struct strbuf **p_hdr_data, **s_hdr_data; static int use_scissors; +static int add_message_id; static int use_inbody_headers = 1; #define MAX_HDR_PARSED 10 @@ -198,6 +200,12 @@ static void handle_content_type(struct strbuf *line) } } +static void handle_message_id(const struct strbuf *line) +{ + if (add_message_id) + message_id = strdup(line->buf); +} + static void handle_content_transfer_encoding(const struct strbuf *line) { if (strcasestr(line->buf, "base64")) @@ -288,6 +296,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) { @@ -326,10 +350,18 @@ static int check_header(const struct strbuf *line, ret = 1; goto check_header_out; } + if (cmp_header(line, "Message-Id")) { + len = strlen("Message-Id: "); + strbuf_add(&sb, line->buf + len, line->len - len); + decode_header(&sb); + handle_message_id(&sb); + ret = 1; + goto check_header_out; + } /* 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])) { @@ -800,6 +832,8 @@ static int handle_commit_msg(struct strbuf *line) } if (patchbreak(line)) { + if (message_id) + fprintf(cmitmsg, "Message-Id: %s\n", message_id); fclose(cmitmsg); cmitmsg = NULL; return 1; @@ -997,7 +1031,7 @@ static int git_mailinfo_config(const char *var, const char *value, void *unused) } static const char mailinfo_usage[] = - "git mailinfo [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info"; + "git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info"; int cmd_mailinfo(int argc, const char **argv, const char *prefix) { @@ -1016,6 +1050,8 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) keep_subject = 1; else if (!strcmp(argv[1], "-b")) keep_non_patch_brackets_in_subject = 1; + else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--message-id")) + add_message_id = 1; else if (!strcmp(argv[1], "-u")) metainfo_charset = def_charset; else if (!strcmp(argv[1], "-n")) diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index 06296d4bdf..8e02ea109a 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -53,14 +53,15 @@ static int keep_cr; */ static int split_one(FILE *mbox, const char *name, int allow_bare) { - FILE *output = NULL; + FILE *output; int fd; int status = 0; int is_bare = !is_from_line(buf.buf, buf.len); - if (is_bare && !allow_bare) - goto corrupt; - + if (is_bare && !allow_bare) { + fprintf(stderr, "corrupt mailbox\n"); + exit(1); + } fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666); if (fd < 0) die_errno("cannot open output file '%s'", name); @@ -91,13 +92,6 @@ static int split_one(FILE *mbox, const char *name, int allow_bare) } fclose(output); return status; - - corrupt: - if (output) - fclose(output); - unlink(name); - fprintf(stderr, "corrupt mailbox\n"); - exit(1); } static int populate_maildir_list(struct string_list *list, const char *path) diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 0ecde8da30..08a8217890 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -10,7 +10,7 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all) { struct commit_list *result; - result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0); + result = get_merge_bases_many_dirty(rev[0], rev_nr - 1, rev + 1); if (!result) return 1; @@ -26,8 +26,8 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all) } static const char * const merge_base_usage[] = { - N_("git merge-base [-a|--all] <commit> <commit>..."), - N_("git merge-base [-a|--all] --octopus <commit>..."), + N_("git merge-base [-a | --all] <commit> <commit>..."), + N_("git merge-base [-a | --all] --octopus <commit>..."), N_("git merge-base --independent <commit>..."), N_("git merge-base --is-ancestor <commit> <commit>"), N_("git merge-base --fork-point <ref> [<commit>]"), @@ -176,7 +176,7 @@ static int handle_fork_point(int argc, const char **argv) for (i = 0; i < revs.nr; i++) revs.commit[i]->object.flags &= ~TMP_MARK; - bases = get_merge_bases_many(derived, revs.nr, revs.commit, 0); + bases = get_merge_bases_many_dirty(derived, revs.nr, revs.commit); /* * There should be one and only one merge base, when we found diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 844f84f40b..ea8093f676 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -5,7 +5,7 @@ #include "parse-options.h" static const char *const merge_file_usage[] = { - N_("git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file file2"), + N_("git merge-file [<options>] [-L <name1> [-L <orig> [-L <name2>]]] <file1> <orig-file> <file2>"), NULL }; @@ -42,7 +42,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) N_("for conflicts, use this marker size")), OPT__QUIET(&quiet, N_("do not warn about conflicts")), OPT_CALLBACK('L', NULL, names, N_("name"), - N_("set labels for file1/orig_file/file2"), &label_cb), + N_("set labels for file1/orig-file/file2"), &label_cb), OPT_END(), }; @@ -90,7 +90,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) if (ret >= 0) { const char *filename = argv[0]; - FILE *f = to_stdout ? stdout : fopen(filename, "wb"); + const char *fpath = prefix_filename(prefix, prefixlen, argv[0]); + FILE *f = to_stdout ? stdout : fopen(fpath, "wb"); if (!f) ret = error("Could not open %s for writing", filename); diff --git a/builtin/merge-index.c b/builtin/merge-index.c index b416d92849..1a1eafa6fd 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -75,7 +75,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix) signal(SIGCHLD, SIG_DFL); if (argc < 3) - usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)"); + usage("git merge-index [-o] [-q] <merge-program> (-a | [--] [<filename>...])"); read_cache(); diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 61cbde4094..f9ab48597e 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -25,7 +25,7 @@ static void add_merge_entry(struct merge_list *entry) merge_result_end = &entry->next; } -static void merge_trees_recursive(struct tree_desc t[3], const char *base, int df_conflict); +static void merge_trees(struct tree_desc t[3], const char *base); static const char *explanation(struct merge_list *entry) { @@ -195,8 +195,8 @@ static void resolve(const struct traverse_info *info, struct name_entry *ours, s add_merge_entry(final); } -static void unresolved_directory(const struct traverse_info *info, struct name_entry n[3], - int df_conflict) +static void unresolved_directory(const struct traverse_info *info, + struct name_entry n[3]) { char *newbase; struct name_entry *p; @@ -218,7 +218,7 @@ static void unresolved_directory(const struct traverse_info *info, struct name_e buf2 = fill_tree_descriptor(t+2, ENTRY_SHA1(n + 2)); #undef ENTRY_SHA1 - merge_trees_recursive(t, newbase, df_conflict); + merge_trees(t, newbase); free(buf0); free(buf1); @@ -259,7 +259,7 @@ static void unresolved(const struct traverse_info *info, struct name_entry n[3]) dirmask |= (1 << i); } - unresolved_directory(info, n, dirmask && (dirmask != mask)); + unresolved_directory(info, n); if (dirmask == mask) return; @@ -335,21 +335,15 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s return mask; } -static void merge_trees_recursive(struct tree_desc t[3], const char *base, int df_conflict) +static void merge_trees(struct tree_desc t[3], const char *base) { struct traverse_info info; setup_traverse_info(&info, base); - info.data = &df_conflict; info.fn = threeway_callback; traverse_trees(3, t, &info); } -static void merge_trees(struct tree_desc t[3], const char *base) -{ - merge_trees_recursive(t, base, 0); -} - static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) { unsigned char sha1[20]; diff --git a/builtin/merge.c b/builtin/merge.c index 8763b2efa2..f89f60e11a 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -9,6 +9,7 @@ #include "cache.h" #include "parse-options.h" #include "builtin.h" +#include "lockfile.h" #include "run-command.h" #include "diff.h" #include "refs.h" @@ -28,6 +29,7 @@ #include "remote.h" #include "fmt-merge-msg.h" #include "gpg-interface.h" +#include "sequencer.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -40,8 +42,8 @@ struct strategy { }; static const char * const builtin_merge_usage[] = { - N_("git merge [options] [<commit>...]"), - N_("git merge [options] <msg> HEAD <commit>"), + N_("git merge [<options>] [<commit>...]"), + N_("git merge [<options>] <msg> HEAD <commit>"), N_("git merge --abort"), NULL }; @@ -63,7 +65,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[] = { @@ -237,11 +239,10 @@ static void drop_save(void) static int save_state(unsigned char *stash) { int len; - struct child_process cp; + struct child_process cp = CHILD_PROCESS_INIT; struct strbuf buffer = STRBUF_INIT; const char *argv[] = {"stash", "create", NULL}; - memset(&cp, 0, sizeof(cp)); cp.argv = argv; cp.out = -1; cp.git_cmd = 1; @@ -398,7 +399,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. @@ -491,8 +492,7 @@ static void merge_name(const char *remote, struct strbuf *msg) } if (len) { struct strbuf truname = STRBUF_INIT; - strbuf_addstr(&truname, "refs/heads/"); - strbuf_addstr(&truname, remote); + strbuf_addf(&truname, "refs/heads/%s", remote); strbuf_setlen(&truname, truname.len - len); if (ref_exists(truname.buf)) { strbuf_addf(msg, @@ -503,28 +503,7 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_release(&truname); goto cleanup; } - } - - if (!strcmp(remote, "FETCH_HEAD") && - !access(git_path("FETCH_HEAD"), R_OK)) { - const char *filename; - FILE *fp; - struct strbuf line = STRBUF_INIT; - char *ptr; - - filename = git_path("FETCH_HEAD"); - fp = fopen(filename, "r"); - if (!fp) - die_errno(_("could not open '%s' for reading"), - filename); - strbuf_getline(&line, fp, '\n'); - fclose(fp); - ptr = strstr(line.buf, "\tnot-for-merge\t"); - if (ptr) - strbuf_remove(&line, ptr-line.buf+1, 13); - strbuf_addbuf(msg, &line); - strbuf_release(&line); - goto cleanup; + strbuf_release(&truname); } if (remote_head->util) { @@ -557,7 +536,7 @@ static void parse_branch_merge_options(char *bmo) if (argc < 0) die(_("Bad branch.%s.mergeoptions string: %s"), branch, split_cmdline_strerror(argc)); - argv = xrealloc(argv, sizeof(*argv) * (argc + 2)); + REALLOC_ARRAY(argv, argc + 2); memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); argc++; argv[0] = "branch.*.mergeoptions"; @@ -657,22 +636,18 @@ 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)); + static struct lock_file lock; - 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); + rollback_lock_file(&lock); if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { 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,14 +675,13 @@ 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); + rollback_lock_file(&lock); return clean ? 0 : 1; } else { return try_merge_command(strategy, xopts_nr, xopts, @@ -843,16 +817,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"); @@ -887,28 +859,20 @@ static int finish_automerge(struct commit *head, return 0; } -static int suggest_conflicts(int renormalizing) +static int suggest_conflicts(void) { const char *filename; FILE *fp; - int pos; + struct strbuf msgbuf = STRBUF_INIT; filename = git_path("MERGE_MSG"); fp = fopen(filename, "a"); if (!fp) die_errno(_("Could not open '%s' for writing"), filename); - fprintf(fp, "\nConflicts:\n"); - for (pos = 0; pos < active_nr; pos++) { - const struct cache_entry *ce = active_cache[pos]; - - if (ce_stage(ce)) { - fprintf(fp, "\t%s\n", ce->name); - while (pos + 1 < active_nr && - !strcmp(ce->name, - active_cache[pos + 1]->name)) - pos++; - } - } + + append_conflicts_hint(&msgbuf); + fputs(msgbuf.buf, fp); + strbuf_release(&msgbuf); fclose(fp); rerere(allow_rerere_auto); printf(_("Automatic merge failed; " @@ -1051,28 +1015,24 @@ static int default_edit_option(void) st_stdin.st_mode == st_stdout.st_mode); } -static struct commit_list *collect_parents(struct commit *head_commit, - int *head_subsumed, - int argc, const char **argv) +static struct commit_list *reduce_parents(struct commit *head_commit, + int *head_subsumed, + struct commit_list *remoteheads) { - int i; - struct commit_list *remoteheads = NULL, *parents, *next; - struct commit_list **remotes = &remoteheads; + struct commit_list *parents, *next, **remotes = &remoteheads; - if (head_commit) - remotes = &commit_list_insert(head_commit, remotes)->next; - for (i = 0; i < argc; i++) { - struct commit *commit = get_merge_parent(argv[i]); - if (!commit) - help_unknown_ref(argv[i], "merge", - "not something we can merge"); - remotes = &commit_list_insert(commit, remotes)->next; - } - *remotes = NULL; + /* + * Is the current HEAD reachable from another commit being + * merged? If so we do not want to record it as a parent of + * the resulting merge, unless --no-ff is given. We will flip + * this variable to 0 when we find HEAD among the independent + * tips being merged. + */ + *head_subsumed = 1; + /* Find what parents to record by checking independent ones. */ parents = reduce_heads(remoteheads); - *head_subsumed = 1; /* we will flip this to 0 when we find it */ for (remoteheads = NULL, remotes = &remoteheads; parents; parents = next) { @@ -1082,7 +1042,119 @@ static struct commit_list *collect_parents(struct commit *head_commit, *head_subsumed = 0; else remotes = &commit_list_insert(commit, remotes)->next; + free(parents); + } + return remoteheads; +} + +static void prepare_merge_message(struct strbuf *merge_names, struct strbuf *merge_msg) +{ + struct fmt_merge_msg_opts opts; + + memset(&opts, 0, sizeof(opts)); + opts.add_title = !have_message; + opts.shortlog_len = shortlog_len; + opts.credit_people = (0 < option_edit); + + fmt_merge_msg(merge_names, merge_msg, &opts); + if (merge_msg->len) + strbuf_setlen(merge_msg, merge_msg->len - 1); +} + +static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge_names) +{ + const char *filename; + int fd, pos, npos; + struct strbuf fetch_head_file = STRBUF_INIT; + + if (!merge_names) + merge_names = &fetch_head_file; + + filename = git_path("FETCH_HEAD"); + fd = open(filename, O_RDONLY); + if (fd < 0) + die_errno(_("could not open '%s' for reading"), filename); + + if (strbuf_read(merge_names, fd, 0) < 0) + die_errno(_("could not read '%s'"), filename); + if (close(fd) < 0) + die_errno(_("could not close '%s'"), filename); + + for (pos = 0; pos < merge_names->len; pos = npos) { + unsigned char sha1[20]; + char *ptr; + struct commit *commit; + + ptr = strchr(merge_names->buf + pos, '\n'); + if (ptr) + npos = ptr - merge_names->buf + 1; + else + npos = merge_names->len; + + if (npos - pos < 40 + 2 || + get_sha1_hex(merge_names->buf + pos, sha1)) + commit = NULL; /* bad */ + else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2)) + continue; /* not-for-merge */ + else { + char saved = merge_names->buf[pos + 40]; + merge_names->buf[pos + 40] = '\0'; + commit = get_merge_parent(merge_names->buf + pos); + merge_names->buf[pos + 40] = saved; + } + if (!commit) { + if (ptr) + *ptr = '\0'; + die("not something we can merge in %s: %s", + filename, merge_names->buf + pos); + } + remotes = &commit_list_insert(commit, remotes)->next; + } + + if (merge_names == &fetch_head_file) + strbuf_release(&fetch_head_file); +} + +static struct commit_list *collect_parents(struct commit *head_commit, + int *head_subsumed, + int argc, const char **argv, + struct strbuf *merge_msg) +{ + int i; + struct commit_list *remoteheads = NULL; + struct commit_list **remotes = &remoteheads; + struct strbuf merge_names = STRBUF_INIT, *autogen = NULL; + + if (merge_msg && (!have_message || shortlog_len)) + autogen = &merge_names; + + if (head_commit) + remotes = &commit_list_insert(head_commit, remotes)->next; + + if (argc == 1 && !strcmp(argv[0], "FETCH_HEAD")) { + handle_fetch_head(remotes, autogen); + remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads); + } else { + for (i = 0; i < argc; i++) { + struct commit *commit = get_merge_parent(argv[i]); + if (!commit) + help_unknown_ref(argv[i], "merge", + "not something we can merge"); + remotes = &commit_list_insert(commit, remotes)->next; + } + remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads); + if (autogen) { + struct commit_list *p; + for (p = remoteheads; p; p = p->next) + merge_name(merge_remote_util(p->item)->name, autogen); + } + } + + if (autogen) { + prepare_merge_message(autogen, merge_msg); + strbuf_release(autogen); } + return remoteheads; } @@ -1108,7 +1180,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. */ - branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag); + branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, &flag); if (branch && starts_with(branch, "refs/heads/")) branch += 11; if (!branch || is_null_sha1(head_sha1)) @@ -1150,14 +1222,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix) */ if (advice_resolve_conflict) die(_("You have not concluded your merge (MERGE_HEAD exists).\n" - "Please, commit your changes before you can merge.")); + "Please, commit your changes before you merge.")); else die(_("You have not concluded your merge (MERGE_HEAD exists).")); } if (file_exists(git_path("CHERRY_PICK_HEAD"))) { if (advice_resolve_conflict) die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" - "Please, commit your changes before you can merge.")); + "Please, commit your changes before you merge.")); else die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).")); } @@ -1172,61 +1244,62 @@ int cmd_merge(int argc, const char **argv, const char *prefix) option_commit = 0; } - if (!abort_current_merge) { - if (!argc) { - if (default_to_upstream) - argc = setup_with_upstream(&argv); - else - die(_("No commit specified and merge.defaultToUpstream not set.")); - } else if (argc == 1 && !strcmp(argv[0], "-")) - argv[0] = "@{-1}"; + if (!argc) { + if (default_to_upstream) + argc = setup_with_upstream(&argv); + else + die(_("No commit specified and merge.defaultToUpstream not set.")); + } else if (argc == 1 && !strcmp(argv[0], "-")) { + argv[0] = "@{-1}"; } + if (!argc) usage_with_options(builtin_merge_usage, builtin_merge_options); - /* - * This could be traditional "merge <msg> HEAD <commit>..." and - * the way we can tell it is to see if the second token is HEAD, - * but some people might have misused the interface and used a - * commit-ish that is the same as HEAD there instead. - * Traditional format never would have "-m" so it is an - * additional safety measure to check for it. - */ - - if (!have_message && head_commit && - is_old_style_invocation(argc, argv, head_commit->object.sha1)) { - strbuf_addstr(&merge_msg, argv[0]); - head_arg = argv[1]; - argv += 2; - argc -= 2; - remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); - } else if (!head_commit) { + if (!head_commit) { struct commit *remote_head; /* * If the merged head is a valid one there is no reason * to forbid "git merge" into a branch yet to be born. * We do the same for "git pull". */ - if (argc != 1) - die(_("Can merge only exactly one commit into " - "empty head")); if (squash) die(_("Squash commit into empty head not supported yet")); if (fast_forward == FF_NO) die(_("Non-fast-forward commit does not make sense into " "an empty head")); - remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); + remoteheads = collect_parents(head_commit, &head_subsumed, + argc, argv, NULL); remote_head = remoteheads->item; if (!remote_head) die(_("%s - not something we can merge"), argv[0]); + if (remoteheads->next) + die(_("Can merge only exactly one commit into empty head")); 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; + } + /* + * This could be traditional "merge <msg> HEAD <commit>..." and + * the way we can tell it is to see if the second token is HEAD, + * but some people might have misused the interface and used a + * commit-ish that is the same as HEAD there instead. + * Traditional format never would have "-m" so it is an + * additional safety measure to check for it. + */ + if (!have_message && + is_old_style_invocation(argc, argv, head_commit->object.sha1)) { + warning("old-style 'git merge <msg> HEAD <commit>' is deprecated."); + strbuf_addstr(&merge_msg, argv[0]); + head_arg = argv[1]; + argv += 2; + argc -= 2; + remoteheads = collect_parents(head_commit, &head_subsumed, + argc, argv, NULL); + } else { /* We are invoked directly as the first-class UI. */ head_arg = "HEAD"; @@ -1235,21 +1308,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * the standard merge summary message to be appended * to the given message. */ - remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); - for (p = remoteheads; p; p = p->next) - merge_name(merge_remote_util(p->item)->name, &merge_names); - - if (!have_message || shortlog_len) { - struct fmt_merge_msg_opts opts; - memset(&opts, 0, sizeof(opts)); - opts.add_title = !have_message; - opts.shortlog_len = shortlog_len; - opts.credit_people = (0 < option_edit); - - fmt_merge_msg(&merge_names, &merge_msg, &opts); - if (merge_msg.len) - strbuf_setlen(&merge_msg, merge_msg.len - 1); - } + remoteheads = collect_parents(head_commit, &head_subsumed, + argc, argv, &merge_msg); } if (!head_commit || !argc) @@ -1282,10 +1342,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); } } @@ -1330,7 +1387,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (!remoteheads) ; /* already up-to-date */ else if (!remoteheads->next) - common = get_merge_bases(head_commit, remoteheads->item, 1); + common = get_merge_bases(head_commit, remoteheads->item); else { struct commit_list *list = remoteheads; commit_list_insert(head_commit, &list); @@ -1339,7 +1396,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. */ @@ -1427,7 +1484,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * merge_bases again, otherwise "git merge HEAD^ * HEAD^^" would be missed. */ - common_one = get_merge_bases(head_commit, j->item, 1); + common_one = get_merge_bases(head_commit, j->item); if (hashcmp(common_one->item->object.sha1, j->item->object.sha1)) { up_to_date = 0; @@ -1560,7 +1617,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) fprintf(stderr, _("Automatic merge went well; " "stopped before committing as requested\n")); else - ret = suggest_conflicts(option_renormalize); + ret = suggest_conflicts(); done: free(branch_to_free); diff --git a/builtin/mv.c b/builtin/mv.c index 180ef99127..d1d43168ae 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -3,8 +3,8 @@ * * Copyright (C) 2006 Johannes Schindelin */ -#include "cache.h" #include "builtin.h" +#include "lockfile.h" #include "dir.h" #include "cache-tree.h" #include "string-list.h" @@ -12,7 +12,7 @@ #include "submodule.h" static const char * const builtin_mv_usage[] = { - N_("git mv [options] <source>... <destination>"), + N_("git mv [<options>] <source>... <destination>"), NULL }; @@ -61,9 +61,49 @@ static const char *add_slash(const char *path) static struct lock_file lock_file; #define SUBMODULE_WITH_GITDIR ((const char *)1) +static void prepare_move_submodule(const char *src, int first, + const char **submodule_gitfile) +{ + struct strbuf submodule_dotgit = STRBUF_INIT; + if (!S_ISGITLINK(active_cache[first]->ce_mode)) + die(_("Directory %s is in index and no submodule?"), src); + if (!is_staging_gitmodules_ok()) + die(_("Please stage your changes to .gitmodules or stash them to proceed")); + strbuf_addf(&submodule_dotgit, "%s/.git", src); + *submodule_gitfile = read_gitfile(submodule_dotgit.buf); + if (*submodule_gitfile) + *submodule_gitfile = xstrdup(*submodule_gitfile); + else + *submodule_gitfile = SUBMODULE_WITH_GITDIR; + strbuf_release(&submodule_dotgit); +} + +static int index_range_of_same_dir(const char *src, int length, + int *first_p, int *last_p) +{ + const char *src_w_slash = add_slash(src); + int first, last, len_w_slash = length + 1; + + first = cache_name_pos(src_w_slash, len_w_slash); + if (first >= 0) + die(_("%.*s is in index"), len_w_slash, src_w_slash); + + first = -1 - first; + for (last = first; last < active_nr; last++) { + const char *path = active_cache[last]->name; + if (strncmp(path, src_w_slash, len_w_slash)) + break; + } + if (src_w_slash != src) + free((char *)src_w_slash); + *first_p = first; + *last_p = last; + return last - first; +} + 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 +125,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")); @@ -108,7 +148,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME); } else { if (argc != 1) - die("destination '%s' is not a directory", dest_path[0]); + die(_("destination '%s' is not a directory"), dest_path[0]); destination = dest_path; } @@ -131,75 +171,36 @@ int cmd_mv(int argc, const char **argv, const char *prefix) && lstat(dst, &st) == 0) bad = _("cannot move directory over file"); else if (src_is_dir) { - int first = cache_name_pos(src, length); - if (first >= 0) { - struct strbuf submodule_dotgit = STRBUF_INIT; - if (!S_ISGITLINK(active_cache[first]->ce_mode)) - die (_("Huh? Directory %s is in index and no submodule?"), src); - if (!is_staging_gitmodules_ok()) - die (_("Please, stage your changes to .gitmodules or stash them to proceed")); - strbuf_addf(&submodule_dotgit, "%s/.git", src); - submodule_gitfile[i] = read_gitfile(submodule_dotgit.buf); - if (submodule_gitfile[i]) - submodule_gitfile[i] = xstrdup(submodule_gitfile[i]); - else - submodule_gitfile[i] = SUBMODULE_WITH_GITDIR; - strbuf_release(&submodule_dotgit); - } else { - const char *src_w_slash = add_slash(src); - int last, len_w_slash = length + 1; + int first = cache_name_pos(src, length), last; - modes[i] = WORKING_DIRECTORY; + if (first >= 0) + prepare_move_submodule(src, first, + submodule_gitfile + i); + else if (index_range_of_same_dir(src, length, + &first, &last) < 1) + bad = _("source directory is empty"); + else { /* last - first >= 1 */ + int j, dst_len, n; - first = cache_name_pos(src_w_slash, len_w_slash); - if (first >= 0) - die (_("Huh? %.*s is in index?"), - len_w_slash, src_w_slash); - - first = -1 - first; - for (last = first; last < active_nr; last++) { - const char *path = active_cache[last]->name; - if (strncmp(path, src_w_slash, len_w_slash)) - break; - } - if (src_w_slash != src) - free((char *)src_w_slash); - - if (last - first < 1) - bad = _("source directory is empty"); - else { - int j, dst_len; - - if (last - first > 0) { - source = xrealloc(source, - (argc + last - first) - * sizeof(char *)); - destination = xrealloc(destination, - (argc + last - first) - * sizeof(char *)); - modes = xrealloc(modes, - (argc + last - first) - * sizeof(enum update_mode)); - submodule_gitfile = xrealloc(submodule_gitfile, - (argc + last - first) - * sizeof(char *)); - } + modes[i] = WORKING_DIRECTORY; + n = argc + last - first; + REALLOC_ARRAY(source, n); + REALLOC_ARRAY(destination, n); + REALLOC_ARRAY(modes, n); + REALLOC_ARRAY(submodule_gitfile, n); - dst = add_slash(dst); - dst_len = strlen(dst); + dst = add_slash(dst); + dst_len = strlen(dst); - for (j = 0; j < last - first; j++) { - const char *path = - active_cache[first + j]->name; - source[argc + j] = path; - destination[argc + j] = - prefix_path(dst, dst_len, - path + length + 1); - modes[argc + j] = INDEX; - submodule_gitfile[argc + j] = NULL; - } - argc += last - first; + for (j = 0; j < last - first; j++) { + const char *path = active_cache[first + j]->name; + source[argc + j] = path; + destination[argc + j] = + prefix_path(dst, dst_len, path + length + 1); + modes[argc + j] = INDEX; + submodule_gitfile[argc + j] = NULL; } + argc += last - first; } } else if (cache_name_pos(src, length) < 0) bad = _("not under version control"); @@ -225,24 +226,22 @@ int cmd_mv(int argc, const char **argv, const char *prefix) else string_list_insert(&src_for_dst, dst); - if (bad) { - if (ignore_errors) { - if (--argc > 0) { - memmove(source + i, source + i + 1, - (argc - i) * sizeof(char *)); - memmove(destination + i, - destination + i + 1, - (argc - i) * sizeof(char *)); - memmove(modes + i, modes + i + 1, - (argc - i) * sizeof(enum update_mode)); - memmove(submodule_gitfile + i, - submodule_gitfile + i + 1, - (argc - i) * sizeof(char *)); - i--; - } - } else - die (_("%s, source=%s, destination=%s"), - bad, src, dst); + if (!bad) + continue; + if (!ignore_errors) + die(_("%s, source=%s, destination=%s"), + bad, src, dst); + if (--argc > 0) { + int n = argc - i; + memmove(source + i, source + i + 1, + n * sizeof(char *)); + memmove(destination + i, destination + i + 1, + n * sizeof(char *)); + memmove(modes + i, modes + i + 1, + n * sizeof(enum update_mode)); + memmove(submodule_gitfile + i, submodule_gitfile + i + 1, + n * sizeof(char *)); + i--; } } @@ -254,7 +253,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) printf(_("Renaming %s to %s\n"), src, dst); if (!show_only && mode != INDEX) { if (rename(src, dst) < 0 && !ignore_errors) - die_errno (_("renaming '%s' failed"), src); + die_errno(_("renaming '%s' failed"), src); if (submodule_gitfile[i]) { if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR) connect_work_tree_and_git_dir(dst, submodule_gitfile[i]); @@ -275,11 +274,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (gitmodules_modified) stage_updated_gitmodules(); - if (active_cache_changed) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) - die(_("Unable to write new index file")); - } + if (active_cache_changed && + write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + die(_("Unable to write new index file")); return 0; } diff --git a/builtin/name-rev.c b/builtin/name-rev.c index c824d4ec5f..9736d4452f 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); @@ -255,9 +252,9 @@ static void show_name(const struct object *obj, } static char const * const name_rev_usage[] = { - N_("git name-rev [options] <commit>..."), - N_("git name-rev [options] --all"), - N_("git name-rev [options] --stdin"), + N_("git name-rev [<options>] <commit>..."), + N_("git name-rev [<options>] --all"), + N_("git name-rev [<options>] --stdin"), NULL }; diff --git a/builtin/notes.c b/builtin/notes.c index 39c8573cde..63f95fc554 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -21,18 +21,18 @@ #include "notes-utils.h" static const char * const git_notes_usage[] = { - N_("git notes [--ref <notes_ref>] [list [<object>]]"), - N_("git notes [--ref <notes_ref>] add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"), - N_("git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>"), - N_("git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"), - N_("git notes [--ref <notes_ref>] edit [<object>]"), - N_("git notes [--ref <notes_ref>] show [<object>]"), - N_("git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>"), + N_("git notes [--ref <notes-ref>] [list [<object>]]"), + N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"), + N_("git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>"), + N_("git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"), + N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"), + N_("git notes [--ref <notes-ref>] show [<object>]"), + N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"), N_("git notes merge --commit [-v | -q]"), N_("git notes merge --abort [-v | -q]"), - N_("git notes [--ref <notes_ref>] remove [<object>...]"), - N_("git notes [--ref <notes_ref>] prune [-n | -v]"), - N_("git notes [--ref <notes_ref>] get-ref"), + N_("git notes [--ref <notes-ref>] remove [<object>...]"), + N_("git notes [--ref <notes-ref>] prune [-n | -v]"), + N_("git notes [--ref <notes-ref>] get-ref"), NULL }; @@ -68,7 +68,7 @@ static const char * const git_notes_show_usage[] = { }; static const char * const git_notes_merge_usage[] = { - N_("git notes merge [<options>] <notes_ref>"), + N_("git notes merge [<options>] <notes-ref>"), N_("git notes merge --commit [<options>]"), N_("git notes merge --abort [<options>]"), NULL @@ -92,12 +92,22 @@ static const char * const git_notes_get_ref_usage[] = { static const char note_template[] = "\nWrite/edit the notes for the following object:\n"; -struct msg_arg { +struct note_data { int given; int use_editor; + char *edit_path; struct strbuf buf; }; +static void free_note_data(struct note_data *d) +{ + if (d->edit_path) { + unlink_or_warn(d->edit_path); + free(d->edit_path); + } + strbuf_release(&d->buf); +} + static int list_each_note(const unsigned char *object_sha1, const unsigned char *note_sha1, char *note_path, void *cb_data) @@ -106,7 +116,7 @@ static int list_each_note(const unsigned char *object_sha1, return 0; } -static void write_note_data(int fd, const unsigned char *sha1) +static void copy_obj_to_fd(int fd, const unsigned char *sha1) { unsigned long size; enum object_type type; @@ -122,12 +132,11 @@ static void write_commented_object(int fd, const unsigned char *object) { const char *show_args[5] = {"show", "--stat", "--no-notes", sha1_to_hex(object), NULL}; - struct child_process show; + struct child_process show = CHILD_PROCESS_INIT; struct strbuf buf = STRBUF_INIT; struct strbuf cbuf = STRBUF_INIT; /* Invoke "git show --stat --no-notes $object" */ - memset(&show, 0, sizeof(show)); show.argv = show_args; show.no_stdin = 1; show.out = -1; @@ -150,26 +159,23 @@ static void write_commented_object(int fd, const unsigned char *object) sha1_to_hex(object)); } -static void create_note(const unsigned char *object, struct msg_arg *msg, - int append_only, const unsigned char *prev, - unsigned char *result) +static void prepare_note_data(const unsigned char *object, struct note_data *d, + const unsigned char *old_note) { - char *path = NULL; - - if (msg->use_editor || !msg->given) { + if (d->use_editor || !d->given) { int fd; struct strbuf buf = STRBUF_INIT; /* write the template message before editing: */ - path = git_pathdup("NOTES_EDITMSG"); - fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); + d->edit_path = git_pathdup("NOTES_EDITMSG"); + fd = open(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (fd < 0) - die_errno(_("could not create file '%s'"), path); + die_errno(_("could not create file '%s'"), d->edit_path); - if (msg->given) - write_or_die(fd, msg->buf.buf, msg->buf.len); - else if (prev && !append_only) - write_note_data(fd, prev); + if (d->given) + write_or_die(fd, d->buf.buf, d->buf.len); + else if (old_note) + copy_obj_to_fd(fd, old_note); strbuf_addch(&buf, '\n'); strbuf_add_commented_lines(&buf, note_template, strlen(note_template)); @@ -180,94 +186,71 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, close(fd); strbuf_release(&buf); - strbuf_reset(&(msg->buf)); - - if (launch_editor(path, &(msg->buf), NULL)) { - die(_("Please supply the note contents using either -m" \ - " or -F option")); - } - stripspace(&(msg->buf), 1); - } - - if (prev && append_only) { - /* Append buf to previous note contents */ - unsigned long size; - enum object_type type; - char *prev_buf = read_sha1_file(prev, &type, &size); + strbuf_reset(&d->buf); - strbuf_grow(&(msg->buf), size + 1); - if (msg->buf.len && prev_buf && size) - strbuf_insert(&(msg->buf), 0, "\n", 1); - if (prev_buf && size) - strbuf_insert(&(msg->buf), 0, prev_buf, size); - free(prev_buf); - } - - if (!msg->buf.len) { - fprintf(stderr, _("Removing note for object %s\n"), - sha1_to_hex(object)); - hashclr(result); - } else { - if (write_sha1_file(msg->buf.buf, msg->buf.len, blob_type, result)) { - error(_("unable to write note object")); - if (path) - error(_("The note contents has been left in %s"), - path); - exit(128); + if (launch_editor(d->edit_path, &d->buf, NULL)) { + die(_("Please supply the note contents using either -m or -F option")); } + stripspace(&d->buf, 1); } +} - if (path) { - unlink_or_warn(path); - free(path); +static void write_note_data(struct note_data *d, unsigned char *sha1) +{ + if (write_sha1_file(d->buf.buf, d->buf.len, blob_type, sha1)) { + error(_("unable to write note object")); + if (d->edit_path) + error(_("The note contents have been left in %s"), + d->edit_path); + exit(128); } } static int parse_msg_arg(const struct option *opt, const char *arg, int unset) { - struct msg_arg *msg = opt->value; + struct note_data *d = opt->value; - strbuf_grow(&(msg->buf), strlen(arg) + 2); - if (msg->buf.len) - strbuf_addch(&(msg->buf), '\n'); - strbuf_addstr(&(msg->buf), arg); - stripspace(&(msg->buf), 0); + strbuf_grow(&d->buf, strlen(arg) + 2); + if (d->buf.len) + strbuf_addch(&d->buf, '\n'); + strbuf_addstr(&d->buf, arg); + stripspace(&d->buf, 0); - msg->given = 1; + d->given = 1; return 0; } static int parse_file_arg(const struct option *opt, const char *arg, int unset) { - struct msg_arg *msg = opt->value; + struct note_data *d = opt->value; - if (msg->buf.len) - strbuf_addch(&(msg->buf), '\n'); + if (d->buf.len) + strbuf_addch(&d->buf, '\n'); if (!strcmp(arg, "-")) { - if (strbuf_read(&(msg->buf), 0, 1024) < 0) + if (strbuf_read(&d->buf, 0, 1024) < 0) die_errno(_("cannot read '%s'"), arg); - } else if (strbuf_read_file(&(msg->buf), arg, 1024) < 0) + } else if (strbuf_read_file(&d->buf, arg, 1024) < 0) die_errno(_("could not open or read '%s'"), arg); - stripspace(&(msg->buf), 0); + stripspace(&d->buf, 0); - msg->given = 1; + d->given = 1; return 0; } static int parse_reuse_arg(const struct option *opt, const char *arg, int unset) { - struct msg_arg *msg = opt->value; + struct note_data *d = opt->value; char *buf; unsigned char object[20]; enum object_type type; unsigned long len; - if (msg->buf.len) - strbuf_addch(&(msg->buf), '\n'); + if (d->buf.len) + strbuf_addch(&d->buf, '\n'); if (get_sha1(arg, object)) die(_("Failed to resolve '%s' as a valid ref."), arg); - if (!(buf = read_sha1_file(object, &type, &len)) || !len) { + if (!(buf = read_sha1_file(object, &type, &len))) { free(buf); die(_("Failed to read object '%s'."), arg); } @@ -275,17 +258,17 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset) free(buf); die(_("Cannot read note data from non-blob object '%s'."), arg); } - strbuf_add(&(msg->buf), buf, len); + strbuf_add(&d->buf, buf, len); free(buf); - msg->given = 1; + d->given = 1; return 0; } static int parse_reedit_arg(const struct option *opt, const char *arg, int unset) { - struct msg_arg *msg = opt->value; - msg->use_editor = 1; + struct note_data *d = opt->value; + d->use_editor = 1; return parse_reuse_arg(opt, arg, unset); } @@ -398,26 +381,27 @@ static int append_edit(int argc, const char **argv, const char *prefix); static int add(int argc, const char **argv, const char *prefix) { - int retval = 0, force = 0; + int force = 0, allow_empty = 0; const char *object_ref; struct notes_tree *t; unsigned char object[20], new_note[20]; - char logmsg[100]; const unsigned char *note; - struct msg_arg msg = { 0, 0, STRBUF_INIT }; + struct note_data d = { 0, 0, NULL, STRBUF_INIT }; struct option options[] = { - { OPTION_CALLBACK, 'm', "message", &msg, N_("message"), + { OPTION_CALLBACK, 'm', "message", &d, N_("message"), N_("note contents as a string"), PARSE_OPT_NONEG, parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &msg, N_("file"), + { OPTION_CALLBACK, 'F', "file", &d, N_("file"), N_("note contents in a file"), PARSE_OPT_NONEG, parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &msg, N_("object"), + { OPTION_CALLBACK, 'c', "reedit-message", &d, N_("object"), N_("reuse and edit specified note object"), PARSE_OPT_NONEG, parse_reedit_arg}, - { OPTION_CALLBACK, 'C', "reuse-message", &msg, N_("object"), + { OPTION_CALLBACK, 'C', "reuse-message", &d, N_("object"), N_("reuse specified note object"), PARSE_OPT_NONEG, parse_reuse_arg}, + OPT_BOOL(0, "allow-empty", &allow_empty, + N_("allow storing empty note")), OPT__FORCE(&force, N_("replace existing notes")), OPT_END() }; @@ -440,41 +424,44 @@ static int add(int argc, const char **argv, const char *prefix) if (note) { if (!force) { - if (!msg.given) { - /* - * Redirect to "edit" subcommand. - * - * We only end up here if none of -m/-F/-c/-C - * or -f are given. The original args are - * therefore still in argv[0-1]. - */ - argv[0] = "edit"; - free_notes(t); - return append_edit(argc, argv, prefix); + free_notes(t); + if (d.given) { + free_note_data(&d); + return error(_("Cannot add notes. " + "Found existing notes for object %s. " + "Use '-f' to overwrite existing notes"), + sha1_to_hex(object)); } - retval = error(_("Cannot add notes. Found existing notes " - "for object %s. Use '-f' to overwrite " - "existing notes"), sha1_to_hex(object)); - goto out; + /* + * Redirect to "edit" subcommand. + * + * We only end up here if none of -m/-F/-c/-C or -f are + * given. The original args are therefore still in + * argv[0-1]. + */ + argv[0] = "edit"; + return append_edit(argc, argv, prefix); } fprintf(stderr, _("Overwriting existing notes for object %s\n"), sha1_to_hex(object)); } - create_note(object, &msg, 0, note, new_note); - - if (is_null_sha1(new_note)) + prepare_note_data(object, &d, note); + if (d.buf.len || allow_empty) { + write_note_data(&d, new_note); + if (add_note(t, object, new_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); + commit_notes(t, "Notes added by 'git notes add'"); + } else { + fprintf(stderr, _("Removing note for object %s\n"), + sha1_to_hex(object)); remove_note(t, object); - else if (add_note(t, object, new_note, combine_notes_overwrite)) - die("BUG: combine_notes_overwrite failed"); + commit_notes(t, "Notes removed by 'git notes add'"); + } - snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'", - is_null_sha1(new_note) ? "removed" : "added", "add"); - commit_notes(t, logmsg); -out: + free_note_data(&d); free_notes(t); - strbuf_release(&(msg.buf)); - return retval; + return 0; } static int copy(int argc, const char **argv, const char *prefix) @@ -555,26 +542,29 @@ out: static int append_edit(int argc, const char **argv, const char *prefix) { + int allow_empty = 0; const char *object_ref; struct notes_tree *t; unsigned char object[20], new_note[20]; const unsigned char *note; char logmsg[100]; const char * const *usage; - struct msg_arg msg = { 0, 0, STRBUF_INIT }; + struct note_data d = { 0, 0, NULL, STRBUF_INIT }; struct option options[] = { - { OPTION_CALLBACK, 'm', "message", &msg, N_("message"), + { OPTION_CALLBACK, 'm', "message", &d, N_("message"), N_("note contents as a string"), PARSE_OPT_NONEG, parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &msg, N_("file"), + { OPTION_CALLBACK, 'F', "file", &d, N_("file"), N_("note contents in a file"), PARSE_OPT_NONEG, parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &msg, N_("object"), + { OPTION_CALLBACK, 'c', "reedit-message", &d, N_("object"), N_("reuse and edit specified note object"), PARSE_OPT_NONEG, parse_reedit_arg}, - { OPTION_CALLBACK, 'C', "reuse-message", &msg, N_("object"), + { OPTION_CALLBACK, 'C', "reuse-message", &d, N_("object"), N_("reuse specified note object"), PARSE_OPT_NONEG, parse_reuse_arg}, + OPT_BOOL(0, "allow-empty", &allow_empty, + N_("allow storing empty note")), OPT_END() }; int edit = !strcmp(argv[0], "edit"); @@ -588,7 +578,7 @@ static int append_edit(int argc, const char **argv, const char *prefix) usage_with_options(usage, options); } - if (msg.given && edit) + if (d.given && edit) fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated " "for the 'edit' subcommand.\n" "Please use 'git notes add -f -m/-F/-c/-C' instead.\n")); @@ -601,18 +591,39 @@ static int append_edit(int argc, const char **argv, const char *prefix) t = init_notes_check(argv[0]); note = get_note(t, object); - create_note(object, &msg, !edit, note, new_note); + prepare_note_data(object, &d, edit ? note : NULL); - if (is_null_sha1(new_note)) - remove_note(t, object); - else if (add_note(t, object, new_note, combine_notes_overwrite)) - die("BUG: combine_notes_overwrite failed"); + if (note && !edit) { + /* Append buf to previous note contents */ + unsigned long size; + enum object_type type; + char *prev_buf = read_sha1_file(note, &type, &size); + + strbuf_grow(&d.buf, size + 1); + if (d.buf.len && prev_buf && size) + strbuf_insert(&d.buf, 0, "\n", 1); + if (prev_buf && size) + strbuf_insert(&d.buf, 0, prev_buf, size); + free(prev_buf); + } - snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'", - is_null_sha1(new_note) ? "removed" : "added", argv[0]); + if (d.buf.len || allow_empty) { + write_note_data(&d, new_note); + if (add_note(t, object, new_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); + snprintf(logmsg, sizeof(logmsg), "Notes added by 'git notes %s'", + argv[0]); + } else { + fprintf(stderr, _("Removing note for object %s\n"), + sha1_to_hex(object)); + remove_note(t, object); + snprintf(logmsg, sizeof(logmsg), "Notes removed by 'git notes %s'", + argv[0]); + } commit_notes(t, logmsg); + + free_note_data(&d); free_notes(t); - strbuf_release(&(msg.buf)); return 0; } @@ -703,7 +714,7 @@ static int merge_commit(struct notes_merge_options *o) init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); o->local_ref = local_ref_to_free = - resolve_refdup("NOTES_MERGE_REF", sha1, 0, NULL); + resolve_refdup("NOTES_MERGE_REF", 0, sha1, NULL); if (!o->local_ref) die("Failed to resolve NOTES_MERGE_REF"); @@ -717,7 +728,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 +823,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)", @@ -940,7 +951,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) const char *override_notes_ref = NULL; struct option options[] = { OPT_STRING(0, "ref", &override_notes_ref, N_("notes-ref"), - N_("use notes from <notes_ref>")), + N_("use notes from <notes-ref>")), OPT_END() }; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index de36c60ca1..c067107a6a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -20,6 +20,9 @@ #include "streaming.h" #include "thread-utils.h" #include "pack-bitmap.h" +#include "reachable.h" +#include "sha1-array.h" +#include "argv-array.h" static const char *pack_usage[] = { N_("git pack-objects --stdout [options...] [< ref-list | < object-list]"), @@ -89,8 +92,7 @@ static void index_commit_for_bitmap(struct commit *commit) { if (indexed_commits_nr >= indexed_commits_alloc) { indexed_commits_alloc = (indexed_commits_alloc + 32) * 2; - indexed_commits = xrealloc(indexed_commits, - indexed_commits_alloc * sizeof(struct commit *)); + REALLOC_ARRAY(indexed_commits, indexed_commits_alloc); } indexed_commits[indexed_commits_nr++] = commit; @@ -123,7 +125,6 @@ static unsigned long do_compress(void **pptr, unsigned long size) void *in, *out; unsigned long maxsize; - memset(&stream, 0, sizeof(stream)); git_deflate_init(&stream, pack_compression_level); maxsize = git_deflate_bound(&stream, size); @@ -151,7 +152,6 @@ static unsigned long write_large_blob_data(struct git_istream *st, struct sha1fi unsigned char obuf[1024 * 16]; unsigned long olen = 0; - memset(&stream, 0, sizeof(stream)); git_deflate_init(&stream, pack_compression_level); for (;;) { @@ -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) { @@ -960,10 +961,8 @@ static int want_object_in_pack(const unsigned char *sha1, off_t offset = find_pack_entry_one(sha1, p); if (offset) { if (!*found_pack) { - if (!is_pack_valid(p)) { - warning("packfile %s cannot be accessed", p->pack_name); + if (!is_pack_valid(p)) continue; - } *found_offset = offset; *found_pack = p; } @@ -1973,8 +1972,6 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, init_threaded_search(); - if (!delta_search_threads) /* --threads=0 means autodetect */ - delta_search_threads = online_cpus(); if (delta_search_threads <= 1) { find_deltas(list, &list_size, window, depth, processed); cleanup_threaded_search(); @@ -2214,10 +2211,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; @@ -2412,6 +2405,27 @@ static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1) return 0; } +/* + * Store a list of sha1s that are should not be discarded + * because they are either written too recently, or are + * reachable from another object that was. + * + * This is filled by get_object_list. + */ +static struct sha1_array recent_objects; + +static int loosened_object_can_be_discarded(const unsigned char *sha1, + unsigned long mtime) +{ + if (!unpack_unreachable_expiration) + return 0; + if (mtime > unpack_unreachable_expiration) + return 0; + if (sha1_array_lookup(&recent_objects, sha1) >= 0) + return 0; + return 1; +} + static void loosen_unused_packed_objects(struct rev_info *revs) { struct packed_git *p; @@ -2422,17 +2436,14 @@ static void loosen_unused_packed_objects(struct rev_info *revs) if (!p->pack_local || p->pack_keep) continue; - if (unpack_unreachable_expiration && - p->mtime < unpack_unreachable_expiration) - continue; - if (open_pack_index(p)) die("cannot open pack index"); for (i = 0; i < p->num_objects; i++) { sha1 = nth_packed_object_sha1(p, i); if (!packlist_find(&to_pack, sha1, NULL) && - !has_sha1_pack_kept_or_nonlocal(sha1)) + !has_sha1_pack_kept_or_nonlocal(sha1) && + !loosened_object_can_be_discarded(sha1, p->mtime)) if (force_object_loose(sha1, p->mtime)) die("unable to force loose object"); } @@ -2468,6 +2479,19 @@ static int get_object_list_from_bitmap(struct rev_info *revs) return 0; } +static void record_recent_object(struct object *obj, + const struct name_path *path, + const char *last, + void *data) +{ + sha1_array_append(&recent_objects, obj->sha1); +} + +static void record_recent_commit(struct commit *commit, void *data) +{ + sha1_array_append(&recent_objects, commit->object.sha1); +} + static void get_object_list(int ac, const char **av) { struct rev_info revs; @@ -2498,6 +2522,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); @@ -2514,10 +2539,23 @@ static void get_object_list(int ac, const char **av) mark_edges_uninteresting(&revs, show_edge); traverse_commit_list(&revs, show_commit, show_object, NULL); + if (unpack_unreachable_expiration) { + revs.ignore_missing_links = 1; + if (add_unseen_recent_objects_to_traversal(&revs, + unpack_unreachable_expiration)) + die("unable to add recent objects"); + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); + traverse_commit_list(&revs, record_recent_commit, + record_recent_object, NULL); + } + if (keep_unreachable) add_objects_in_unpacked_packs(&revs); if (unpack_unreachable) loosen_unused_packed_objects(&revs); + + sha1_array_clear(&recent_objects); } static int option_parse_index_version(const struct option *opt, @@ -2571,10 +2609,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) { int use_internal_rev_list = 0; int thin = 0; + int shallow = 0; int all_progress_implied = 0; - const char *rp_av[6]; - int rp_ac = 0; + struct argv_array rp = ARGV_ARRAY_INIT; int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0; + int rev_list_index = 0; struct option pack_objects_options[] = { OPT_SET_INT('q', "quiet", &progress, N_("do not show progress meter"), 0), @@ -2621,6 +2660,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) { OPTION_SET_INT, 0, "reflog", &rev_list_reflog, NULL, N_("include objects referred by reflog entries"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, + { OPTION_SET_INT, 0, "indexed-objects", &rev_list_index, NULL, + N_("include objects referred to by the index"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, OPT_BOOL(0, "stdout", &pack_to_stdout, N_("output pack to stdout")), OPT_BOOL(0, "include-tag", &include_tag, @@ -2632,6 +2674,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, option_parse_unpack_unreachable }, OPT_BOOL(0, "thin", &thin, N_("create thin packs")), + OPT_BOOL(0, "shallow", &shallow, + N_("create packs suitable for shallow fetches")), OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep, N_("ignore packs that have companion .keep file")), OPT_INTEGER(0, "compression", &pack_compression_level, @@ -2663,24 +2707,30 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (pack_to_stdout != !base_name || argc) usage_with_options(pack_usage, pack_objects_options); - rp_av[rp_ac++] = "pack-objects"; + argv_array_push(&rp, "pack-objects"); if (thin) { use_internal_rev_list = 1; - rp_av[rp_ac++] = "--objects-edge"; + argv_array_push(&rp, shallow + ? "--objects-edge-aggressive" + : "--objects-edge"); } else - rp_av[rp_ac++] = "--objects"; + argv_array_push(&rp, "--objects"); if (rev_list_all) { use_internal_rev_list = 1; - rp_av[rp_ac++] = "--all"; + argv_array_push(&rp, "--all"); } if (rev_list_reflog) { use_internal_rev_list = 1; - rp_av[rp_ac++] = "--reflog"; + argv_array_push(&rp, "--reflog"); + } + if (rev_list_index) { + use_internal_rev_list = 1; + argv_array_push(&rp, "--indexed-objects"); } if (rev_list_unpacked) { use_internal_rev_list = 1; - rp_av[rp_ac++] = "--unpacked"; + argv_array_push(&rp, "--unpacked"); } if (!reuse_object) @@ -2689,6 +2739,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) pack_compression_level = Z_DEFAULT_COMPRESSION; else if (pack_compression_level < 0 || pack_compression_level > Z_BEST_COMPRESSION) die("bad pack compression level %d", pack_compression_level); + + if (!delta_search_threads) /* --threads=0 means autodetect */ + delta_search_threads = online_cpus(); + #ifdef NO_PTHREADS if (delta_search_threads != 1) warning("no threads support, ignoring --threads"); @@ -2707,6 +2761,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (keep_unreachable && unpack_unreachable) die("--keep-unreachable and --unpack-unreachable are incompatible."); + if (!rev_list_all || !rev_list_reflog || !rev_list_index) + unpack_unreachable_expiration = 0; if (!use_internal_rev_list || !pack_to_stdout || is_repository_shallow()) use_bitmap_index = 0; @@ -2724,8 +2780,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (!use_internal_rev_list) read_object_list_from_stdin(); else { - rp_av[rp_ac] = NULL; - get_object_list(rp_ac, rp_av); + get_object_list(rp.argc, rp.argv); + argv_array_clear(&rp); } cleanup_preferred_base(); if (include_tag && nr_result) diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 649c3aaa93..d0532f66b1 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -11,7 +11,7 @@ #define BLKSIZE 512 static const char pack_redundant_usage[] = -"git pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>"; +"git pack-redundant [--verbose] [--alt-odb] (--all | <filename.pack>...)"; static int load_all_packs, verbose, alt_odb; diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c index b20b1ec4c1..39f9a55d16 100644 --- a/builtin/pack-refs.c +++ b/builtin/pack-refs.c @@ -3,7 +3,7 @@ #include "refs.h" static char const * const pack_refs_usage[] = { - N_("git pack-refs [options]"), + N_("git pack-refs [<options>]"), NULL }; diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 3cfe02d5a5..ba34dac4d2 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, struct object_id *id, struct object_id *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); + memcpy(name, oid_to_hex(id), GIT_SHA1_HEXSZ + 1); + printf("%s %s\n", oid_to_hex(result), name); } 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(struct object_id *result, git_SHA_CTX *ctx) +{ + unsigned char hash[GIT_SHA1_RAWSZ]; + unsigned short carry = 0; + int i; + + git_SHA1_Final(hash, ctx); + git_SHA1_Init(ctx); + /* 20-byte sum, with carry */ + for (i = 0; i < GIT_SHA1_RAWSZ; ++i) { + carry += result->hash[i] + hash[i]; + result->hash[i] = carry; + carry >>= 8; + } +} + +static int get_one_patchid(struct object_id *next_oid, struct object_id *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); + oidclr(result); while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) { char *line = line_buf->buf; @@ -75,7 +93,7 @@ static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct st else if (!memcmp(line, "\\ ", 2) && 12 < strlen(line)) continue; - if (!get_sha1_hex(p, next_sha1)) { + if (!get_oid_hex(p, next_oid)) { found_next = 1; break; } @@ -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); + oidclr(next_oid); + + 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; + struct object_id oid, n, result; int patchlen; struct strbuf line_buf = STRBUF_INIT; - git_SHA1_Init(&ctx); - hashclr(sha1); + oidclr(&oid); while (!feof(stdin)) { - patchlen = get_one_patchid(n, &ctx, &line_buf); - flush_current_id(patchlen, sha1, &ctx); - hashcpy(sha1, n); + patchlen = get_one_patchid(&n, &result, &line_buf, stable); + flush_current_id(patchlen, &oid, &result); + oidcpy(&oid, &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/prune-packed.c b/builtin/prune-packed.c index 6879468c46..7cf900ea07 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -4,69 +4,47 @@ #include "parse-options.h" static const char * const prune_packed_usage[] = { - N_("git prune-packed [-n|--dry-run] [-q|--quiet]"), + N_("git prune-packed [-n | --dry-run] [-q | --quiet]"), NULL }; static struct progress *progress; -static void prune_dir(int i, DIR *dir, struct strbuf *pathname, int opts) +static int prune_subdir(int nr, const char *path, void *data) { - struct dirent *de; - char hex[40]; - int top_len = pathname->len; + int *opts = data; + display_progress(progress, nr + 1); + if (!(*opts & PRUNE_PACKED_DRY_RUN)) + rmdir(path); + return 0; +} + +static int prune_object(const unsigned char *sha1, const char *path, + void *data) +{ + int *opts = data; - sprintf(hex, "%02x", i); - while ((de = readdir(dir)) != NULL) { - unsigned char sha1[20]; - if (strlen(de->d_name) != 38) - continue; - memcpy(hex + 2, de->d_name, 38); - if (get_sha1_hex(hex, sha1)) - continue; - if (!has_sha1_pack(sha1)) - continue; + if (!has_sha1_pack(sha1)) + return 0; - strbuf_add(pathname, de->d_name, 38); - if (opts & PRUNE_PACKED_DRY_RUN) - printf("rm -f %s\n", pathname->buf); - else - unlink_or_warn(pathname->buf); - display_progress(progress, i + 1); - strbuf_setlen(pathname, top_len); - } + if (*opts & PRUNE_PACKED_DRY_RUN) + printf("rm -f %s\n", path); + else + unlink_or_warn(path); + return 0; } void prune_packed_objects(int opts) { - int i; - const char *dir = get_object_directory(); - struct strbuf pathname = STRBUF_INIT; - int top_len; - - strbuf_addstr(&pathname, dir); if (opts & PRUNE_PACKED_VERBOSE) progress = start_progress_delay(_("Removing duplicate objects"), 256, 95, 2); - if (pathname.len && pathname.buf[pathname.len - 1] != '/') - strbuf_addch(&pathname, '/'); - - top_len = pathname.len; - for (i = 0; i < 256; i++) { - DIR *d; + for_each_loose_file_in_objdir(get_object_directory(), + prune_object, NULL, prune_subdir, &opts); - display_progress(progress, i + 1); - strbuf_setlen(&pathname, top_len); - strbuf_addf(&pathname, "%02x/", i); - d = opendir(pathname.buf); - if (!d) - continue; - prune_dir(i, d, &pathname, opts); - closedir(d); - strbuf_setlen(&pathname, top_len + 2); - rmdir(pathname.buf); - } + /* Ensure we show 100% before finishing progress */ + display_progress(progress, 256); stop_progress(&progress); } diff --git a/builtin/prune.c b/builtin/prune.c index 144a3bdb33..0c73246c72 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -31,11 +31,23 @@ static int prune_tmp_file(const char *fullpath) return 0; } -static int prune_object(const char *fullpath, const unsigned char *sha1) +static int prune_object(const unsigned char *sha1, const char *fullpath, + void *data) { struct stat st; - if (lstat(fullpath, &st)) - return error("Could not stat '%s'", fullpath); + + /* + * Do we know about this object? + * It must have been reachable + */ + if (lookup_object(sha1)) + return 0; + + if (lstat(fullpath, &st)) { + /* report errors, but do not stop pruning */ + error("Could not stat '%s'", fullpath); + return 0; + } if (st.st_mtime > expire) return 0; if (show_only || verbose) { @@ -48,68 +60,109 @@ static int prune_object(const char *fullpath, const unsigned char *sha1) return 0; } -static int prune_dir(int i, struct strbuf *path) +static int prune_cruft(const char *basename, const char *path, void *data) { - size_t baselen = path->len; - DIR *dir = opendir(path->buf); - struct dirent *de; + if (starts_with(basename, "tmp_obj_")) + prune_tmp_file(path); + else + fprintf(stderr, "bad sha1 file: %s\n", path); + return 0; +} - if (!dir) - return 0; +static int prune_subdir(int nr, const char *path, void *data) +{ + if (!show_only) + rmdir(path); + return 0; +} - while ((de = readdir(dir)) != NULL) { - char name[100]; - unsigned char sha1[20]; +static int prune_worktree(const char *id, struct strbuf *reason) +{ + struct stat st; + char *path; + int fd, len; - if (is_dot_or_dotdot(de->d_name)) - continue; - if (strlen(de->d_name) == 38) { - sprintf(name, "%02x", i); - memcpy(name+2, de->d_name, 39); - if (get_sha1_hex(name, sha1) < 0) - break; - - /* - * Do we know about this object? - * It must have been reachable - */ - if (lookup_object(sha1)) - continue; - - strbuf_addf(path, "/%s", de->d_name); - prune_object(path->buf, sha1); - strbuf_setlen(path, baselen); - continue; - } - if (starts_with(de->d_name, "tmp_obj_")) { - strbuf_addf(path, "/%s", de->d_name); - prune_tmp_file(path->buf); - strbuf_setlen(path, baselen); - continue; + if (!is_directory(git_path("worktrees/%s", id))) { + strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id); + return 1; + } + if (file_exists(git_path("worktrees/%s/locked", id))) + return 0; + if (stat(git_path("worktrees/%s/gitdir", id), &st)) { + strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id); + return 1; + } + fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); + if (fd < 0) { + strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), + id, strerror(errno)); + return 1; + } + len = st.st_size; + path = xmalloc(len + 1); + read_in_full(fd, path, len); + close(fd); + while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) + len--; + if (!len) { + strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id); + free(path); + return 1; + } + path[len] = '\0'; + if (!file_exists(path)) { + struct stat st_link; + free(path); + /* + * the repo is moved manually and has not been + * accessed since? + */ + if (!stat(git_path("worktrees/%s/link", id), &st_link) && + st_link.st_nlink > 1) + return 0; + if (st.st_mtime <= expire) { + strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id); + return 1; + } else { + return 0; } - fprintf(stderr, "bad sha1 file: %s/%s\n", path->buf, de->d_name); } - closedir(dir); - if (!show_only) - rmdir(path->buf); + free(path); return 0; } -static void prune_object_dir(const char *path) +static void prune_worktrees(void) { - struct strbuf buf = STRBUF_INIT; - size_t baselen; - int i; - - strbuf_addstr(&buf, path); - strbuf_addch(&buf, '/'); - baselen = buf.len; - - for (i = 0; i < 256; i++) { - strbuf_addf(&buf, "%02x", i); - prune_dir(i, &buf); - strbuf_setlen(&buf, baselen); + struct strbuf reason = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; + DIR *dir = opendir(git_path("worktrees")); + struct dirent *d; + int ret; + if (!dir) + return; + while ((d = readdir(dir)) != NULL) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + strbuf_reset(&reason); + if (!prune_worktree(d->d_name, &reason)) + continue; + if (show_only || verbose) + printf("%s\n", reason.buf); + if (show_only) + continue; + strbuf_reset(&path); + strbuf_addstr(&path, git_path("worktrees/%s", d->d_name)); + ret = remove_dir_recursively(&path, 0); + if (ret < 0 && errno == ENOTDIR) + ret = unlink(path.buf); + if (ret) + error(_("failed to remove: %s"), strerror(errno)); } + closedir(dir); + if (!show_only) + rmdir(git_path("worktrees")); + strbuf_release(&reason); + strbuf_release(&path); } /* @@ -138,10 +191,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct progress *progress = NULL; + int do_prune_worktrees = 0; const struct option options[] = { OPT__DRY_RUN(&show_only, N_("do not remove, show only")), OPT__VERBOSE(&verbose, N_("report pruned objects")), OPT_BOOL(0, "progress", &show_progress, N_("show progress")), + OPT_BOOL(0, "worktrees", &do_prune_worktrees, N_("prune .git/worktrees")), OPT_EXPIRY_DATE(0, "expire", &expire, N_("expire objects older than <time>")), OPT_END() @@ -151,9 +206,18 @@ int cmd_prune(int argc, const char **argv, const char *prefix) expire = ULONG_MAX; save_commit_buffer = 0; check_replace_refs = 0; + ref_paranoia = 1; init_revisions(&revs, prefix); argc = parse_options(argc, argv, prefix, options, prune_usage, 0); + + if (do_prune_worktrees) { + if (argc) + die(_("--worktrees does not take extra arguments")); + prune_worktrees(); + return 0; + } + while (argc--) { unsigned char sha1[20]; const char *name = *argv++; @@ -171,9 +235,10 @@ int cmd_prune(int argc, const char **argv, const char *prefix) if (show_progress) progress = start_progress_delay(_("Checking connectivity"), 0, 0, 2); - mark_reachable_objects(&revs, 1, progress); + mark_reachable_objects(&revs, 1, expire, progress); stop_progress(&progress); - prune_object_dir(get_object_directory()); + for_each_loose_file_in_objdir(get_object_directory(), prune_object, + prune_cruft, prune_subdir, NULL); prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0); remove_temporary_files(get_object_directory()); diff --git a/builtin/push.c b/builtin/push.c index f8dfea41e1..57c138bd7b 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. @@ -162,7 +161,7 @@ static const char message_detached_head_die[] = " git push %s HEAD:<name-of-remote-branch>\n"); static void setup_push_upstream(struct remote *remote, struct branch *branch, - int triangular) + int triangular, int simple) { struct strbuf refspec = STRBUF_INIT; @@ -185,7 +184,7 @@ static void setup_push_upstream(struct remote *remote, struct branch *branch, "to update which remote branch."), remote->name, branch->name); - if (push_default == PUSH_DEFAULT_SIMPLE) { + if (simple) { /* Additional safety */ if (strcmp(branch->refname, branch->merge[0]->src)) die_push_simple(branch, remote); @@ -258,11 +257,11 @@ static void setup_default_push_refspecs(struct remote *remote) if (triangular) setup_push_current(remote, branch); else - setup_push_upstream(remote, branch, triangular); + setup_push_upstream(remote, branch, triangular, 1); break; case PUSH_DEFAULT_UPSTREAM: - setup_push_upstream(remote, branch, triangular); + setup_push_upstream(remote, branch, triangular, 0); break; case PUSH_DEFAULT_CURRENT: @@ -472,6 +471,26 @@ static int option_parse_recurse_submodules(const struct option *opt, return 0; } +static int git_push_config(const char *k, const char *v, void *cb) +{ + int *flags = cb; + int status; + + status = git_gpg_config(k, v, NULL); + if (status) + return status; + + if (!strcmp(k, "push.followtags")) { + if (git_config_bool(k, v)) + *flags |= TRANSPORT_PUSH_FOLLOW_TAGS; + else + *flags &= ~TRANSPORT_PUSH_FOLLOW_TAGS; + return 0; + } + + return git_default_config(k, v, NULL); +} + int cmd_push(int argc, const char **argv, const char *prefix) { int flags = 0; @@ -493,7 +512,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) 0, CAS_OPT_NAME, &cas, N_("refname>:<expect"), N_("require old value of ref to be at this value"), PARSE_OPT_OPTARG, parseopt_push_cas_option }, - { OPTION_CALLBACK, 0, "recurse-submodules", &flags, N_("check"), + { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check|on-demand", N_("control recursive pushing of submodules"), PARSE_OPT_OPTARG, option_parse_recurse_submodules }, OPT_BOOL( 0 , "thin", &thin, N_("use thin pack")), @@ -507,11 +526,13 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK), OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"), TRANSPORT_PUSH_FOLLOW_TAGS), + OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT), + OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC), OPT_END() }; packet_trace_identity("push"); - git_config(git_default_config, NULL); + git_config(git_push_config, &flags); argc = parse_options(argc, argv, prefix, options, push_usage, 0); if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR)))) diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 0d7ef847a7..43b47f72f1 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -5,6 +5,7 @@ */ #include "cache.h" +#include "lockfile.h" #include "object.h" #include "tree.h" #include "tree-walk.h" @@ -99,7 +100,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 +150,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 +241,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..d2ec52bca9 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "lockfile.h" #include "pack.h" #include "refs.h" #include "pkt-line.h" @@ -15,6 +16,9 @@ #include "connected.h" #include "argv-array.h" #include "version.h" +#include "tag.h" +#include "gpg-interface.h" +#include "sigchain.h" static const char receive_pack_usage[] = "git receive-pack <git-dir>"; @@ -22,7 +26,8 @@ enum deny_action { DENY_UNCONFIGURED, DENY_IGNORE, DENY_WARN, - DENY_REFUSE + DENY_REFUSE, + DENY_UPDATE_INSTEAD }; static int deny_deletes; @@ -33,19 +38,38 @@ static int receive_fsck_objects = -1; static int transfer_fsck_objects = -1; static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; +static int advertise_atomic_push = 1; static int unpack_limit = 100; static int report_status; static int use_sideband; +static int use_atomic; static int quiet; static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; static int fix_thin = 1; +static int stateless_rpc; +static const char *service_dir; static const char *head_name; static void *head_name_to_free; static int sent_capabilities; static int shallow_update; static const char *alt_shallow_file; +static struct strbuf push_cert = STRBUF_INIT; +static unsigned char push_cert_sha1[20]; +static struct signature_check sigcheck; +static const char *push_cert_nonce; +static const char *cert_nonce_seed; + +static const char *NONCE_UNSOLICITED = "UNSOLICITED"; +static const char *NONCE_BAD = "BAD"; +static const char *NONCE_MISSING = "MISSING"; +static const char *NONCE_OK = "OK"; +static const char *NONCE_SLOP = "SLOP"; +static const char *nonce_status; +static long nonce_stamp_slop; +static unsigned long nonce_stamp_slop_limit; +static struct ref_transaction *transaction; static enum deny_action parse_deny_action(const char *var, const char *value) { @@ -56,6 +80,8 @@ static enum deny_action parse_deny_action(const char *var, const char *value) return DENY_WARN; if (!strcasecmp(value, "refuse")) return DENY_REFUSE; + if (!strcasecmp(value, "updateinstead")) + return DENY_UPDATE_INSTEAD; } if (git_config_bool(var, value)) return DENY_REFUSE; @@ -129,6 +155,19 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.certnonceseed") == 0) + return git_config_string(&cert_nonce_seed, var, value); + + if (strcmp(var, "receive.certnonceslop") == 0) { + nonce_stamp_slop_limit = git_config_ulong(var, value); + return 0; + } + + if (strcmp(var, "receive.advertiseatomic") == 0) { + advertise_atomic_push = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -137,15 +176,25 @@ static void show_ref(const char *path, const unsigned char *sha1) if (ref_is_hidden(path)) return; - if (sent_capabilities) + if (sent_capabilities) { packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); - else - packet_write(1, "%s %s%c%s%s agent=%s\n", - sha1_to_hex(sha1), path, 0, - " report-status delete-refs side-band-64k quiet", - prefer_ofs_delta ? " ofs-delta" : "", - git_user_agent_sanitized()); - sent_capabilities = 1; + } else { + struct strbuf cap = STRBUF_INIT; + + strbuf_addstr(&cap, + "report-status delete-refs side-band-64k quiet"); + if (advertise_atomic_push) + strbuf_addstr(&cap, " atomic"); + if (prefer_ofs_delta) + strbuf_addstr(&cap, " ofs-delta"); + if (push_cert_nonce) + strbuf_addf(&cap, " push-cert=%s", push_cert_nonce); + strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized()); + packet_write(1, "%s %s%c%s\n", + sha1_to_hex(sha1), path, 0, cap.buf); + strbuf_release(&cap); + sent_capabilities = 1; + } } static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused) @@ -252,10 +301,231 @@ static int copy_to_sideband(int in, int out, void *arg) return 0; } +#define HMAC_BLOCK_SIZE 64 + +static void hmac_sha1(unsigned char *out, + const char *key_in, size_t key_len, + const char *text, size_t text_len) +{ + unsigned char key[HMAC_BLOCK_SIZE]; + unsigned char k_ipad[HMAC_BLOCK_SIZE]; + unsigned char k_opad[HMAC_BLOCK_SIZE]; + int i; + git_SHA_CTX ctx; + + /* RFC 2104 2. (1) */ + memset(key, '\0', HMAC_BLOCK_SIZE); + if (HMAC_BLOCK_SIZE < key_len) { + git_SHA1_Init(&ctx); + git_SHA1_Update(&ctx, key_in, key_len); + git_SHA1_Final(key, &ctx); + } else { + memcpy(key, key_in, key_len); + } + + /* RFC 2104 2. (2) & (5) */ + for (i = 0; i < sizeof(key); i++) { + k_ipad[i] = key[i] ^ 0x36; + k_opad[i] = key[i] ^ 0x5c; + } + + /* RFC 2104 2. (3) & (4) */ + git_SHA1_Init(&ctx); + git_SHA1_Update(&ctx, k_ipad, sizeof(k_ipad)); + git_SHA1_Update(&ctx, text, text_len); + git_SHA1_Final(out, &ctx); + + /* RFC 2104 2. (6) & (7) */ + git_SHA1_Init(&ctx); + git_SHA1_Update(&ctx, k_opad, sizeof(k_opad)); + git_SHA1_Update(&ctx, out, 20); + git_SHA1_Final(out, &ctx); +} + +static char *prepare_push_cert_nonce(const char *path, unsigned long stamp) +{ + struct strbuf buf = STRBUF_INIT; + unsigned char sha1[20]; + + strbuf_addf(&buf, "%s:%lu", path, stamp); + hmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));; + strbuf_release(&buf); + + /* RFC 2104 5. HMAC-SHA1-80 */ + strbuf_addf(&buf, "%lu-%.*s", stamp, 20, sha1_to_hex(sha1)); + return strbuf_detach(&buf, NULL); +} + +/* + * NEEDSWORK: reuse find_commit_header() from jk/commit-author-parsing + * after dropping "_commit" from its name and possibly moving it out + * of commit.c + */ +static char *find_header(const char *msg, size_t len, const char *key) +{ + int key_len = strlen(key); + const char *line = msg; + + while (line && line < msg + len) { + const char *eol = strchrnul(line, '\n'); + + if ((msg + len <= eol) || line == eol) + return NULL; + if (line + key_len < eol && + !memcmp(line, key, key_len) && line[key_len] == ' ') { + int offset = key_len + 1; + return xmemdupz(line + offset, (eol - line) - offset); + } + line = *eol ? eol + 1 : NULL; + } + return NULL; +} + +static const char *check_nonce(const char *buf, size_t len) +{ + char *nonce = find_header(buf, len, "nonce"); + unsigned long stamp, ostamp; + char *bohmac, *expect = NULL; + const char *retval = NONCE_BAD; + + if (!nonce) { + retval = NONCE_MISSING; + goto leave; + } else if (!push_cert_nonce) { + retval = NONCE_UNSOLICITED; + goto leave; + } else if (!strcmp(push_cert_nonce, nonce)) { + retval = NONCE_OK; + goto leave; + } + + if (!stateless_rpc) { + /* returned nonce MUST match what we gave out earlier */ + retval = NONCE_BAD; + goto leave; + } + + /* + * In stateless mode, we may be receiving a nonce issued by + * another instance of the server that serving the same + * repository, and the timestamps may not match, but the + * nonce-seed and dir should match, so we can recompute and + * report the time slop. + * + * In addition, when a nonce issued by another instance has + * timestamp within receive.certnonceslop seconds, we pretend + * as if we issued that nonce when reporting to the hook. + */ + + /* nonce is concat(<seconds-since-epoch>, "-", <hmac>) */ + if (*nonce <= '0' || '9' < *nonce) { + retval = NONCE_BAD; + goto leave; + } + stamp = strtoul(nonce, &bohmac, 10); + if (bohmac == nonce || bohmac[0] != '-') { + retval = NONCE_BAD; + goto leave; + } + + expect = prepare_push_cert_nonce(service_dir, stamp); + if (strcmp(expect, nonce)) { + /* Not what we would have signed earlier */ + retval = NONCE_BAD; + goto leave; + } + + /* + * By how many seconds is this nonce stale? Negative value + * would mean it was issued by another server with its clock + * skewed in the future. + */ + ostamp = strtoul(push_cert_nonce, NULL, 10); + nonce_stamp_slop = (long)ostamp - (long)stamp; + + if (nonce_stamp_slop_limit && + labs(nonce_stamp_slop) <= nonce_stamp_slop_limit) { + /* + * Pretend as if the received nonce (which passes the + * HMAC check, so it is not a forged by third-party) + * is what we issued. + */ + free((void *)push_cert_nonce); + push_cert_nonce = xstrdup(nonce); + retval = NONCE_OK; + } else { + retval = NONCE_SLOP; + } + +leave: + free(nonce); + free(expect); + return retval; +} + +static void prepare_push_cert_sha1(struct child_process *proc) +{ + static int already_done; + + if (!push_cert.len) + return; + + if (!already_done) { + struct strbuf gpg_output = STRBUF_INIT; + struct strbuf gpg_status = STRBUF_INIT; + int bogs /* beginning_of_gpg_sig */; + + already_done = 1; + if (write_sha1_file(push_cert.buf, push_cert.len, "blob", push_cert_sha1)) + hashclr(push_cert_sha1); + + memset(&sigcheck, '\0', sizeof(sigcheck)); + sigcheck.result = 'N'; + + bogs = parse_signature(push_cert.buf, push_cert.len); + if (verify_signed_buffer(push_cert.buf, bogs, + push_cert.buf + bogs, push_cert.len - bogs, + &gpg_output, &gpg_status) < 0) { + ; /* error running gpg */ + } else { + sigcheck.payload = push_cert.buf; + sigcheck.gpg_output = gpg_output.buf; + sigcheck.gpg_status = gpg_status.buf; + parse_gpg_output(&sigcheck); + } + + strbuf_release(&gpg_output); + strbuf_release(&gpg_status); + nonce_status = check_nonce(push_cert.buf, bogs); + } + if (!is_null_sha1(push_cert_sha1)) { + argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT=%s", + sha1_to_hex(push_cert_sha1)); + argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_SIGNER=%s", + sigcheck.signer ? sigcheck.signer : ""); + argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_KEY=%s", + sigcheck.key ? sigcheck.key : ""); + argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_STATUS=%c", + sigcheck.result); + if (push_cert_nonce) { + argv_array_pushf(&proc->env_array, + "GIT_PUSH_CERT_NONCE=%s", + push_cert_nonce); + argv_array_pushf(&proc->env_array, + "GIT_PUSH_CERT_NONCE_STATUS=%s", + nonce_status); + if (nonce_status == NONCE_SLOP) + argv_array_pushf(&proc->env_array, + "GIT_PUSH_CERT_NONCE_SLOP=%ld", + nonce_stamp_slop); + } + } +} + typedef int (*feed_fn)(void *, const char **, size_t *); static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state) { - struct child_process proc; + struct child_process proc = CHILD_PROCESS_INIT; struct async muxer; const char *argv[2]; int code; @@ -266,7 +536,6 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta argv[1] = NULL; - memset(&proc, 0, sizeof(proc)); proc.argv = argv; proc.in = -1; proc.stdout_to_stderr = 1; @@ -281,6 +550,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta proc.err = muxer.in; } + prepare_push_cert_sha1(&proc); + code = start_command(&proc); if (code) { if (use_sideband) @@ -288,6 +559,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta return code; } + sigchain_push(SIGPIPE, SIG_IGN); + while (1) { const char *buf; size_t n; @@ -299,6 +572,9 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta close(proc.in); if (use_sideband) finish_async(&muxer); + + sigchain_pop(SIGPIPE); + return finish_command(&proc); } @@ -350,7 +626,7 @@ static int run_receive_hook(struct command *commands, const char *hook_name, static int run_update_hook(struct command *cmd) { const char *argv[5]; - struct child_process proc; + struct child_process proc = CHILD_PROCESS_INIT; int code; argv[0] = find_hook("update"); @@ -362,7 +638,6 @@ static int run_update_hook(struct command *cmd) argv[3] = sha1_to_hex(cmd->new_sha1); argv[4] = NULL; - memset(&proc, 0, sizeof(proc)); proc.no_stdin = 1; proc.stdout_to_stderr = 1; proc.err = use_sideband ? -1 : 0; @@ -438,7 +713,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] && @@ -468,14 +743,130 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) return 0; } +/* + * NEEDSWORK: we should consolidate various implementions of "are we + * on an unborn branch?" test into one, and make the unified one more + * robust. !get_sha1() based check used here and elsewhere would not + * allow us to tell an unborn branch from corrupt ref, for example. + * For the purpose of fixing "deploy-to-update does not work when + * pushing into an empty repository" issue, this should suffice for + * now. + */ +static int head_has_history(void) +{ + unsigned char sha1[20]; + + return !get_sha1("HEAD", sha1); +} + +static const char *push_to_deploy(unsigned char *sha1, + struct argv_array *env, + const char *work_tree) +{ + const char *update_refresh[] = { + "update-index", "-q", "--ignore-submodules", "--refresh", NULL + }; + const char *diff_files[] = { + "diff-files", "--quiet", "--ignore-submodules", "--", NULL + }; + const char *diff_index[] = { + "diff-index", "--quiet", "--cached", "--ignore-submodules", + NULL, "--", NULL + }; + const char *read_tree[] = { + "read-tree", "-u", "-m", NULL, NULL + }; + struct child_process child = CHILD_PROCESS_INIT; + + child.argv = update_refresh; + child.env = env->argv; + child.dir = work_tree; + child.no_stdin = 1; + child.stdout_to_stderr = 1; + child.git_cmd = 1; + if (run_command(&child)) + return "Up-to-date check failed"; + + /* run_command() does not clean up completely; reinitialize */ + child_process_init(&child); + child.argv = diff_files; + child.env = env->argv; + child.dir = work_tree; + child.no_stdin = 1; + child.stdout_to_stderr = 1; + child.git_cmd = 1; + if (run_command(&child)) + return "Working directory has unstaged changes"; + + /* diff-index with either HEAD or an empty tree */ + diff_index[4] = head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX; + + child_process_init(&child); + child.argv = diff_index; + child.env = env->argv; + child.no_stdin = 1; + child.no_stdout = 1; + child.stdout_to_stderr = 0; + child.git_cmd = 1; + if (run_command(&child)) + return "Working directory has staged changes"; + + read_tree[3] = sha1_to_hex(sha1); + child_process_init(&child); + child.argv = read_tree; + child.env = env->argv; + child.dir = work_tree; + child.no_stdin = 1; + child.no_stdout = 1; + child.stdout_to_stderr = 0; + child.git_cmd = 1; + if (run_command(&child)) + return "Could not update working tree to new HEAD"; + + return NULL; +} + +static const char *push_to_checkout_hook = "push-to-checkout"; + +static const char *push_to_checkout(unsigned char *sha1, + struct argv_array *env, + const char *work_tree) +{ + argv_array_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree)); + if (run_hook_le(env->argv, push_to_checkout_hook, + sha1_to_hex(sha1), NULL)) + return "push-to-checkout hook declined"; + else + return NULL; +} + +static const char *update_worktree(unsigned char *sha1) +{ + const char *retval; + const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : ".."; + struct argv_array env = ARGV_ARRAY_INIT; + + if (is_bare_repository()) + return "denyCurrentBranch = updateInstead needs a worktree"; + + argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir())); + + if (!find_hook(push_to_checkout_hook)) + retval = push_to_deploy(sha1, &env, work_tree); + else + retval = push_to_checkout(sha1, &env, work_tree); + + argv_array_clear(&env); + return retval; +} + static const char *update(struct command *cmd, struct shallow_info *si) { const char *name = cmd->ref_name; struct strbuf namespaced_name_buf = STRBUF_INIT; - const char *namespaced_name; + const char *namespaced_name, *ret; unsigned char *old_sha1 = cmd->old_sha1; unsigned char *new_sha1 = cmd->new_sha1; - struct ref_lock *lock; /* only refs/... are allowed */ if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) { @@ -499,6 +890,11 @@ static const char *update(struct command *cmd, struct shallow_info *si) if (deny_current_branch == DENY_UNCONFIGURED) refuse_unconfigured_deny(); return "branch is currently checked out"; + case DENY_UPDATE_INSTEAD: + ret = update_worktree(new_sha1); + if (ret) + return ret; + break; } } @@ -523,10 +919,13 @@ static const char *update(struct command *cmd, struct shallow_info *si) break; case DENY_REFUSE: case DENY_UNCONFIGURED: + case DENY_UPDATE_INSTEAD: if (deny_delete_current == DENY_UNCONFIGURED) refuse_unconfigured_deny_delete_current(); rp_error("refusing to delete the current branch: %s", name); return "deletion of the current branch prohibited"; + default: + return "Invalid denyDeleteCurrent setting"; } } } @@ -560,6 +959,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) } if (is_null_sha1(new_sha1)) { + struct strbuf err = STRBUF_INIT; if (!parse_object(old_sha1)) { old_sha1 = NULL; if (ref_exists(name)) { @@ -569,26 +969,35 @@ static const char *update(struct command *cmd, struct shallow_info *si) cmd->did_not_exist = 1; } } - if (delete_ref(namespaced_name, old_sha1, 0)) { - rp_error("failed to delete %s", name); + if (ref_transaction_delete(transaction, + namespaced_name, + old_sha1, + 0, "push", &err)) { + rp_error("%s", err.buf); + strbuf_release(&err); return "failed to delete"; } + strbuf_release(&err); return NULL; /* good */ } else { + struct strbuf err = STRBUF_INIT; if (shallow_update && si->shallow_ref[cmd->index] && update_shallow_ref(cmd, si)) return "shallow error"; - lock = lock_any_ref_for_update(namespaced_name, old_sha1, - 0, NULL); - if (!lock) { - rp_error("failed to lock %s", name); - return "failed to lock"; - } - if (write_ref_sha1(lock, new_sha1, "push")) { - return "failed to write"; /* error() already called */ + if (ref_transaction_update(transaction, + namespaced_name, + new_sha1, old_sha1, + 0, "push", + &err)) { + rp_error("%s", err.buf); + strbuf_release(&err); + + return "failed to update ref"; } + strbuf_release(&err); + return NULL; /* good */ } } @@ -598,8 +1007,8 @@ static void run_update_post_hook(struct command *commands) struct command *cmd; int argc; const char **argv; - struct child_process proc; - char *hook; + struct child_process proc = CHILD_PROCESS_INIT; + const char *hook; hook = find_hook("post-update"); for (argc = 0, cmd = commands; cmd; cmd = cmd->next) { @@ -614,17 +1023,13 @@ 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; - memset(&proc, 0, sizeof(proc)); proc.no_stdin = 1; proc.stdout_to_stderr = 1; proc.err = use_sideband ? -1 : 0; @@ -648,7 +1053,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) int flag; strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); - dst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag); + dst_name = resolve_ref_unsafe(buf.buf, 0, sha1, &flag); strbuf_release(&buf); if (!(flag & REF_ISSYMREF)) @@ -698,7 +1103,7 @@ static void check_aliased_updates(struct command *commands) string_list_append(&ref_list, cmd->ref_name); item->util = (void *)cmd; } - sort_string_list(&ref_list); + string_list_sort(&ref_list); for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) @@ -776,11 +1181,105 @@ static void reject_updates_to_hidden(struct command *commands) } } +static int should_process_cmd(struct command *cmd) +{ + return !cmd->error_string && !cmd->skip_update; +} + +static void warn_if_skipped_connectivity_check(struct command *commands, + struct shallow_info *si) +{ + struct command *cmd; + int checked_connectivity = 1; + + for (cmd = commands; cmd; cmd = cmd->next) { + if (should_process_cmd(cmd) && si->shallow_ref[cmd->index]) { + error("BUG: connectivity check has not been run on ref %s", + cmd->ref_name); + checked_connectivity = 0; + } + } + if (!checked_connectivity) + die("BUG: connectivity check skipped???"); +} + +static void execute_commands_non_atomic(struct command *commands, + struct shallow_info *si) +{ + struct command *cmd; + struct strbuf err = STRBUF_INIT; + + for (cmd = commands; cmd; cmd = cmd->next) { + if (!should_process_cmd(cmd)) + continue; + + transaction = ref_transaction_begin(&err); + if (!transaction) { + rp_error("%s", err.buf); + strbuf_reset(&err); + cmd->error_string = "transaction failed to start"; + continue; + } + + cmd->error_string = update(cmd, si); + + if (!cmd->error_string + && ref_transaction_commit(transaction, &err)) { + rp_error("%s", err.buf); + strbuf_reset(&err); + cmd->error_string = "failed to update ref"; + } + ref_transaction_free(transaction); + } + strbuf_release(&err); +} + +static void execute_commands_atomic(struct command *commands, + struct shallow_info *si) +{ + struct command *cmd; + struct strbuf err = STRBUF_INIT; + const char *reported_error = "atomic push failure"; + + transaction = ref_transaction_begin(&err); + if (!transaction) { + rp_error("%s", err.buf); + strbuf_reset(&err); + reported_error = "transaction failed to start"; + goto failure; + } + + for (cmd = commands; cmd; cmd = cmd->next) { + if (!should_process_cmd(cmd)) + continue; + + cmd->error_string = update(cmd, si); + + if (cmd->error_string) + goto failure; + } + + if (ref_transaction_commit(transaction, &err)) { + rp_error("%s", err.buf); + reported_error = "atomic transaction failed"; + goto failure; + } + goto cleanup; + +failure: + for (cmd = commands; cmd; cmd = cmd->next) + if (!cmd->error_string) + cmd->error_string = reported_error; + +cleanup: + ref_transaction_free(transaction); + strbuf_release(&err); +} + static void execute_commands(struct command *commands, const char *unpacker_error, struct shallow_info *si) { - int checked_connectivity; struct command *cmd; unsigned char sha1[20]; struct iterate_data data; @@ -809,29 +1308,64 @@ static void execute_commands(struct command *commands, check_aliased_updates(commands); free(head_name_to_free); - head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL); + head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL); - checked_connectivity = 1; - for (cmd = commands; cmd; cmd = cmd->next) { - if (cmd->error_string) - continue; + if (use_atomic) + execute_commands_atomic(commands, si); + else + execute_commands_non_atomic(commands, si); - if (cmd->skip_update) - continue; + if (shallow_update) + warn_if_skipped_connectivity_check(commands, si); +} - cmd->error_string = update(cmd, si); - if (shallow_update && !cmd->error_string && - si->shallow_ref[cmd->index]) { - error("BUG: connectivity check has not been run on ref %s", - cmd->ref_name); - checked_connectivity = 0; - } - } +static struct command **queue_command(struct command **tail, + const char *line, + int linelen) +{ + unsigned char old_sha1[20], new_sha1[20]; + struct command *cmd; + const char *refname; + int reflen; + + if (linelen < 83 || + line[40] != ' ' || + line[81] != ' ' || + get_sha1_hex(line, old_sha1) || + get_sha1_hex(line + 41, new_sha1)) + die("protocol error: expected old/new/ref, got '%s'", line); + + refname = line + 82; + reflen = linelen - 82; + cmd = xcalloc(1, sizeof(struct command) + reflen + 1); + hashcpy(cmd->old_sha1, old_sha1); + hashcpy(cmd->new_sha1, new_sha1); + memcpy(cmd->ref_name, refname, reflen); + cmd->ref_name[reflen] = '\0'; + *tail = cmd; + return &cmd->next; +} + +static void queue_commands_from_cert(struct command **tail, + struct strbuf *push_cert) +{ + const char *boc, *eoc; + + if (*tail) + die("protocol error: got both push certificate and unsigned commands"); + + boc = strstr(push_cert->buf, "\n\n"); + if (!boc) + die("malformed push certificate %.*s", 100, push_cert->buf); + else + boc += 2; + eoc = push_cert->buf + parse_signature(push_cert->buf, push_cert->len); - if (shallow_update && !checked_connectivity) - error("BUG: run 'git fsck' for safety.\n" - "If there are errors, try to remove " - "the reported refs above"); + while (boc < eoc) { + const char *eol = memchr(boc, '\n', eoc - boc); + tail = queue_command(tail, boc, eol ? eol - boc : eoc - eol); + boc = eol ? eol + 1 : eoc; + } } static struct command *read_head_info(struct sha1_array *shallow) @@ -840,48 +1374,62 @@ static struct command *read_head_info(struct sha1_array *shallow) struct command **p = &commands; for (;;) { char *line; - unsigned char old_sha1[20], new_sha1[20]; - struct command *cmd; - char *refname; - int len, reflen; + int len, linelen; line = packet_read_line(0, &len); if (!line) break; if (len == 48 && starts_with(line, "shallow ")) { - if (get_sha1_hex(line + 8, old_sha1)) - die("protocol error: expected shallow sha, got '%s'", line + 8); - sha1_array_append(shallow, old_sha1); + unsigned char sha1[20]; + if (get_sha1_hex(line + 8, sha1)) + die("protocol error: expected shallow sha, got '%s'", + line + 8); + sha1_array_append(shallow, sha1); continue; } - if (len < 83 || - line[40] != ' ' || - line[81] != ' ' || - get_sha1_hex(line, old_sha1) || - get_sha1_hex(line + 41, new_sha1)) - die("protocol error: expected old/new/ref, got '%s'", - line); - - refname = line + 82; - reflen = strlen(refname); - if (reflen + 82 < len) { - const char *feature_list = refname + reflen + 1; + linelen = strlen(line); + if (linelen < len) { + const char *feature_list = line + linelen + 1; if (parse_feature_request(feature_list, "report-status")) report_status = 1; if (parse_feature_request(feature_list, "side-band-64k")) use_sideband = LARGE_PACKET_MAX; if (parse_feature_request(feature_list, "quiet")) quiet = 1; + if (advertise_atomic_push + && parse_feature_request(feature_list, "atomic")) + use_atomic = 1; } - cmd = xcalloc(1, sizeof(struct command) + len - 80); - hashcpy(cmd->old_sha1, old_sha1); - hashcpy(cmd->new_sha1, new_sha1); - memcpy(cmd->ref_name, line + 82, len - 81); - *p = cmd; - p = &cmd->next; + + if (!strcmp(line, "push-cert")) { + int true_flush = 0; + char certbuf[1024]; + + for (;;) { + len = packet_read(0, NULL, NULL, + certbuf, sizeof(certbuf), 0); + if (!len) { + true_flush = 1; + break; + } + if (!strcmp(certbuf, "push-cert-end\n")) + break; /* end of cert */ + strbuf_addstr(&push_cert, certbuf); + } + + if (true_flush) + break; + continue; + } + + p = queue_command(p, line, linelen); } + + if (push_cert.len) + queue_commands_from_cert(p, &push_cert); + return commands; } @@ -910,11 +1458,10 @@ static const char *pack_lockfile; static const char *unpack(int err_fd, struct shallow_info *si) { struct pack_header hdr; - struct argv_array av = ARGV_ARRAY_INIT; const char *hdr_err; int status; char hdr_arg[38]; - struct child_process child; + struct child_process child = CHILD_PROCESS_INIT; int fsck_objects = (receive_fsck_objects >= 0 ? receive_fsck_objects : transfer_fsck_objects >= 0 @@ -933,17 +1480,16 @@ static const char *unpack(int err_fd, struct shallow_info *si) if (si->nr_ours || si->nr_theirs) { alt_shallow_file = setup_temporary_shallow(si->shallow); - argv_array_pushl(&av, "--shallow-file", alt_shallow_file, NULL); + argv_array_push(&child.args, "--shallow-file"); + argv_array_push(&child.args, alt_shallow_file); } - memset(&child, 0, sizeof(child)); if (ntohl(hdr.hdr_entries) < unpack_limit) { - argv_array_pushl(&av, "unpack-objects", hdr_arg, NULL); + argv_array_pushl(&child.args, "unpack-objects", hdr_arg, NULL); if (quiet) - argv_array_push(&av, "-q"); + argv_array_push(&child.args, "-q"); if (fsck_objects) - argv_array_push(&av, "--strict"); - child.argv = av.argv; + argv_array_push(&child.args, "--strict"); child.no_stdout = 1; child.err = err_fd; child.git_cmd = 1; @@ -958,13 +1504,12 @@ static const char *unpack(int err_fd, struct shallow_info *si) if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) strcpy(keep_arg + s, "localhost"); - argv_array_pushl(&av, "index-pack", + argv_array_pushl(&child.args, "index-pack", "--stdin", hdr_arg, keep_arg, NULL); if (fsck_objects) - argv_array_push(&av, "--strict"); + argv_array_push(&child.args, "--strict"); if (fix_thin) - argv_array_push(&av, "--fix-thin"); - child.argv = av.argv; + argv_array_push(&child.args, "--fix-thin"); child.out = -1; child.err = err_fd; child.git_cmd = 1; @@ -1123,9 +1668,7 @@ static int delete_only(struct command *commands) 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; struct command *commands; struct sha1_array shallow = SHA1_ARRAY_INIT; struct sha1_array ref = SHA1_ARRAY_INIT; @@ -1158,19 +1701,21 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) usage(receive_pack_usage); } - if (dir) + if (service_dir) usage(receive_pack_usage); - dir = xstrdup(arg); + service_dir = arg; } - if (!dir) + if (!service_dir) usage(receive_pack_usage); setup_path(); - if (!enter_repo(dir, 0)) - die("'%s' does not appear to be a git repository", dir); + if (!enter_repo(service_dir, 0)) + die("'%s' does not appear to be a git repository", service_dir); git_config(receive_pack_config, NULL); + if (cert_nonce_seed) + push_cert_nonce = prepare_push_cert_nonce(service_dir, time(NULL)); if (0 <= transfer_unpack_limit) unpack_limit = transfer_unpack_limit; @@ -1215,5 +1760,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) packet_flush(1); sha1_array_clear(&shallow); sha1_array_clear(&ref); + free((void *)push_cert_nonce); return 0; } diff --git a/builtin/reflog.c b/builtin/reflog.c index c12a9784e6..8182b648b9 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -1,5 +1,5 @@ -#include "cache.h" #include "builtin.h" +#include "lockfile.h" #include "commit.h" #include "refs.h" #include "dir.h" @@ -8,32 +8,24 @@ #include "revision.h" #include "reachable.h" -/* - * reflog expire - */ - +/* NEEDSWORK: switch to using parse_options */ static const char reflog_expire_usage[] = -"git reflog expire [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>..."; +"git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>..."; static const char reflog_delete_usage[] = -"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>..."; +"git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>..."; static unsigned long default_reflog_expire; static unsigned long default_reflog_expire_unreachable; struct cmd_reflog_expire_cb { struct rev_info revs; - int dry_run; int stalefix; - int rewrite; - int updateref; - int verbose; unsigned long expire_total; unsigned long expire_unreachable; int recno; }; -struct expire_reflog_cb { - FILE *newlog; +struct expire_reflog_policy_cb { enum { UE_NORMAL, UE_ALWAYS, @@ -41,14 +33,16 @@ struct expire_reflog_cb { } unreachable_expire_kind; struct commit_list *mark_list; unsigned long mark_limit; - struct cmd_reflog_expire_cb *cmd; - unsigned char last_kept_sha1[20]; + struct cmd_reflog_expire_cb cmd; + struct commit *tip_commit; + struct commit_list *tips; }; struct collected_reflog { unsigned char sha1[20]; char reflog[FLEX_ARRAY]; }; + struct collect_reflog_cb { struct collected_reflog **e; int alloc; @@ -220,7 +214,7 @@ static int keep_entry(struct commit **it, unsigned char *sha1) * the expire_limit and queue them back, so that the caller can call * us again to restart the traversal with longer expire_limit. */ -static void mark_reachable(struct expire_reflog_cb *cb) +static void mark_reachable(struct expire_reflog_policy_cb *cb) { struct commit *commit; struct commit_list *pending; @@ -259,7 +253,7 @@ static void mark_reachable(struct expire_reflog_cb *cb) cb->mark_list = leftover; } -static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1) +static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, unsigned char *sha1) { /* * We may or may not have the commit yet - if not, look it @@ -288,55 +282,39 @@ static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsig return !(commit->object.flags & REACHABLE); } -static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, - const char *email, unsigned long timestamp, int tz, - const char *message, void *cb_data) +/* + * Return true iff the specified reflog entry should be expired. + */ +static int should_expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) { - struct expire_reflog_cb *cb = cb_data; + struct expire_reflog_policy_cb *cb = cb_data; struct commit *old, *new; - if (timestamp < cb->cmd->expire_total) - goto prune; - - if (cb->cmd->rewrite) - osha1 = cb->last_kept_sha1; + if (timestamp < cb->cmd.expire_total) + return 1; old = new = NULL; - if (cb->cmd->stalefix && + if (cb->cmd.stalefix && (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1))) - goto prune; + return 1; - if (timestamp < cb->cmd->expire_unreachable) { + if (timestamp < cb->cmd.expire_unreachable) { if (cb->unreachable_expire_kind == UE_ALWAYS) - goto prune; + return 1; if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1)) - goto prune; + return 1; } - if (cb->cmd->recno && --(cb->cmd->recno) == 0) - goto prune; - - if (cb->newlog) { - char sign = (tz < 0) ? '-' : '+'; - int zone = (tz < 0) ? (-tz) : tz; - fprintf(cb->newlog, "%s %s %s %lu %c%04d\t%s", - sha1_to_hex(osha1), sha1_to_hex(nsha1), - email, timestamp, sign, zone, - message); - hashcpy(cb->last_kept_sha1, nsha1); - } - if (cb->cmd->verbose) - printf("keep %s", message); - return 0; - prune: - if (!cb->newlog) - printf("would prune %s", message); - else if (cb->cmd->verbose) - printf("prune %s", message); + if (cb->cmd.recno && --(cb->cmd.recno) == 0) + return 1; + return 0; } -static int push_tip_to_list(const char *refname, const unsigned char *sha1, int flags, void *cb_data) +static int push_tip_to_list(const char *refname, const unsigned char *sha1, + int flags, void *cb_data) { struct commit_list **list = cb_data; struct commit *tip_commit; @@ -349,104 +327,56 @@ static int push_tip_to_list(const char *refname, const unsigned char *sha1, int return 0; } -static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data) +static void reflog_expiry_prepare(const char *refname, + const unsigned char *sha1, + void *cb_data) { - struct cmd_reflog_expire_cb *cmd = cb_data; - struct expire_reflog_cb cb; - struct ref_lock *lock; - char *log_file, *newlog_path = NULL; - struct commit *tip_commit; - struct commit_list *tips; - int status = 0; - - memset(&cb, 0, sizeof(cb)); - - /* - * we take the lock for the ref itself to prevent it from - * getting updated. - */ - lock = lock_any_ref_for_update(ref, sha1, 0, NULL); - if (!lock) - return error("cannot lock ref '%s'", ref); - log_file = git_pathdup("logs/%s", ref); - if (!file_exists(log_file)) - goto finish; - if (!cmd->dry_run) { - newlog_path = git_pathdup("logs/%s.lock", ref); - cb.newlog = fopen(newlog_path, "w"); - } + struct expire_reflog_policy_cb *cb = cb_data; - cb.cmd = cmd; - - if (!cmd->expire_unreachable || !strcmp(ref, "HEAD")) { - tip_commit = NULL; - cb.unreachable_expire_kind = UE_HEAD; + if (!cb->cmd.expire_unreachable || !strcmp(refname, "HEAD")) { + cb->tip_commit = NULL; + cb->unreachable_expire_kind = UE_HEAD; } else { - tip_commit = lookup_commit_reference_gently(sha1, 1); - if (!tip_commit) - cb.unreachable_expire_kind = UE_ALWAYS; + cb->tip_commit = lookup_commit_reference_gently(sha1, 1); + if (!cb->tip_commit) + cb->unreachable_expire_kind = UE_ALWAYS; else - cb.unreachable_expire_kind = UE_NORMAL; + cb->unreachable_expire_kind = UE_NORMAL; } - if (cmd->expire_unreachable <= cmd->expire_total) - cb.unreachable_expire_kind = UE_ALWAYS; + if (cb->cmd.expire_unreachable <= cb->cmd.expire_total) + cb->unreachable_expire_kind = UE_ALWAYS; - cb.mark_list = NULL; - tips = NULL; - if (cb.unreachable_expire_kind != UE_ALWAYS) { - if (cb.unreachable_expire_kind == UE_HEAD) { + cb->mark_list = NULL; + cb->tips = NULL; + if (cb->unreachable_expire_kind != UE_ALWAYS) { + if (cb->unreachable_expire_kind == UE_HEAD) { struct commit_list *elem; - for_each_ref(push_tip_to_list, &tips); - for (elem = tips; elem; elem = elem->next) - commit_list_insert(elem->item, &cb.mark_list); + for_each_ref(push_tip_to_list, &cb->tips); + for (elem = cb->tips; elem; elem = elem->next) + commit_list_insert(elem->item, &cb->mark_list); } else { - commit_list_insert(tip_commit, &cb.mark_list); + commit_list_insert(cb->tip_commit, &cb->mark_list); } - cb.mark_limit = cmd->expire_total; - mark_reachable(&cb); + cb->mark_limit = cb->cmd.expire_total; + mark_reachable(cb); } +} - for_each_reflog_ent(ref, expire_reflog_ent, &cb); +static void reflog_expiry_cleanup(void *cb_data) +{ + struct expire_reflog_policy_cb *cb = cb_data; - if (cb.unreachable_expire_kind != UE_ALWAYS) { - if (cb.unreachable_expire_kind == UE_HEAD) { + if (cb->unreachable_expire_kind != UE_ALWAYS) { + if (cb->unreachable_expire_kind == UE_HEAD) { struct commit_list *elem; - for (elem = tips; elem; elem = elem->next) + for (elem = cb->tips; elem; elem = elem->next) clear_commit_marks(elem->item, REACHABLE); - free_commit_list(tips); + free_commit_list(cb->tips); } else { - clear_commit_marks(tip_commit, REACHABLE); + clear_commit_marks(cb->tip_commit, REACHABLE); } } - finish: - if (cb.newlog) { - if (fclose(cb.newlog)) { - status |= error("%s: %s", strerror(errno), - newlog_path); - unlink(newlog_path); - } else if (cmd->updateref && - (write_in_full(lock->lock_fd, - sha1_to_hex(cb.last_kept_sha1), 40) != 40 || - write_str_in_full(lock->lock_fd, "\n") != 1 || - close_ref(lock) < 0)) { - status |= error("Couldn't write %s", - lock->lk->filename); - unlink(newlog_path); - } else if (rename(newlog_path, log_file)) { - status |= error("cannot rename %s to %s", - newlog_path, log_file); - unlink(newlog_path); - } else if (cmd->updateref && commit_ref(lock)) { - status |= error("Couldn't set %s", lock->ref_name); - } else { - adjust_shared_perm(log_file); - } - } - free(newlog_path); - free(log_file); - unlock_ref(lock); - return status; } static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data) @@ -590,10 +520,11 @@ static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, c static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) { - struct cmd_reflog_expire_cb cb; + struct expire_reflog_policy_cb cb; unsigned long now = time(NULL); int i, status, do_all; int explicit_expiry = 0; + unsigned int flags = 0; default_reflog_expire_unreachable = now - 30 * 24 * 3600; default_reflog_expire = now - 90 * 24 * 3600; @@ -603,33 +534,33 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) do_all = status = 0; memset(&cb, 0, sizeof(cb)); - cb.expire_total = default_reflog_expire; - cb.expire_unreachable = default_reflog_expire_unreachable; + cb.cmd.expire_total = default_reflog_expire; + cb.cmd.expire_unreachable = default_reflog_expire_unreachable; for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) - cb.dry_run = 1; + flags |= EXPIRE_REFLOGS_DRY_RUN; else if (starts_with(arg, "--expire=")) { - if (parse_expiry_date(arg + 9, &cb.expire_total)) + if (parse_expiry_date(arg + 9, &cb.cmd.expire_total)) die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_TOTAL; } else if (starts_with(arg, "--expire-unreachable=")) { - if (parse_expiry_date(arg + 21, &cb.expire_unreachable)) + if (parse_expiry_date(arg + 21, &cb.cmd.expire_unreachable)) die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_UNREACH; } else if (!strcmp(arg, "--stale-fix")) - cb.stalefix = 1; + cb.cmd.stalefix = 1; else if (!strcmp(arg, "--rewrite")) - cb.rewrite = 1; + flags |= EXPIRE_REFLOGS_REWRITE; else if (!strcmp(arg, "--updateref")) - cb.updateref = 1; + flags |= EXPIRE_REFLOGS_UPDATE_REF; else if (!strcmp(arg, "--all")) do_all = 1; else if (!strcmp(arg, "--verbose")) - cb.verbose = 1; + flags |= EXPIRE_REFLOGS_VERBOSE; else if (!strcmp(arg, "--")) { i++; break; @@ -645,12 +576,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) * even in older repository. We cannot trust what's reachable * from reflog if the repository was pruned with older git. */ - if (cb.stalefix) { - init_revisions(&cb.revs, prefix); - if (cb.verbose) + if (cb.cmd.stalefix) { + init_revisions(&cb.cmd.revs, prefix); + if (flags & EXPIRE_REFLOGS_VERBOSE) printf("Marking reachable objects..."); - mark_reachable_objects(&cb.revs, 0, NULL); - if (cb.verbose) + mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL); + if (flags & EXPIRE_REFLOGS_VERBOSE) putchar('\n'); } @@ -662,8 +593,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) for_each_reflog(collect_reflog, &collected); for (i = 0; i < collected.nr; i++) { struct collected_reflog *e = collected.e[i]; - set_reflog_expiry_param(&cb, explicit_expiry, e->reflog); - status |= expire_reflog(e->reflog, e->sha1, 0, &cb); + set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog); + status |= reflog_expire(e->reflog, e->sha1, flags, + reflog_expiry_prepare, + should_expire_reflog_ent, + reflog_expiry_cleanup, + &cb); free(e); } free(collected.e); @@ -676,8 +611,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) status |= error("%s points nowhere!", argv[i]); continue; } - set_reflog_expiry_param(&cb, explicit_expiry, ref); - status |= expire_reflog(ref, sha1, 0, &cb); + set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref); + status |= reflog_expire(ref, sha1, flags, + reflog_expiry_prepare, + should_expire_reflog_ent, + reflog_expiry_cleanup, + &cb); } return status; } @@ -686,29 +625,30 @@ static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { - struct cmd_reflog_expire_cb *cb = cb_data; - if (!cb->expire_total || timestamp < cb->expire_total) - cb->recno++; + struct expire_reflog_policy_cb *cb = cb_data; + if (!cb->cmd.expire_total || timestamp < cb->cmd.expire_total) + cb->cmd.recno++; return 0; } static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) { - struct cmd_reflog_expire_cb cb; + struct expire_reflog_policy_cb cb; int i, status = 0; + unsigned int flags = 0; memset(&cb, 0, sizeof(cb)); for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) - cb.dry_run = 1; + flags |= EXPIRE_REFLOGS_DRY_RUN; else if (!strcmp(arg, "--rewrite")) - cb.rewrite = 1; + flags |= EXPIRE_REFLOGS_REWRITE; else if (!strcmp(arg, "--updateref")) - cb.updateref = 1; + flags |= EXPIRE_REFLOGS_UPDATE_REF; else if (!strcmp(arg, "--verbose")) - cb.verbose = 1; + flags |= EXPIRE_REFLOGS_VERBOSE; else if (!strcmp(arg, "--")) { i++; break; @@ -740,15 +680,19 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) recno = strtoul(spec + 2, &ep, 10); if (*ep == '}') { - cb.recno = -recno; + cb.cmd.recno = -recno; for_each_reflog_ent(ref, count_reflog_ent, &cb); } else { - cb.expire_total = approxidate(spec + 2); + cb.cmd.expire_total = approxidate(spec + 2); for_each_reflog_ent(ref, count_reflog_ent, &cb); - cb.expire_total = 0; + cb.cmd.expire_total = 0; } - status |= expire_reflog(ref, sha1, 0, &cb); + status |= reflog_expire(ref, sha1, flags, + reflog_expiry_prepare, + should_expire_reflog_ent, + reflog_expiry_cleanup, + &cb); free(ref); } return status; diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c index 692c834d9d..3b8c22cc75 100644 --- a/builtin/remote-ext.c +++ b/builtin/remote-ext.c @@ -30,16 +30,14 @@ static char *strip_escapes(const char *str, const char *service, size_t rpos = 0; int escape = 0; char special = 0; - size_t psoff = 0; + const char *service_noprefix = service; struct strbuf ret = STRBUF_INIT; - /* Calculate prefix length for \s and lengths for \s and \S */ - if (!strncmp(service, "git-", 4)) - psoff = 4; + skip_prefix(service_noprefix, "git-", &service_noprefix); /* Pass the service to command. */ setenv("GIT_EXT_SERVICE", service, 1); - setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 1); + setenv("GIT_EXT_SERVICE_NOPREFIX", service_noprefix, 1); /* Scan the length of argument. */ while (str[rpos] && (escape || str[rpos] != ' ')) { @@ -85,7 +83,7 @@ static char *strip_escapes(const char *str, const char *service, strbuf_addch(&ret, str[rpos]); break; case 's': - strbuf_addstr(&ret, service + psoff); + strbuf_addstr(&ret, service_noprefix); break; case 'S': strbuf_addstr(&ret, service); @@ -179,9 +177,8 @@ static void send_git_request(int stdin_fd, const char *serv, const char *repo, static int run_child(const char *arg, const char *service) { int r; - struct child_process child; + struct child_process child = CHILD_PROCESS_INIT; - memset(&child, 0, sizeof(child)); child.in = -1; child.out = -1; child.err = 0; diff --git a/builtin/remote.c b/builtin/remote.c index c9102e8fe9..ad57fc984e 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -10,10 +10,10 @@ static const char * const builtin_remote_usage[] = { N_("git remote [-v | --verbose]"), - N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>"), + N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags | --no-tags] [--mirror=<fetch|push>] <name> <url>"), N_("git remote rename <old> <new>"), N_("git remote remove <name>"), - N_("git remote set-head <name> (-a | --auto | -d | --delete |<branch>)"), + N_("git remote set-head <name> (-a | --auto | -d | --delete | <branch>)"), N_("git remote [-v | --verbose] show [-n] <name>"), N_("git remote prune [-n | --dry-run] <name>"), N_("git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]"), @@ -180,7 +180,9 @@ static int add(int argc, const char **argv) url = argv[1]; remote = remote_get(name); - if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) || + if (remote && (remote->url_nr > 1 || + (strcmp(name, remote->url[0]) && + strcmp(url, remote->url[0])) || remote->fetch_refspec_nr)) die(_("remote %s already exists."), name); @@ -250,9 +252,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 +265,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; @@ -353,9 +354,9 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat free_refs(stale_refs); free_refs(fetch_map); - sort_string_list(&states->new); - sort_string_list(&states->tracked); - sort_string_list(&states->stale); + string_list_sort(&states->new); + string_list_sort(&states->tracked); + string_list_sort(&states->stale); return 0; } @@ -568,7 +569,8 @@ static int read_remote_branches(const char *refname, strbuf_addf(&buf, "refs/remotes/%s/", rename->old); if (starts_with(refname, buf.buf)) { item = string_list_append(rename->remote_branches, xstrdup(refname)); - symref = resolve_ref_unsafe(refname, orig_sha1, 1, &flag); + symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING, + orig_sha1, &flag); if (flag & REF_ISSYMREF) item->util = xstrdup(symref); else @@ -582,7 +584,7 @@ static int migrate_file(struct remote *remote) { struct strbuf buf = STRBUF_INIT; int i; - char *path = NULL; + const char *path = NULL; strbuf_addf(&buf, "remote.%s.url", remote->name); for (i = 0; i < remote->url_nr; i++) @@ -704,7 +706,7 @@ static int mv(int argc, const char **argv) int flag = 0; unsigned char sha1[20]; - read_ref_full(item->string, sha1, 1, &flag); + read_ref_full(item->string, RESOLVE_REF_READING, sha1, &flag); if (!(flag & REF_ISSYMREF)) continue; if (delete_ref(item->string, NULL, REF_NODEREF)) @@ -749,14 +751,12 @@ static int mv(int argc, const char **argv) static int remove_branches(struct string_list *branches) { - const char **branch_names; + struct strbuf err = STRBUF_INIT; int i, result = 0; - 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); - free(branch_names); + if (repack_without_refs(branches, &err)) + result |= error("%s", err.buf); + strbuf_release(&err); for (i = 0; i < branches->nr; i++) { struct string_list_item *item = branches->items + i; @@ -911,7 +911,7 @@ static int get_remote_ref_states(const char *name, get_push_ref_states(remote_refs, states); } else { for_each_ref(append_ref_to_tracked_list, states); - sort_string_list(&states->tracked); + string_list_sort(&states->tracked); get_push_ref_states_noquery(states); } @@ -1130,7 +1130,7 @@ static int show_all(void) if (!result) { int i; - sort_string_list(&list); + string_list_sort(&list); for (i = 0; i < list.nr; i++) { struct string_list_item *item = list.items + i; if (verbose) @@ -1311,10 +1311,10 @@ static int set_head(int argc, const char **argv) static int prune_remote(const char *remote, int dry_run) { - int result = 0, i; + int result = 0; struct ref_states states; - struct string_list delete_refs_list = STRING_LIST_INIT_NODUP; - const char **delete_refs; + struct string_list refs_to_prune = STRING_LIST_INIT_NODUP; + struct string_list_item *item; const char *dangling_msg = dry_run ? _(" %s will become dangling!") : _(" %s has become dangling!"); @@ -1322,25 +1322,30 @@ static int prune_remote(const char *remote, int dry_run) memset(&states, 0, sizeof(states)); get_remote_ref_states(remote, &states, GET_REF_STATES); - if (states.stale.nr) { - printf_ln(_("Pruning %s"), remote); - printf_ln(_("URL: %s"), - states.remote->url_nr - ? states.remote->url[0] - : _("(no URL)")); - - delete_refs = xmalloc(states.stale.nr * sizeof(*delete_refs)); - 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); - free(delete_refs); + if (!states.stale.nr) { + free_remote_ref_states(&states); + return 0; } - for (i = 0; i < states.stale.nr; i++) { - const char *refname = states.stale.items[i].util; + printf_ln(_("Pruning %s"), remote); + printf_ln(_("URL: %s"), + states.remote->url_nr + ? states.remote->url[0] + : _("(no URL)")); + + for_each_string_list_item(item, &states.stale) + string_list_append(&refs_to_prune, item->util); + string_list_sort(&refs_to_prune); + + if (!dry_run) { + struct strbuf err = STRBUF_INIT; + if (repack_without_refs(&refs_to_prune, &err)) + result |= error("%s", err.buf); + strbuf_release(&err); + } - string_list_insert(&delete_refs_list, refname); + for_each_string_list_item(item, &states.stale) { + const char *refname = item->util; if (!dry_run) result |= delete_ref(refname, NULL, 0); @@ -1353,9 +1358,9 @@ static int prune_remote(const char *remote, int dry_run) abbrev_ref(refname, "refs/remotes/")); } - warn_dangling_symrefs(stdout, dangling_msg, &delete_refs_list); - string_list_clear(&delete_refs_list, 0); + warn_dangling_symrefs(stdout, dangling_msg, &refs_to_prune); + string_list_clear(&refs_to_prune, 0); free_remote_ref_states(&states); return result; } diff --git a/builtin/repack.c b/builtin/repack.c index 36c1cf9c25..af7340c7ba 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -10,11 +10,11 @@ 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[] = { - N_("git repack [options]"), + N_("git repack [<options>]"), NULL }; @@ -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))) @@ -133,9 +133,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) {".idx"}, {".bitmap", 1}, }; - struct child_process cmd; + struct child_process cmd = CHILD_PROCESS_INIT; struct string_list_item *item; - struct argv_array cmd_args = ARGV_ARRAY_INIT; struct string_list names = STRING_LIST_INIT_DUP; struct string_list rollback = STRING_LIST_INIT_NODUP; struct string_list existing_packs = STRING_LIST_INIT_DUP; @@ -195,64 +194,66 @@ 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()); sigchain_push_common(remove_pack_on_signal); - argv_array_push(&cmd_args, "pack-objects"); - argv_array_push(&cmd_args, "--keep-true-parents"); + argv_array_push(&cmd.args, "pack-objects"); + argv_array_push(&cmd.args, "--keep-true-parents"); if (!pack_kept_objects) - argv_array_push(&cmd_args, "--honor-pack-keep"); - argv_array_push(&cmd_args, "--non-empty"); - argv_array_push(&cmd_args, "--all"); - argv_array_push(&cmd_args, "--reflog"); + argv_array_push(&cmd.args, "--honor-pack-keep"); + argv_array_push(&cmd.args, "--non-empty"); + argv_array_push(&cmd.args, "--all"); + argv_array_push(&cmd.args, "--reflog"); + argv_array_push(&cmd.args, "--indexed-objects"); if (window) - argv_array_pushf(&cmd_args, "--window=%s", window); + argv_array_pushf(&cmd.args, "--window=%s", window); if (window_memory) - argv_array_pushf(&cmd_args, "--window-memory=%s", window_memory); + argv_array_pushf(&cmd.args, "--window-memory=%s", window_memory); if (depth) - argv_array_pushf(&cmd_args, "--depth=%s", depth); + argv_array_pushf(&cmd.args, "--depth=%s", depth); if (max_pack_size) - argv_array_pushf(&cmd_args, "--max-pack-size=%s", max_pack_size); + argv_array_pushf(&cmd.args, "--max-pack-size=%s", max_pack_size); if (no_reuse_delta) - argv_array_pushf(&cmd_args, "--no-reuse-delta"); + 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-"); + argv_array_pushf(&cmd.args, "--no-reuse-object"); + if (write_bitmaps) + argv_array_push(&cmd.args, "--write-bitmap-index"); if (pack_everything & ALL_INTO_ONE) { get_non_kept_pack_filenames(&existing_packs); if (existing_packs.nr && delete_redundant) { - if (unpack_unreachable) - argv_array_pushf(&cmd_args, + if (unpack_unreachable) { + argv_array_pushf(&cmd.args, "--unpack-unreachable=%s", unpack_unreachable); - else if (pack_everything & LOOSEN_UNREACHABLE) - argv_array_push(&cmd_args, + argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); + } else if (pack_everything & LOOSEN_UNREACHABLE) { + argv_array_push(&cmd.args, "--unpack-unreachable"); + } else { + argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); + } } } else { - argv_array_push(&cmd_args, "--unpacked"); - argv_array_push(&cmd_args, "--incremental"); + argv_array_push(&cmd.args, "--unpacked"); + argv_array_push(&cmd.args, "--incremental"); } if (local) - argv_array_push(&cmd_args, "--local"); + argv_array_push(&cmd.args, "--local"); if (quiet) - argv_array_push(&cmd_args, "--quiet"); + argv_array_push(&cmd.args, "--quiet"); if (delta_base_offset) - argv_array_push(&cmd_args, "--delta-base-offset"); + argv_array_push(&cmd.args, "--delta-base-offset"); - argv_array_push(&cmd_args, packtmp); + argv_array_push(&cmd.args, packtmp); - memset(&cmd, 0, sizeof(cmd)); - cmd.argv = cmd_args.argv; cmd.git_cmd = 1; cmd.out = -1; cmd.no_stdin = 1; @@ -271,7 +272,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix) ret = finish_command(&cmd); if (ret) return ret; - argv_array_clear(&cmd_args); if (!names.nr && !quiet) printf("Nothing new to pack.\n"); @@ -285,7 +285,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) failed = 0; for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - char *fname, *fname_old; + const char *fname_old; + char *fname; fname = mkpathdup("%s/pack-%s%s", packdir, item->string, exts[ext].name); if (!file_exists(fname)) { @@ -313,7 +314,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (failed) { struct string_list rollback_failure = STRING_LIST_INIT_DUP; for_each_string_list_item(item, &rollback) { - char *fname, *fname_old; + const char *fname_old; + char *fname; fname = mkpathdup("%s/%s", packdir, item->string); fname_old = mkpath("%s/old-%s", packdir, item->string); if (rename(fname_old, fname)) @@ -366,7 +368,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) /* Remove the "old-" files */ for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - char *fname; + const char *fname; fname = mkpath("%s/old-%s%s", packdir, item->string, @@ -379,7 +381,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) /* End of pack replacement. */ if (delete_redundant) { - sort_string_list(&names); + int opts = 0; + string_list_sort(&names); for_each_string_list_item(item, &existing_packs) { char *sha1; size_t len = strlen(item->string); @@ -389,25 +392,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (!string_list_has_string(&names, sha1)) remove_redundant_pack(packdir, item->string); } - argv_array_push(&cmd_args, "prune-packed"); - if (quiet) - argv_array_push(&cmd_args, "--quiet"); - - memset(&cmd, 0, sizeof(cmd)); - cmd.argv = cmd_args.argv; - cmd.git_cmd = 1; - run_command(&cmd); - argv_array_clear(&cmd_args); + if (!quiet && isatty(2)) + opts |= PRUNE_PACKED_VERBOSE; + prune_packed_objects(opts); } - if (!no_update_server_info) { - argv_array_push(&cmd_args, "update-server-info"); - memset(&cmd, 0, sizeof(cmd)); - cmd.argv = cmd_args.argv; - cmd.git_cmd = 1; - run_command(&cmd); - argv_array_clear(&cmd_args); - } + if (!no_update_server_info) + update_server_info(0); remove_temporary_files(); string_list_clear(&names, 0); string_list_clear(&rollback, 0); diff --git a/builtin/replace.c b/builtin/replace.c index b62420a01a..54bf01acb4 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,37 @@ 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_transaction *transaction; + struct strbuf err = STRBUF_INIT; + obj_type = sha1_object_info(object, NULL); repl_type = sha1_object_info(repl, NULL); if (!force && obj_type != repl_type) @@ -152,28 +167,274 @@ 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) - die("%s: cannot lock the ref", ref); - if (write_ref_sha1(lock, repl, NULL) < 0) - die("%s: cannot update the ref", ref); + transaction = ref_transaction_begin(&err); + if (!transaction || + ref_transaction_update(transaction, ref, repl, prev, + 0, NULL, &err) || + ref_transaction_commit(transaction, &err)) + die("%s", err.buf); + ref_transaction_free(transaction); 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 = CHILD_PROCESS_INIT; + 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 = CHILD_PROCESS_INIT; + 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 +443,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/rerere.c b/builtin/rerere.c index 98eb8c5404..7afadd2ead 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -9,7 +9,7 @@ #include "pathspec.h" static const char * const rerere_usage[] = { - N_("git rerere [clear | forget path... | status | remaining | diff | gc]"), + N_("git rerere [clear | forget <path>... | status | remaining | diff | gc]"), NULL, }; diff --git a/builtin/reset.c b/builtin/reset.c index 6bd6245821..4c08ddc1ca 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -8,6 +8,7 @@ * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano */ #include "builtin.h" +#include "lockfile.h" #include "tag.h" #include "object.h" #include "commit.h" @@ -84,7 +85,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 +253,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 +354,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 +370,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..4d10dd9545 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); } @@ -277,7 +279,7 @@ static int try_difference(const char *arg) struct commit *a, *b; a = lookup_commit_reference(sha1); b = lookup_commit_reference(end); - exclude = get_merge_bases(a, b, 1); + exclude = get_merge_bases(a, b); while (exclude) { struct commit_list *n = exclude->next; show_rev(REVERSED, @@ -356,7 +358,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) { static int keep_dashdash = 0, stop_at_non_option = 0; static char const * const parseopt_usage[] = { - N_("git rev-parse --parseopt [options] -- [<args>...]"), + N_("git rev-parse --parseopt [<options>] -- [<args>...]"), NULL }; static struct option parseopt_opts[] = { @@ -494,9 +496,9 @@ static void die_no_single_rev(int quiet) } static const char builtin_rev_parse_usage[] = -N_("git rev-parse --parseopt [options] -- [<args>...]\n" +N_("git rev-parse --parseopt [<options>] -- [<args>...]\n" " or: git rev-parse --sq-quote [<arg>...]\n" - " or: git rev-parse [options] [<arg>...]\n" + " or: git rev-parse [<options>] [<arg>...]\n" "\n" "Run \"git rev-parse --parseopt -h\" for more information on the first usage."); @@ -506,7 +508,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) int has_dashdash = 0; int output_prefix = 0; unsigned char sha1[20]; + unsigned int flags = 0; const char *name = NULL; + struct object_context unused; if (argc > 1 && !strcmp("--parseopt", argv[1])) return cmd_parseopt(argc - 1, argv + 1, prefix); @@ -529,6 +533,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) { const char *arg = argv[i]; + if (!strcmp(arg, "--git-path")) { + if (!argv[i + 1]) + die("--git-path requires an argument"); + puts(git_path("%s", argv[i + 1])); + i++; + continue; + } if (as_is) { if (show_file(arg, output_prefix) && as_is < 2) verify_filename(prefix, arg, 0); @@ -594,6 +605,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "--quiet") || !strcmp(arg, "-q")) { quiet = 1; + flags |= GET_SHA1_QUIETLY; continue; } if (!strcmp(arg, "--short") || @@ -734,7 +746,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "--git-dir")) { const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); - static char cwd[PATH_MAX]; + char *cwd; int len; if (gitdir) { puts(gitdir); @@ -744,10 +756,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) puts(".git"); continue; } - if (!getcwd(cwd, PATH_MAX)) - die_errno("unable to get current working directory"); + cwd = xgetcwd(); len = strlen(cwd); printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : ""); + free(cwd); + continue; + } + if (!strcmp(arg, "--git-common-dir")) { + puts(get_git_common_dir()); continue; } if (!strcmp(arg, "--resolve-git-dir")) { @@ -775,6 +791,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; @@ -807,7 +832,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) name++; type = REVERSED; } - if (!get_sha1(name, sha1)) { + if (!get_sha1_with_context(name, flags, sha1, &unused)) { if (verify) revs_count++; else diff --git a/builtin/revert.c b/builtin/revert.c index f9ed5bd5d0..56a2c36669 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -19,13 +19,13 @@ */ static const char * const revert_usage[] = { - N_("git revert [options] <commit-ish>..."), + N_("git revert [<options>] <commit-ish>..."), N_("git revert <subcommand>"), NULL }; static const char * const cherry_pick_usage[] = { - N_("git cherry-pick [options] <commit-ish>..."), + N_("git cherry-pick [<options>] <commit-ish>..."), N_("git cherry-pick <subcommand>"), NULL }; diff --git a/builtin/rm.c b/builtin/rm.c index 960634dd0c..3304bff42a 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -3,8 +3,8 @@ * * Copyright (C) Linus Torvalds 2006 */ -#include "cache.h" #include "builtin.h" +#include "lockfile.h" #include "dir.h" #include "cache-tree.h" #include "tree-walk.h" @@ -14,7 +14,7 @@ #include "pathspec.h" static const char * const builtin_rm_usage[] = { - N_("git rm [options] [--] <file>..."), + N_("git rm [<options>] [--] <file>..."), NULL }; @@ -65,7 +65,7 @@ static void error_removing_concrete_submodules(struct string_list *files, int *e Q_("the following submodule (or one of its nested " "submodules)\n" "uses a .git directory:", - "the following submodules (or one of its nested " + "the following submodules (or one of their nested " "submodules)\n" "use a .git directory:", files->nr), _("\n(use 'rm -rf' if you really want to remove " @@ -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..b961e5ae78 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -13,7 +13,7 @@ #include "sha1-array.h" static const char send_pack_usage[] = -"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n" +"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]\n" " --all and explicit <ref> specification are mutually exclusive."; static struct send_pack_args args; @@ -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++; @@ -153,6 +154,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.verbose = 1; continue; } + if (!strcmp(arg, "--signed")) { + args.push_cert = 1; + continue; + } if (!strcmp(arg, "--progress")) { progress = 1; continue; @@ -165,10 +170,18 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.use_thin_pack = 1; continue; } + if (!strcmp(arg, "--atomic")) { + args.atomic = 1; + continue; + } if (!strcmp(arg, "--stateless-rpc")) { args.stateless_rpc = 1; continue; } + if (!strcmp(arg, "--stdin")) { + from_stdin = 1; + continue; + } if (!strcmp(arg, "--helper-status")) { helper_status = 1; continue; @@ -201,6 +214,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/shortlog.c b/builtin/shortlog.c index 4b7e53623f..c0bab6aaa9 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -10,7 +10,7 @@ #include "parse-options.h" static char const * const shortlog_usage[] = { - N_("git shortlog [<options>] [<revision range>] [[--] [<path>...]]"), + N_("git shortlog [<options>] [<revision-range>] [[--] [<path>...]]"), NULL }; diff --git a/builtin/show-branch.c b/builtin/show-branch.c index d87317290c..e69fb7c489 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -6,8 +6,11 @@ #include "parse-options.h" static const char* show_branch_usage[] = { - N_("git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"), - N_("git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]"), + N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n" + " [--current] [--color[=<when>] | --no-color] [--sparse]\n" + " [--more=<n> | --list | --independent | --merge-base]\n" + " [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"), + N_("git show-branch (-g | --reflog)[=<n>[,<base>]] [--list] [<ref>]"), NULL }; @@ -563,7 +566,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb) default_arg[default_num++] = "show-branch"; } else if (default_alloc <= default_num + 1) { default_alloc = default_alloc * 3 / 2 + 20; - default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc); + REALLOC_ARRAY(default_arg, default_alloc); } default_arg[default_num++] = xstrdup(value); default_arg[default_num] = NULL; @@ -715,7 +718,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) } /* If nothing is specified, show all branches by default */ - if (ac + all_heads + all_remotes == 0) + if (ac <= topics && all_heads + all_remotes == 0) all_heads = 1; if (reflog) { @@ -723,11 +726,14 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) char nth_desc[256]; char *ref; int base = 0; + unsigned int flags = 0; if (ac == 0) { static const char *fake_av[2]; - fake_av[0] = resolve_refdup("HEAD", sha1, 1, NULL); + fake_av[0] = resolve_refdup("HEAD", + RESOLVE_REF_READING, + sha1, NULL); fake_av[1] = NULL; av = fake_av; ac = 1; @@ -749,18 +755,18 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) /* Ah, that is a date spec... */ unsigned long at; at = approxidate(reflog_base); - read_ref_at(ref, at, -1, sha1, NULL, + read_ref_at(ref, flags, at, -1, sha1, NULL, NULL, NULL, &base); } } for (i = 0; i < reflog; i++) { - char *logmsg, *m; + char *logmsg; const char *msg; unsigned long timestamp; int tz; - if (read_ref_at(ref, 0, base+i, sha1, &logmsg, + if (read_ref_at(ref, flags, 0, base+i, sha1, &logmsg, ×tamp, &tz, NULL)) { reflog = i; break; @@ -770,26 +776,26 @@ 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); else { while (0 < ac) { append_one_rev(*av); ac--; av++; } + if (all_heads + all_remotes) + snarf_refs(all_heads, all_remotes); } - head_p = resolve_ref_unsafe("HEAD", head_sha1, 1, NULL); + head_p = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + head_sha1, NULL); if (head_p) { head_len = strlen(head_p); memcpy(head, head_p, head_len + 1); diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 5ba1f30838..afb10309d6 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -7,7 +7,7 @@ #include "parse-options.h" static const char * const show_ref_usage[] = { - N_("git show-ref [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [pattern*] "), + N_("git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference] [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [<pattern>...]"), N_("git show-ref --exclude-existing[=pattern] < ref-list"), NULL }; diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index b6a711d319..ce0fde705c 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -4,8 +4,8 @@ #include "parse-options.h" static const char * const git_symbolic_ref_usage[] = { - N_("git symbolic-ref [options] name [ref]"), - N_("git symbolic-ref -d [-q] name"), + N_("git symbolic-ref [<options>] <name> [<ref>]"), + N_("git symbolic-ref -d [-q] <name>"), NULL }; @@ -13,7 +13,7 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print) { unsigned char sha1[20]; int flag; - const char *refname = resolve_ref_unsafe(HEAD, sha1, 0, &flag); + const char *refname = resolve_ref_unsafe(HEAD, 0, sha1, &flag); if (!refname) die("No such ref: %s", HEAD); diff --git a/builtin/tag.c b/builtin/tag.c index 6c7c6bde9d..6f07ac6b93 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -19,9 +19,9 @@ #include "column.h" static const char * const git_tag_usage[] = { - N_("git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]"), + N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"), N_("git tag -d <tagname>..."), - N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>] " + N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]" "\n\t\t[<pattern>...]"), N_("git tag -v <tagname>..."), NULL @@ -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; +} + +/* + * Mimicking the real stack, this stack lives on the heap, avoiding stack + * overflows. + * + * At each recursion step, the stack items points to the commits whose + * ancestors are to be inspected. + */ +struct stack { + int nr, alloc; + struct stack_entry { + struct commit *commit; + struct commit_list *parents; + } *stack; +}; + +static void push_to_stack(struct commit *candidate, struct stack *stack) +{ + int index = stack->nr++; + ALLOC_GROW(stack->stack, stack->nr, stack->alloc); + stack->stack[index].commit = candidate; + stack->stack[index].parents = candidate->parents; } -static int contains(struct commit *candidate, const struct commit_list *want) +static enum contains_result contains(struct commit *candidate, + const struct commit_list *want) { - return contains_recurse(candidate, want); + struct stack stack = { 0, 0, NULL }; + int result = contains_test(candidate, want); + + if (result != CONTAINS_UNKNOWN) + return result; + + push_to_stack(candidate, &stack); + while (stack.nr) { + struct stack_entry *entry = &stack.stack[stack.nr - 1]; + struct commit *commit = entry->commit; + struct commit_list *parents = entry->parents; + + if (!parents) { + commit->object.flags |= UNINTERESTING; + stack.nr--; + } + /* + * If we just popped the stack, parents->item has been marked, + * therefore contains_test will return a meaningful 0 or 1. + */ + else switch (contains_test(parents->item, want)) { + case CONTAINS_YES: + commit->object.flags |= TMP_MARK; + stack.nr--; + break; + case CONTAINS_NO: + entry->parents = parents->next; + break; + case CONTAINS_UNKNOWN: + push_to_stack(parents->item, &stack); + break; + } + } + free(stack.stack); + return contains_test(candidate, want); } static void show_tag_lines(const unsigned char *sha1, int lines) @@ -278,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) @@ -488,14 +576,15 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct strbuf ref = STRBUF_INIT; unsigned char object[20], prev[20]; const char *object_ref, *tag; - struct ref_lock *lock; 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; + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; struct option options[] = { OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'), { OPTION_INTEGER, 'n', NULL, &lines, N_("n"), @@ -516,13 +605,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_STRING('u', "local-user", &keyid, N_("key-id"), N_("use another key to sign the tag")), OPT__FORCE(&force, N_("replace the tag if exists")), + + OPT_GROUP(N_("Tag listing options")), 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 }, - - OPT_GROUP(N_("Tag listing options")), { OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"), N_("print only tags that contain the commit"), @@ -574,9 +663,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; @@ -641,14 +730,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (annotate) create_tag(object, tag, &buf, &opt, prev, object); - lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL); - if (!lock) - die(_("%s: cannot lock the ref"), ref.buf); - if (write_ref_sha1(lock, object, NULL) < 0) - die(_("%s: cannot update the ref"), ref.buf); + transaction = ref_transaction_begin(&err); + if (!transaction || + ref_transaction_update(transaction, ref.buf, object, prev, + 0, NULL, &err) || + ref_transaction_commit(transaction, &err)) + die("%s", err.buf); + ref_transaction_free(transaction); if (force && !is_null_sha1(prev) && hashcmp(prev, object)) printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV)); + strbuf_release(&err); strbuf_release(&buf); strbuf_release(&ref); return 0; diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 99cde45879..ac6667242c 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -91,7 +91,7 @@ static void use(int bytes) static void *get_data(unsigned long size) { git_zstream stream; - void *buf = xmalloc(size); + void *buf = xmallocz(size); memset(&stream, 0, sizeof(stream)); @@ -164,10 +164,10 @@ static unsigned nr_objects; * Called only from check_object() after it verified this object * is Ok. */ -static void write_cached_object(struct object *obj) +static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf) { unsigned char sha1[20]; - struct obj_buffer *obj_buf = lookup_object_buffer(obj); + if (write_sha1_file(obj_buf->buffer, obj_buf->size, typename(obj->type), sha1) < 0) die("failed to write object %s", sha1_to_hex(obj->sha1)); obj->flags |= FLAG_WRITTEN; @@ -180,6 +180,8 @@ static void write_cached_object(struct object *obj) */ static int check_object(struct object *obj, int type, void *data) { + struct obj_buffer *obj_buf; + if (!obj) return 1; @@ -198,11 +200,15 @@ static int check_object(struct object *obj, int type, void *data) return 0; } - if (fsck_object(obj, 1, fsck_error_function)) + obj_buf = lookup_object_buffer(obj); + if (!obj_buf) + die("Whoops! Cannot find object '%s'", sha1_to_hex(obj->sha1)); + if (fsck_object(obj, obj_buf->buffer, obj_buf->size, 1, + fsck_error_function)) die("Error in object"); if (fsck_walk(obj, check_object, NULL)) die("Error on reachable objects of %s", sha1_to_hex(obj->sha1)); - write_cached_object(obj); + write_cached_object(obj, obj_buf); return 0; } diff --git a/builtin/update-index.c b/builtin/update-index.c index ebea285e1b..0665b31ea1 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "lockfile.h" #include "quote.h" #include "cache-tree.h" #include "tree-walk.h" @@ -13,6 +14,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 +57,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 +270,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: @@ -396,7 +400,7 @@ static void read_index_info(int line_termination) } static const char * const update_index_usage[] = { - N_("git update-index [options] [--] [<file>...]"), + N_("git update-index [<options>] [--] [<file>...]"), NULL }; @@ -528,10 +532,9 @@ static int do_unresolve(int ac, const char **av, for (i = 1; i < ac; i++) { const char *arg = av[i]; - const char *p = prefix_path(prefix, prefix_length, arg); + char *p = prefix_path(prefix, prefix_length, arg); err |= unresolve_one(p); - if (p < arg || p > arg + strlen(arg)) - free((char *)p); + free(p); } return err; } @@ -580,6 +583,7 @@ static int do_reupdate(int ac, const char **av, path = xstrdup(ce->name); update_one(path); free(path); + free(old); if (save_nr != active_nr) goto redo; } @@ -743,6 +747,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 +830,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() }; @@ -863,14 +870,14 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) case PARSE_OPT_DONE: { const char *path = ctx.argv[0]; - const char *p; + char *p; setup_work_tree(); p = prefix_path(prefix, prefix_length, path); update_one(p); if (set_executable_bit) chmod_path(set_executable_bit, p); - free((char *)p); + free(p); ctx.argc--; ctx.argv++; break; @@ -892,7 +899,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; } @@ -901,7 +908,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) setup_work_tree(); while (strbuf_getline(&buf, stdin, line_termination) != EOF) { - const char *p; + char *p; if (line_termination && buf.buf[0] == '"') { strbuf_reset(&nbuf); if (unquote_c_style(&nbuf, buf.buf, NULL)) @@ -912,20 +919,33 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) update_one(p); if (set_executable_bit) chmod_path(set_executable_bit, p); - free((char *)p); + free(p); } strbuf_release(&nbuf); 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); + unable_to_lock_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..3d79a46b03 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -6,251 +6,354 @@ #include "argv-array.h" static const char * const git_update_ref_usage[] = { - N_("git update-ref [options] -d <refname> [<oldval>]"), - N_("git update-ref [options] <refname> <newval> [<oldval>]"), - N_("git update-ref [options] --stdin [-z]"), + N_("git update-ref [<options>] -d <refname> [<old-val>]"), + N_("git update-ref [<options>] <refname> <new-val> [<old-val>]"), + N_("git update-ref [<options>] --stdin [-z]"), NULL }; -static int updates_alloc; -static int updates_count; -static const struct ref_update **updates; - 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 const char *msg; + +/* + * 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 ref_transaction *transaction, + 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; + struct strbuf err = STRBUF_INIT; + char *refname; + unsigned char new_sha1[20]; + unsigned char old_sha1[20]; + int have_old; - update = update_alloc(); + refname = parse_refname(input, &next); + if (!refname) + die("update: missing <ref>"); - if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) - update_store_ref_name(update, ref.buf); - else - die("update line missing <ref>"); + if (parse_next_sha1(input, &next, new_sha1, "update", refname, + PARSE_SHA1_ALLOW_EMPTY)) + die("update %s: missing <newvalue>", refname); - if ((next = parse_next_arg(next, &newvalue)) != NULL) - update_store_new_sha1(update, newvalue.buf); - else - die("update %s missing <newvalue>", ref.buf); + have_old = !parse_next_sha1(input, &next, old_sha1, "update", refname, + PARSE_SHA1_OLD); - if ((next = parse_next_arg(next, &oldvalue)) != NULL) - update_store_old_sha1(update, oldvalue.buf); - else if(!line_termination) - die("update %s missing [<oldvalue>] NUL", ref.buf); + if (*next != line_termination) + die("update %s: extra input: %s", refname, next); - if (next && *next) - die("update %s has extra input: %s", ref.buf, next); + if (ref_transaction_update(transaction, refname, + new_sha1, have_old ? old_sha1 : NULL, + update_flags, msg, &err)) + die("%s", err.buf); + + update_flags = 0; + free(refname); + strbuf_release(&err); + + return next; } -static void parse_cmd_create(const char *next) +static const char *parse_cmd_create(struct ref_transaction *transaction, + struct strbuf *input, const char *next) { - struct strbuf ref = STRBUF_INIT; - struct strbuf newvalue = STRBUF_INIT; - struct ref_update *update; + struct strbuf err = STRBUF_INIT; + char *refname; + unsigned char new_sha1[20]; - update = update_alloc(); - update->have_old = 1; + refname = parse_refname(input, &next); + if (!refname) + die("create: missing <ref>"); - if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) - update_store_ref_name(update, ref.buf); - else - die("create line missing <ref>"); + if (parse_next_sha1(input, &next, new_sha1, "create", refname, 0)) + die("create %s: missing <newvalue>", refname); - if ((next = parse_next_arg(next, &newvalue)) != NULL) - update_store_new_sha1(update, newvalue.buf); - else - die("create %s missing <newvalue>", ref.buf); - if (is_null_sha1(update->new_sha1)) - die("create %s given zero new value", ref.buf); + if (is_null_sha1(new_sha1)) + die("create %s: zero <newvalue>", refname); + + if (*next != line_termination) + die("create %s: extra input: %s", refname, next); + + if (ref_transaction_create(transaction, refname, new_sha1, + update_flags, msg, &err)) + die("%s", err.buf); + + update_flags = 0; + free(refname); + strbuf_release(&err); - 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 ref_transaction *transaction, + struct strbuf *input, const char *next) { - struct strbuf ref = STRBUF_INIT; - struct strbuf oldvalue = STRBUF_INIT; - struct ref_update *update; + struct strbuf err = STRBUF_INIT; + char *refname; + unsigned char old_sha1[20]; + int have_old; + + refname = parse_refname(input, &next); + if (!refname) + die("delete: 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; + } - update = update_alloc(); + if (*next != line_termination) + die("delete %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("delete line missing <ref>"); + if (ref_transaction_delete(transaction, refname, + have_old ? old_sha1 : NULL, + update_flags, msg, &err)) + die("%s", err.buf); - 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); + update_flags = 0; + free(refname); + strbuf_release(&err); - if (next && *next) - die("delete %s has extra input: %s", ref.buf, next); + return next; } -static void parse_cmd_verify(const char *next) +static const char *parse_cmd_verify(struct ref_transaction *transaction, + struct strbuf *input, const char *next) { - struct strbuf ref = STRBUF_INIT; - struct strbuf value = STRBUF_INIT; - struct ref_update *update; + struct strbuf err = STRBUF_INIT; + char *refname; + unsigned char old_sha1[20]; - update = update_alloc(); + refname = parse_refname(input, &next); + if (!refname) + die("verify: missing <ref>"); - 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 (parse_next_sha1(input, &next, old_sha1, "verify", refname, + PARSE_SHA1_OLD)) + hashclr(old_sha1); + + if (*next != line_termination) + die("verify %s: extra input: %s", refname, next); - 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); + if (ref_transaction_verify(transaction, refname, old_sha1, + update_flags, &err)) + die("%s", err.buf); + + update_flags = 0; + free(refname); + strbuf_release(&err); - 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) +static void update_refs_stdin(struct ref_transaction *transaction) { - 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(transaction, &input, next + 7); + else if (starts_with(next, "create ")) + next = parse_cmd_create(transaction, &input, next + 7); + else if (starts_with(next, "delete ")) + next = parse_cmd_delete(transaction, &input, next + 7); + else if (starts_with(next, "verify ")) + next = parse_cmd_verify(transaction, &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) { - const char *refname, *oldval, *msg = NULL; + const char *refname, *oldval; unsigned char sha1[20], oldsha1[20]; - int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0; + int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0; + unsigned int flags = 0; struct option options[] = { OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")), OPT_BOOL('d', NULL, &delete, N_("delete the reference")), @@ -268,12 +371,22 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) die("Refusing to perform update with empty message."); if (read_stdin) { + struct strbuf err = STRBUF_INIT; + struct ref_transaction *transaction; + + transaction = ref_transaction_begin(&err); + if (!transaction) + die("%s", err.buf); 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); + update_refs_stdin(transaction); + if (ref_transaction_commit(transaction, &err)) + die("%s", err.buf); + ref_transaction_free(transaction); + strbuf_release(&err); + return 0; } if (end_null) @@ -305,5 +418,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..ec0c4e3d83 --- /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..c94e156932 100644 --- a/builtin/verify-pack.c +++ b/builtin/verify-pack.c @@ -8,7 +8,7 @@ static int verify_one_pack(const char *path, unsigned int flags) { - struct child_process index_pack; + struct child_process index_pack = CHILD_PROCESS_INIT; const char *argv[] = {"index-pack", NULL, NULL, NULL }; struct strbuf arg = STRBUF_INIT; int verbose = flags & VERIFY_PACK_VERBOSE; @@ -27,13 +27,11 @@ 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)); index_pack.argv = argv; index_pack.git_cmd = 1; @@ -53,7 +51,7 @@ static int verify_one_pack(const char *path, unsigned int flags) } static const char * const verify_pack_usage[] = { - N_("git verify-pack [-v|--verbose] [-s|--stat-only] <pack>..."), + N_("git verify-pack [-v | --verbose] [-s | --stat-only] <pack>..."), NULL }; diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 9cdf332333..53c68fce3a 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -14,7 +14,7 @@ #include "gpg-interface.h" static const char * const verify_tag_usage[] = { - N_("git verify-tag [-v|--verbose] <tag>..."), + N_("git verify-tag [-v | --verbose] <tag>..."), NULL }; |