diff options
Diffstat (limited to 'builtin')
35 files changed, 1731 insertions, 534 deletions
diff --git a/builtin/add.c b/builtin/add.c index df5135bf62..4bd98b799e 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -63,7 +63,6 @@ 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)) { diff --git a/builtin/apply.c b/builtin/apply.c index 146be97a1a..54aba4e351 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -1638,6 +1638,9 @@ static int parse_fragment(const char *line, unsigned long size, } if (oldlines || newlines) return -1; + if (!deleted && !added) + return -1; + fragment->leading = leading; fragment->trailing = trailing; diff --git a/builtin/blame.c b/builtin/blame.c index b3e948e757..04e4864f8b 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -6,6 +6,7 @@ */ #include "cache.h" +#include "refs.h" #include "builtin.h" #include "blob.h" #include "commit.h" @@ -50,7 +51,7 @@ static int xdl_opts; static int abbrev = -1; static int no_whole_file_rename; -static enum date_mode blame_date_mode = DATE_ISO8601; +static struct date_mode blame_date_mode = { DATE_ISO8601 }; static size_t blame_date_width; static struct string_list mailmap; @@ -1827,7 +1828,7 @@ static const char *format_time(unsigned long time, const char *tz_str, size_t time_width; int tz; tz = atoi(tz_str); - time_str = show_date(time, tz, blame_date_mode); + time_str = show_date(time, tz, &blame_date_mode); strbuf_addstr(&time_buf, time_str); /* * Add space paddings to time_buf to display a fixed width @@ -2176,10 +2177,18 @@ static int git_blame_config(const char *var, const char *value, void *cb) blank_boundary = git_config_bool(var, value); return 0; } + if (!strcmp(var, "blame.showemail")) { + int *output_option = cb; + if (git_config_bool(var, value)) + *output_option |= OUTPUT_SHOW_EMAIL; + else + *output_option &= ~OUTPUT_SHOW_EMAIL; + return 0; + } if (!strcmp(var, "blame.date")) { if (!value) return config_error_nonbool(var); - blame_date_mode = parse_date_format(value); + parse_date_format(value, &blame_date_mode); return 0; } @@ -2520,7 +2529,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) unsigned int range_i; long anchor; - git_config(git_blame_config, NULL); + git_config(git_blame_config, &output_option); init_revisions(&revs, NULL); revs.date_mode = blame_date_mode; DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV); @@ -2561,13 +2570,13 @@ parse_done: if (cmd_is_annotate) { output_option |= OUTPUT_ANNOTATE_COMPAT; - blame_date_mode = DATE_ISO8601; + blame_date_mode.type = DATE_ISO8601; } else { blame_date_mode = revs.date_mode; } /* The maximum width used to show the dates */ - switch (blame_date_mode) { + switch (blame_date_mode.type) { case DATE_RFC2822: blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700"); break; @@ -2596,6 +2605,9 @@ parse_done: case DATE_NORMAL: blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700"); break; + case DATE_STRFTIME: + blame_date_width = strlen(show_date(0, 0, &blame_date_mode)) + 1; /* add the null */ + break; } blame_date_width -= 1; /* strip the null */ diff --git a/builtin/branch.c b/builtin/branch.c index c443cd8251..4fc8beb23c 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -160,7 +160,7 @@ static int branch_merged(int kind, const char *name, } static int check_branch_commit(const char *branchname, const char *refname, - unsigned char *sha1, struct commit *head_rev, + const unsigned char *sha1, struct commit *head_rev, int kinds, int force) { struct commit *rev = lookup_commit_reference(sha1); @@ -253,7 +253,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, continue; } - if (delete_ref(name, sha1, REF_NODEREF)) { + if (delete_ref(name, is_null_sha1(sha1) ? NULL : sha1, + REF_NODEREF)) { error(remote_branch ? _("Error deleting remote-tracking branch '%s'") : _("Error deleting branch '%s'"), diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 049a95f1f1..07baad1e59 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -9,6 +9,16 @@ #include "userdiff.h" #include "streaming.h" #include "tree-walk.h" +#include "sha1-array.h" + +struct batch_options { + int enabled; + int follow_symlinks; + int print_contents; + int buffer_output; + int all_objects; + const char *format; +}; static int cat_one_file(int opt, const char *exp_type, const char *obj_name, int unknown_type) @@ -204,14 +214,25 @@ static size_t expand_format(struct strbuf *sb, const char *start, void *data) return end - start + 1; } -static void print_object_or_die(int fd, struct expand_data *data) +static void batch_write(struct batch_options *opt, const void *data, int len) +{ + if (opt->buffer_output) { + if (fwrite(data, 1, len, stdout) != len) + die_errno("unable to write to stdout"); + } else + write_or_die(1, data, len); +} + +static void print_object_or_die(struct batch_options *opt, struct expand_data *data) { const unsigned char *sha1 = data->sha1; assert(data->info.typep); if (data->type == OBJ_BLOB) { - if (stream_blob_to_fd(fd, sha1, NULL, 0) < 0) + if (opt->buffer_output) + fflush(stdout); + if (stream_blob_to_fd(1, sha1, NULL, 0) < 0) die("unable to stream %s to stdout", sha1_to_hex(sha1)); } else { @@ -227,29 +248,40 @@ static void print_object_or_die(int fd, struct expand_data *data) if (data->info.sizep && size != data->size) die("object %s changed size!?", sha1_to_hex(sha1)); - write_or_die(fd, contents, size); + batch_write(opt, contents, size); free(contents); } } -struct batch_options { - int enabled; - int follow_symlinks; - int print_contents; - const char *format; -}; - -static int batch_one_object(const char *obj_name, struct batch_options *opt, - struct expand_data *data) +static void batch_object_write(const char *obj_name, struct batch_options *opt, + struct expand_data *data) { struct strbuf buf = STRBUF_INIT; + + if (sha1_object_info_extended(data->sha1, &data->info, LOOKUP_REPLACE_OBJECT) < 0) { + printf("%s missing\n", obj_name ? obj_name : sha1_to_hex(data->sha1)); + fflush(stdout); + return; + } + + strbuf_expand(&buf, opt->format, expand_format, data); + strbuf_addch(&buf, '\n'); + batch_write(opt, buf.buf, buf.len); + strbuf_release(&buf); + + if (opt->print_contents) { + print_object_or_die(opt, data); + batch_write(opt, "\n", 1); + } +} + +static void batch_one_object(const char *obj_name, struct batch_options *opt, + struct expand_data *data) +{ struct object_context ctx; int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0; enum follow_symlinks_result result; - if (!obj_name) - return 1; - result = get_sha1_with_context(obj_name, flags, data->sha1, &ctx); if (result != FOUND) { switch (result) { @@ -274,7 +306,7 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt, break; } fflush(stdout); - return 0; + return; } if (ctx.mode == 0) { @@ -282,24 +314,38 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt, (uintmax_t)ctx.symlink_path.len, ctx.symlink_path.buf); fflush(stdout); - return 0; + return; } - if (sha1_object_info_extended(data->sha1, &data->info, LOOKUP_REPLACE_OBJECT) < 0) { - printf("%s missing\n", obj_name); - fflush(stdout); - return 0; - } + batch_object_write(obj_name, opt, data); +} - strbuf_expand(&buf, opt->format, expand_format, data); - strbuf_addch(&buf, '\n'); - write_or_die(1, buf.buf, buf.len); - strbuf_release(&buf); +struct object_cb_data { + struct batch_options *opt; + struct expand_data *expand; +}; - if (opt->print_contents) { - print_object_or_die(1, data); - write_or_die(1, "\n", 1); - } +static void batch_object_cb(const unsigned char sha1[20], void *vdata) +{ + struct object_cb_data *data = vdata; + hashcpy(data->expand->sha1, sha1); + batch_object_write(NULL, data->opt, data->expand); +} + +static int batch_loose_object(const unsigned char *sha1, + const char *path, + void *data) +{ + sha1_array_append(data, sha1); + return 0; +} + +static int batch_packed_object(const unsigned char *sha1, + struct packed_git *pack, + uint32_t pos, + void *data) +{ + sha1_array_append(data, sha1); return 0; } @@ -330,6 +376,21 @@ static int batch_objects(struct batch_options *opt) if (opt->print_contents) data.info.typep = &data.type; + if (opt->all_objects) { + struct sha1_array sa = SHA1_ARRAY_INIT; + struct object_cb_data cb; + + for_each_loose_object(batch_loose_object, &sa, 0); + for_each_packed_object(batch_packed_object, &sa, 0); + + cb.opt = opt; + cb.expand = &data; + sha1_array_for_each_unique(&sa, batch_object_cb, &cb); + + sha1_array_clear(&sa); + return 0; + } + /* * We are going to call get_sha1 on a potentially very large number of * objects. In most large cases, these will be actual object sha1s. The @@ -355,9 +416,7 @@ static int batch_objects(struct batch_options *opt) data.rest = p; } - retval = batch_one_object(buf.buf, opt, &data); - if (retval) - break; + batch_one_object(buf.buf, opt, &data); } strbuf_release(&buf); @@ -412,8 +471,9 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) 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, + OPT_BOOL(0, "allow-unknown-type", &unknown_type, N_("allow -s and -t to work with broken/corrupt objects")), + OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")), { OPTION_CALLBACK, 0, "batch", &batch, "format", N_("show info and content of objects fed from the standard input"), PARSE_OPT_OPTARG, batch_option_callback }, @@ -422,6 +482,8 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, batch_option_callback }, OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks, N_("follow in-tree symlinks (used with --batch or --batch-check)")), + OPT_BOOL(0, "batch-all-objects", &batch.all_objects, + N_("show all objects with --batch or --batch-check")), OPT_END() }; @@ -446,7 +508,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) usage_with_options(cat_file_usage, options); } - if (batch.follow_symlinks && !batch.enabled) { + if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) { usage_with_options(cat_file_usage, options); } diff --git a/builtin/checkout.c b/builtin/checkout.c index 9b49f0e413..9ff5ca4cbe 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -19,8 +19,6 @@ #include "ll-merge.h" #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>"), @@ -51,8 +49,6 @@ struct checkout_opts { struct pathspec pathspec; struct tree *source_tree; - const char *new_worktree; - const char **saved_argv; int new_worktree_mode; }; @@ -273,9 +269,6 @@ 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); @@ -619,22 +612,20 @@ static void update_refs_for_switch(const struct checkout_opts *opts, if (opts->new_branch) { if (opts->new_orphan_branch) { if (opts->new_branch_log && !log_all_ref_updates) { - int temp; - 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; - ret = log_ref_setup(ref_name, &log_file); - log_all_ref_updates = temp; - strbuf_release(&log_file); + char *refname; + struct strbuf err = STRBUF_INIT; + + refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch); + ret = safe_create_reflog(refname, 1, &err); + free(refname); if (ret) { - fprintf(stderr, _("Can not do reflog for '%s'\n"), - opts->new_orphan_branch); + fprintf(stderr, _("Can not do reflog for '%s': %s\n"), + opts->new_orphan_branch, err.buf); + strbuf_release(&err); return; } + strbuf_release(&err); } } else @@ -850,138 +841,6 @@ 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")) { @@ -1110,7 +969,6 @@ static int parse_branchname_arg(int argc, const char **argv, { 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; @@ -1231,17 +1089,6 @@ 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 */ @@ -1321,8 +1168,16 @@ 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->path && !opts->force_detach && !opts->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); + } if (!new->commit && opts->new_branch) { unsigned char rev[20]; @@ -1366,8 +1221,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) 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>'")), - 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(), @@ -1378,9 +1231,6 @@ 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); @@ -1389,13 +1239,7 @@ 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 */ diff --git a/builtin/clean.c b/builtin/clean.c index 6dcb72e644..df53def63f 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -10,7 +10,6 @@ #include "cache.h" #include "dir.h" #include "parse-options.h" -#include "refs.h" #include "string-list.h" #include "quote.h" #include "column.h" @@ -148,6 +147,31 @@ static int exclude_cb(const struct option *opt, const char *arg, int unset) return 0; } +/* + * Return 1 if the given path is the root of a git repository or + * submodule else 0. Will not return 1 for bare repositories with the + * exception of creating a bare repository in "foo/.git" and calling + * is_git_repository("foo"). + */ +static int is_git_repository(struct strbuf *path) +{ + int ret = 0; + int gitfile_error; + size_t orig_path_len = path->len; + assert(orig_path_len != 0); + if (path->buf[orig_path_len - 1] != '/') + strbuf_addch(path, '/'); + strbuf_addstr(path, ".git"); + if (read_gitfile_gently(path->buf, &gitfile_error) || is_git_directory(path->buf)) + ret = 1; + if (gitfile_error == READ_GITFILE_ERR_OPEN_FAILED || + gitfile_error == READ_GITFILE_ERR_READ_FAILED) + ret = 1; /* This could be a real .git file, take the + * safe option and avoid cleaning */ + strbuf_setlen(path, orig_path_len); + return ret; +} + static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, int dry_run, int quiet, int *dir_gone) { @@ -155,13 +179,11 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, struct strbuf quoted = STRBUF_INIT; struct dirent *e; int res = 0, ret = 0, gone = 1, original_len = path->len, len; - unsigned char submodule_head[20]; struct string_list dels = STRING_LIST_INIT_DUP; *dir_gone = 1; - if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && - !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) { + if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && is_git_repository(path)) { if (!quiet) { quote_path_relative(path->buf, prefix, "ed); printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir), diff --git a/builtin/clone.c b/builtin/clone.c index 00535d0178..303a3a7eb6 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -147,6 +147,7 @@ static char *get_repo_path(const char *repo, int *is_bundle) static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) { const char *end = repo + strlen(repo), *start; + size_t len; char *dir; /* @@ -173,20 +174,12 @@ static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) /* * Strip .{bundle,git}. */ - if (is_bundle) { - if (end - start > 7 && !strncmp(end - 7, ".bundle", 7)) - end -= 7; - } else { - if (end - start > 4 && !strncmp(end - 4, ".git", 4)) - end -= 4; - } + strip_suffix(start, is_bundle ? ".bundle" : ".git" , &len); - if (is_bare) { - struct strbuf result = STRBUF_INIT; - strbuf_addf(&result, "%.*s.git", (int)(end - start), start); - dir = strbuf_detach(&result, NULL); - } else - dir = xstrndup(start, end - start); + if (is_bare) + dir = xstrfmt("%.*s.git", (int)len, start); + else + dir = xstrndup(start, len); /* * Replace sequences of 'control' characters and whitespace * with one ascii space, remove leading and trailing spaces. @@ -491,16 +484,26 @@ static void write_remote_refs(const struct ref *local_refs) { const struct ref *r; - lock_packed_refs(LOCK_DIE_ON_ERROR); + struct ref_transaction *t; + struct strbuf err = STRBUF_INIT; + + t = ref_transaction_begin(&err); + if (!t) + die("%s", err.buf); for (r = local_refs; r; r = r->next) { if (!r->peer_ref) continue; - add_packed_ref(r->peer_ref->name, r->old_sha1); + if (ref_transaction_create(t, r->peer_ref->name, r->old_sha1, + 0, NULL, &err)) + die("%s", err.buf); } - if (commit_packed_refs()) - die_errno("unable to overwrite old ref-pack file"); + if (initial_ref_transaction_commit(t, &err)) + die("%s", err.buf); + + strbuf_release(&err); + ref_transaction_free(t); } static void write_followtags(const struct ref *refs, const char *msg) diff --git a/builtin/commit.c b/builtin/commit.c index 254477fd1d..7314f33885 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -856,7 +856,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, _("%s" "Date: %s"), ident_shown++ ? "" : "\n", - show_ident_date(&ai, DATE_NORMAL)); + show_ident_date(&ai, DATE_MODE(NORMAL))); if (!committer_ident_sufficiently_given()) status_printf_ln(s, GIT_COLOR_NORMAL, @@ -1046,7 +1046,7 @@ static const char *find_author_by_nickname(const char *name) commit = get_revision(&revs); if (commit) { struct pretty_print_context ctx = {0}; - ctx.date_mode = DATE_NORMAL; + ctx.date_mode.type = DATE_NORMAL; strbuf_release(&buf); format_commit_message(commit, "%aN <%aE>", &buf, &ctx); clear_mailmap(&mailmap); diff --git a/builtin/fast-export.c b/builtin/fast-export.c index b8182c241d..d23f3beba9 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -5,6 +5,7 @@ */ #include "builtin.h" #include "cache.h" +#include "refs.h" #include "commit.h" #include "object.h" #include "tag.h" diff --git a/builtin/fetch.c b/builtin/fetch.c index 8d5b2dba2b..34b6f5ebca 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -790,20 +790,29 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map, if (4 < i && !strncmp(".git", url + i - 3, 4)) url_len = i - 3; - for (ref = stale_refs; ref; ref = ref->next) { - if (!dry_run) - result |= delete_ref(ref->name, NULL, 0); - if (verbosity >= 0 && !shown_url) { - fprintf(stderr, _("From %.*s\n"), url_len, url); - shown_url = 1; - } - if (verbosity >= 0) { + if (!dry_run) { + struct string_list refnames = STRING_LIST_INIT_NODUP; + + for (ref = stale_refs; ref; ref = ref->next) + string_list_append(&refnames, ref->name); + + result = delete_refs(&refnames); + string_list_clear(&refnames, 0); + } + + if (verbosity >= 0) { + for (ref = stale_refs; ref; ref = ref->next) { + if (!shown_url) { + fprintf(stderr, _("From %.*s\n"), url_len, url); + shown_url = 1; + } fprintf(stderr, " x %-*s %-*s -> %s\n", TRANSPORT_SUMMARY(_("[deleted]")), REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name)); warn_dangling_symref(stderr, dangling_msg, ref->name); } } + free(url); free_refs(stale_refs); return result; diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 05f4c26311..4ba7f282a5 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "refs.h" #include "commit.h" #include "diff.h" #include "revision.h" diff --git a/builtin/fsck.c b/builtin/fsck.c index 4e8e2ee5b7..f4b87e9b33 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -23,8 +23,11 @@ static int show_tags; static int show_unreachable; static int include_reflogs = 1; static int check_full = 1; +static int connectivity_only; static int check_strict; static int keep_cache_objects; +static struct fsck_options fsck_walk_options = FSCK_OPTIONS_DEFAULT; +static struct fsck_options fsck_obj_options = FSCK_OPTIONS_DEFAULT; static struct object_id head_oid; static const char *head_points_at; static int errors_found; @@ -44,39 +47,52 @@ static int show_dangling = 1; #define DIRENT_SORT_HINT(de) ((de)->d_ino) #endif -static void objreport(struct object *obj, const char *severity, - const char *err, va_list params) +static int fsck_config(const char *var, const char *value, void *cb) { - fprintf(stderr, "%s in %s %s: ", - severity, typename(obj->type), sha1_to_hex(obj->sha1)); - vfprintf(stderr, err, params); - fputs("\n", stderr); + if (strcmp(var, "fsck.skiplist") == 0) { + const char *path; + struct strbuf sb = STRBUF_INIT; + + if (git_config_pathname(&path, var, value)) + return 1; + strbuf_addf(&sb, "skiplist=%s", path); + free((char *)path); + fsck_set_msg_types(&fsck_obj_options, sb.buf); + strbuf_release(&sb); + return 0; + } + + if (skip_prefix(var, "fsck.", &var)) { + fsck_set_msg_type(&fsck_obj_options, var, value); + return 0; + } + + return git_default_config(var, value, cb); } -__attribute__((format (printf, 2, 3))) -static int objerror(struct object *obj, const char *err, ...) +static void objreport(struct object *obj, const char *msg_type, + const char *err) +{ + fprintf(stderr, "%s in %s %s: %s\n", + msg_type, typename(obj->type), sha1_to_hex(obj->sha1), err); +} + +static int objerror(struct object *obj, const char *err) { - va_list params; - va_start(params, err); errors_found |= ERROR_OBJECT; - objreport(obj, "error", err, params); - va_end(params); + objreport(obj, "error", err); return -1; } -__attribute__((format (printf, 3, 4))) -static int fsck_error_func(struct object *obj, int type, const char *err, ...) +static int fsck_error_func(struct object *obj, int type, const char *message) { - va_list params; - va_start(params, err); - objreport(obj, (type == FSCK_WARN) ? "warning" : "error", err, params); - va_end(params); + objreport(obj, (type == FSCK_WARN) ? "warning" : "error", message); return (type == FSCK_WARN) ? 0 : 1; } static struct object_array pending; -static int mark_object(struct object *obj, int type, void *data) +static int mark_object(struct object *obj, int type, void *data, struct fsck_options *options) { struct object *parent = data; @@ -119,7 +135,7 @@ static int mark_object(struct object *obj, int type, void *data) static void mark_object_reachable(struct object *obj) { - mark_object(obj, OBJ_ANY, NULL); + mark_object(obj, OBJ_ANY, NULL, NULL); } static int traverse_one_object(struct object *obj) @@ -132,7 +148,7 @@ static int traverse_one_object(struct object *obj) if (parse_tree(tree) < 0) return 1; /* error already displayed */ } - result = fsck_walk(obj, mark_object, obj); + result = fsck_walk(obj, obj, &fsck_walk_options); if (tree) free_tree_buffer(tree); return result; @@ -158,7 +174,7 @@ static int traverse_reachable(void) return !!result; } -static int mark_used(struct object *obj, int type, void *data) +static int mark_used(struct object *obj, int type, void *data, struct fsck_options *options) { if (!obj) return 1; @@ -179,6 +195,8 @@ static void check_reachable_object(struct object *obj) if (!(obj->flags & HAS_OBJ)) { if (has_sha1_pack(obj->sha1)) return; /* it is in pack - forget about it */ + if (connectivity_only && has_sha1_file(obj->sha1)) + return; printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); errors_found |= ERROR_REACHABLE; return; @@ -296,9 +314,9 @@ static int fsck_obj(struct object *obj) fprintf(stderr, "Checking %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); - if (fsck_walk(obj, mark_used, NULL)) + if (fsck_walk(obj, NULL, &fsck_obj_options)) objerror(obj, "broken links"); - if (fsck_object(obj, NULL, 0, check_strict, fsck_error_func)) + if (fsck_object(obj, NULL, 0, &fsck_obj_options)) return -1; if (obj->type == OBJ_TREE) { @@ -451,35 +469,41 @@ static void fsck_dir(int i, char *path) static int default_refs; +static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1) +{ + struct object *obj; + + if (!is_null_sha1(sha1)) { + obj = lookup_object(sha1); + if (obj) { + obj->used = 1; + mark_object_reachable(obj); + } else { + error("%s: invalid reflog entry %s", refname, sha1_to_hex(sha1)); + errors_found |= ERROR_REACHABLE; + } + } +} + static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { - struct object *obj; + const char *refname = cb_data; if (verbose) fprintf(stderr, "Checking reflog %s->%s\n", sha1_to_hex(osha1), sha1_to_hex(nsha1)); - if (!is_null_sha1(osha1)) { - obj = lookup_object(osha1); - if (obj) { - obj->used = 1; - mark_object_reachable(obj); - } - } - obj = lookup_object(nsha1); - if (obj) { - obj->used = 1; - mark_object_reachable(obj); - } + fsck_handle_reflog_sha1(refname, osha1); + fsck_handle_reflog_sha1(refname, nsha1); return 0; } static int fsck_handle_reflog(const char *logname, const struct object_id *oid, int flag, void *cb_data) { - for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL); + for_each_reflog_ent(logname, fsck_handle_reflog_ent, (void *)logname); return 0; } @@ -615,6 +639,7 @@ static struct option fsck_opts[] = { OPT_BOOL(0, "cache", &keep_cache_objects, N_("make index objects head nodes")), OPT_BOOL(0, "reflogs", &include_reflogs, N_("make reflogs head nodes (default)")), OPT_BOOL(0, "full", &check_full, N_("also consider packs and alternate objects")), + OPT_BOOL(0, "connectivity-only", &connectivity_only, N_("check only connectivity")), OPT_BOOL(0, "strict", &check_strict, N_("enable more strict checking")), OPT_BOOL(0, "lost-found", &write_lost_and_found, N_("write dangling objects in .git/lost-found")), @@ -632,6 +657,12 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); + fsck_walk_options.walk = mark_object; + fsck_obj_options.walk = mark_used; + fsck_obj_options.error_func = fsck_error_func; + if (check_strict) + fsck_obj_options.strict = 1; + if (show_progress == -1) show_progress = isatty(2); if (verbose) @@ -642,8 +673,11 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) include_reflogs = 0; } + git_config(fsck_config, NULL); + fsck_head_link(); - fsck_object_dir(get_object_directory()); + if (!connectivity_only) + fsck_object_dir(get_object_directory()); prepare_alt_odb(); for (alt = alt_odb_list; alt; alt = alt->next) { diff --git a/builtin/gc.c b/builtin/gc.c index 36fe33300f..4957c39032 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -293,7 +293,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) 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_worktrees, "prune", "--worktrees", "--expire", NULL); + argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL); argv_array_pushl(&rerere, "rerere", "gc", NULL); gc_config(); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 7ea2020d82..3f10840441 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -75,6 +75,7 @@ static int nr_threads; static int from_stdin; static int strict; static int do_fsck_object; +static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; static int verbose; static int show_stat; static int check_self_contained_and_connected; @@ -192,7 +193,7 @@ static void cleanup_thread(void) #endif -static int mark_link(struct object *obj, int type, void *data) +static int mark_link(struct object *obj, int type, void *data, struct fsck_options *options) { if (!obj) return -1; @@ -616,7 +617,9 @@ static int compare_ofs_delta_bases(off_t offset1, off_t offset2, int cmp = type1 - type2; if (cmp) return cmp; - return offset1 - offset2; + return offset1 < offset2 ? -1 : + offset1 > offset2 ? 1 : + 0; } static int find_ofs_delta(const off_t offset, enum object_type type) @@ -784,7 +787,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, assert(data || obj_entry); read_lock(); - collision_test_needed = has_sha1_file(sha1); + collision_test_needed = has_sha1_file_with_flags(sha1, HAS_SHA1_QUICK); read_unlock(); if (collision_test_needed && !data) { @@ -836,10 +839,9 @@ 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, buf, size, 1, - fsck_error_function)) + fsck_object(obj, buf, size, &fsck_options)) die(_("Error in object")); - if (fsck_walk(obj, mark_link, NULL)) + if (fsck_walk(obj, NULL, &fsck_options)) die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1)); if (obj->type == OBJ_TREE) { @@ -1051,7 +1053,9 @@ 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; + return delta_a->offset < delta_b->offset ? -1 : + delta_a->offset > delta_b->offset ? 1 : + 0; } static int compare_ref_delta_entry(const void *a, const void *b) @@ -1223,7 +1227,7 @@ static void resolve_deltas(void) * - append objects to convert thin pack to full pack if required * - write the final 20-byte SHA-1 */ -static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved); +static void fix_unresolved_deltas(struct sha1file *f); static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1) { if (nr_ref_deltas + nr_ofs_deltas == nr_resolved_deltas) { @@ -1245,7 +1249,7 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha memset(objects + nr_objects + 1, 0, nr_unresolved * sizeof(*objects)); f = sha1fd(output_fd, curr_pack); - fix_unresolved_deltas(f, nr_unresolved); + fix_unresolved_deltas(f); strbuf_addf(&msg, _("completed with %d local objects"), nr_objects - nr_objects_initial); stop_progress_msg(&progress, msg.buf); @@ -1327,10 +1331,10 @@ static int delta_pos_compare(const void *_a, const void *_b) return a->obj_no - b->obj_no; } -static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) +static void fix_unresolved_deltas(struct sha1file *f) { struct ref_delta_entry **sorted_by_pos; - int i, n = 0; + int i; /* * Since many unresolved deltas may well be themselves base objects @@ -1342,12 +1346,12 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) * before deltas depending on them, a good heuristic is to start * resolving deltas in the same order as their position in the pack. */ - sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos)); + sorted_by_pos = xmalloc(nr_ref_deltas * sizeof(*sorted_by_pos)); 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); + sorted_by_pos[i] = &ref_deltas[i]; + qsort(sorted_by_pos, nr_ref_deltas, sizeof(*sorted_by_pos), delta_pos_compare); - for (i = 0; i < n; i++) { + for (i = 0; i < nr_ref_deltas; i++) { struct ref_delta_entry *d = sorted_by_pos[i]; enum object_type type; struct base_data *base_obj = alloc_base_data(); @@ -1611,6 +1615,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) usage(index_pack_usage); check_replace_refs = 0; + fsck_options.walk = mark_link; reset_pack_idx_option(&opts); git_config(git_index_pack_config, &opts); @@ -1628,6 +1633,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) } else if (!strcmp(arg, "--strict")) { strict = 1; do_fsck_object = 1; + } else if (skip_prefix(arg, "--strict=", &arg)) { + strict = 1; + do_fsck_object = 1; + fsck_set_msg_types(&fsck_options, arg); } else if (!strcmp(arg, "--check-self-contained-and-connected")) { strict = 1; check_self_contained_and_connected = 1; diff --git a/builtin/init-db.c b/builtin/init-db.c index 4335738135..49df78d262 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "refs.h" #include "builtin.h" #include "exec_cmd.h" #include "parse-options.h" diff --git a/builtin/log.c b/builtin/log.c index e67671e37a..b50ef7510c 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -5,6 +5,7 @@ * 2006 Junio Hamano */ #include "cache.h" +#include "refs.h" #include "color.h" #include "commit.h" #include "diff.h" @@ -31,6 +32,7 @@ static const char *default_date_mode = NULL; static int default_abbrev_commit; static int default_show_root = 1; +static int default_follow; static int decoration_style; static int decoration_given; static int use_mailmap_config; @@ -102,6 +104,8 @@ static void cmd_log_init_defaults(struct rev_info *rev) { if (fmt_pretty) get_commit_format(fmt_pretty, rev); + if (default_follow) + DIFF_OPT_SET(&rev->diffopt, DEFAULT_FOLLOW_RENAMES); rev->verbose_header = 1; DIFF_OPT_SET(&rev->diffopt, RECURSIVE); rev->diffopt.stat_width = -1; /* use full terminal width */ @@ -112,7 +116,7 @@ static void cmd_log_init_defaults(struct rev_info *rev) DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV); if (default_date_mode) - rev->date_mode = parse_date_format(default_date_mode); + parse_date_format(default_date_mode, &rev->date_mode); rev->diffopt.touched_flags = 0; } @@ -390,6 +394,10 @@ static int git_log_config(const char *var, const char *value, void *cb) default_show_root = git_config_bool(var, value); return 0; } + if (!strcmp(var, "log.follow")) { + default_follow = git_config_bool(var, value); + return 0; + } if (skip_prefix(var, "color.decorate.", &slot_name)) return parse_decorate_color_config(var, slot_name, value); if (!strcmp(var, "log.mailmap")) { @@ -618,6 +626,14 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) return cmd_log_walk(&rev); } +static void default_follow_tweak(struct rev_info *rev, + struct setup_revision_opt *opt) +{ + if (DIFF_OPT_TST(&rev->diffopt, DEFAULT_FOLLOW_RENAMES) && + rev->prune_data.nr == 1) + DIFF_OPT_SET(&rev->diffopt, FOLLOW_RENAMES); +} + int cmd_log(int argc, const char **argv, const char *prefix) { struct rev_info rev; @@ -631,6 +647,7 @@ int cmd_log(int argc, const char **argv, const char *prefix) memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; opt.revarg_opt = REVARG_COMMITTISH; + opt.tweak = default_follow_tweak; cmd_log_init(argc, argv, prefix, &rev, &opt); return cmd_log_walk(&rev); } @@ -795,7 +812,7 @@ static int reopen_stdout(struct commit *commit, const char *subject, static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) { struct rev_info check_rev; - struct commit *commit; + struct commit *commit, *c1, *c2; struct object *o1, *o2; unsigned flags1, flags2; @@ -803,9 +820,11 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) die(_("Need exactly one range.")); o1 = rev->pending.objects[0].item; - flags1 = o1->flags; o2 = rev->pending.objects[1].item; + flags1 = o1->flags; flags2 = o2->flags; + c1 = lookup_commit_reference(o1->sha1); + c2 = lookup_commit_reference(o2->sha1); if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) die(_("Not a range.")); @@ -827,10 +846,8 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) } /* reset for next revision walk */ - clear_commit_marks((struct commit *)o1, - SEEN | UNINTERESTING | SHOWN | ADDED); - clear_commit_marks((struct commit *)o2, - SEEN | UNINTERESTING | SHOWN | ADDED); + clear_commit_marks(c1, SEEN | UNINTERESTING | SHOWN | ADDED); + clear_commit_marks(c2, SEEN | UNINTERESTING | SHOWN | ADDED); o1->flags = flags1; o2->flags = flags2; } @@ -939,7 +956,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, msg = body; pp.fmt = CMIT_FMT_EMAIL; - pp.date_mode = DATE_RFC2822; + pp.date_mode.type = DATE_RFC2822; pp_user_info(&pp, NULL, &sb, committer, encoding); pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte); pp_remainder(&pp, &msg, &sb, 0); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 80fe8c7dc1..62cc16ddc2 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2588,23 +2588,6 @@ static int option_parse_unpack_unreachable(const struct option *opt, return 0; } -static int option_parse_ulong(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - die(_("option %s does not accept negative form"), - opt->long_name); - - if (!git_parse_ulong(arg, opt->value)) - die(_("unable to parse value '%s' for option %s"), - arg, opt->long_name); - return 0; -} - -#define OPT_ULONG(s, l, v, h) \ - { OPTION_CALLBACK, (s), (l), (v), "n", (h), \ - PARSE_OPT_NONEG, option_parse_ulong } - int cmd_pack_objects(int argc, const char **argv, const char *prefix) { int use_internal_rev_list = 0; @@ -2627,16 +2610,16 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 0, "index-version", NULL, N_("version[,offset]"), N_("write the pack index file in the specified idx format version"), 0, option_parse_index_version }, - OPT_ULONG(0, "max-pack-size", &pack_size_limit, - N_("maximum size of each output pack file")), + OPT_MAGNITUDE(0, "max-pack-size", &pack_size_limit, + N_("maximum size of each output pack file")), OPT_BOOL(0, "local", &local, N_("ignore borrowed objects from alternate object store")), OPT_BOOL(0, "incremental", &incremental, N_("ignore packed objects")), OPT_INTEGER(0, "window", &window, N_("limit pack window by objects")), - OPT_ULONG(0, "window-memory", &window_memory_limit, - N_("limit pack window by memory in addition to object limit")), + OPT_MAGNITUDE(0, "window-memory", &window_memory_limit, + N_("limit pack window by memory in addition to object limit")), OPT_INTEGER(0, "depth", &depth, N_("maximum length of delta chain allowed in the resulting pack")), OPT_BOOL(0, "reuse-delta", &reuse_delta, diff --git a/builtin/prune.c b/builtin/prune.c index 0c73246c72..10b03d3e4c 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -6,7 +6,6 @@ #include "reachable.h" #include "parse-options.h" #include "progress.h" -#include "dir.h" static const char * const prune_usage[] = { N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"), @@ -76,95 +75,6 @@ static int prune_subdir(int nr, const char *path, void *data) return 0; } -static int prune_worktree(const char *id, struct strbuf *reason) -{ - struct stat st; - char *path; - int fd, len; - - 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; - } - } - free(path); - return 0; -} - -static void prune_worktrees(void) -{ - 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); -} - /* * Write errors (particularly out of space) can result in * failed temporary packs (and more rarely indexes and other @@ -191,12 +101,10 @@ 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() @@ -211,13 +119,6 @@ int cmd_prune(int argc, const char **argv, const char *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++; diff --git a/builtin/pull.c b/builtin/pull.c new file mode 100644 index 0000000000..722a83c51b --- /dev/null +++ b/builtin/pull.c @@ -0,0 +1,882 @@ +/* + * Builtin "git pull" + * + * Based on git-pull.sh by Junio C Hamano + * + * Fetch one or more remote refs and merge it/them into the current HEAD. + */ +#include "cache.h" +#include "builtin.h" +#include "parse-options.h" +#include "exec_cmd.h" +#include "run-command.h" +#include "sha1-array.h" +#include "remote.h" +#include "dir.h" +#include "refs.h" +#include "revision.h" +#include "lockfile.h" + +enum rebase_type { + REBASE_INVALID = -1, + REBASE_FALSE = 0, + REBASE_TRUE, + REBASE_PRESERVE +}; + +/** + * Parses the value of --rebase. If value is a false value, returns + * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is + * "preserve", returns REBASE_PRESERVE. If value is a invalid value, dies with + * a fatal error if fatal is true, otherwise returns REBASE_INVALID. + */ +static enum rebase_type parse_config_rebase(const char *key, const char *value, + int fatal) +{ + int v = git_config_maybe_bool("pull.rebase", value); + + if (!v) + return REBASE_FALSE; + else if (v > 0) + return REBASE_TRUE; + else if (!strcmp(value, "preserve")) + return REBASE_PRESERVE; + + if (fatal) + die(_("Invalid value for %s: %s"), key, value); + else + error(_("Invalid value for %s: %s"), key, value); + + return REBASE_INVALID; +} + +/** + * Callback for --rebase, which parses arg with parse_config_rebase(). + */ +static int parse_opt_rebase(const struct option *opt, const char *arg, int unset) +{ + enum rebase_type *value = opt->value; + + if (arg) + *value = parse_config_rebase("--rebase", arg, 0); + else + *value = unset ? REBASE_FALSE : REBASE_TRUE; + return *value == REBASE_INVALID ? -1 : 0; +} + +static const char * const pull_usage[] = { + N_("git pull [options] [<repository> [<refspec>...]]"), + NULL +}; + +/* Shared options */ +static int opt_verbosity; +static char *opt_progress; + +/* Options passed to git-merge or git-rebase */ +static enum rebase_type opt_rebase = -1; +static char *opt_diffstat; +static char *opt_log; +static char *opt_squash; +static char *opt_commit; +static char *opt_edit; +static char *opt_ff; +static char *opt_verify_signatures; +static struct argv_array opt_strategies = ARGV_ARRAY_INIT; +static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT; +static char *opt_gpg_sign; + +/* Options passed to git-fetch */ +static char *opt_all; +static char *opt_append; +static char *opt_upload_pack; +static int opt_force; +static char *opt_tags; +static char *opt_prune; +static char *opt_recurse_submodules; +static int opt_dry_run; +static char *opt_keep; +static char *opt_depth; +static char *opt_unshallow; +static char *opt_update_shallow; +static char *opt_refmap; + +static struct option pull_options[] = { + /* Shared options */ + OPT__VERBOSITY(&opt_verbosity), + OPT_PASSTHRU(0, "progress", &opt_progress, NULL, + N_("force progress reporting"), + PARSE_OPT_NOARG), + + /* Options passed to git-merge or git-rebase */ + OPT_GROUP(N_("Options related to merging")), + { OPTION_CALLBACK, 'r', "rebase", &opt_rebase, + N_("false|true|preserve"), + N_("incorporate changes by rebasing rather than merging"), + PARSE_OPT_OPTARG, parse_opt_rebase }, + OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL, + N_("do not show a diffstat at the end of the merge"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG), + OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL, + N_("show a diffstat at the end of the merge"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL, + N_("(synonym to --stat)"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN), + OPT_PASSTHRU(0, "log", &opt_log, N_("n"), + N_("add (at most <n>) entries from shortlog to merge commit message"), + PARSE_OPT_OPTARG), + OPT_PASSTHRU(0, "squash", &opt_squash, NULL, + N_("create a single commit instead of doing a merge"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "commit", &opt_commit, NULL, + N_("perform a commit if the merge succeeds (default)"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "edit", &opt_edit, NULL, + N_("edit message before committing"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "ff", &opt_ff, NULL, + N_("allow fast-forward"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL, + N_("abort if fast-forward is not possible"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG), + OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL, + N_("verify that the named commit has a valid GPG signature"), + PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"), + N_("merge strategy to use"), + 0), + OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts, + N_("option=value"), + N_("option for selected merge strategy"), + 0), + OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"), + N_("GPG sign commit"), + PARSE_OPT_OPTARG), + + /* Options passed to git-fetch */ + OPT_GROUP(N_("Options related to fetching")), + OPT_PASSTHRU(0, "all", &opt_all, NULL, + N_("fetch from all remotes"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('a', "append", &opt_append, NULL, + N_("append to .git/FETCH_HEAD instead of overwriting"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"), + N_("path to upload pack on remote end"), + 0), + OPT__FORCE(&opt_force, N_("force overwrite of local branch")), + OPT_PASSTHRU('t', "tags", &opt_tags, NULL, + N_("fetch all tags and associated objects"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('p', "prune", &opt_prune, NULL, + N_("prune remote-tracking branches no longer on remote"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "recurse-submodules", &opt_recurse_submodules, + N_("on-demand"), + N_("control recursive fetching of submodules"), + PARSE_OPT_OPTARG), + OPT_BOOL(0, "dry-run", &opt_dry_run, + N_("dry run")), + OPT_PASSTHRU('k', "keep", &opt_keep, NULL, + N_("keep downloaded pack"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"), + N_("deepen history of shallow clone"), + 0), + OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL, + N_("convert to a complete repository"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL, + N_("accept refs that update .git/shallow"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"), + N_("specify fetch refmap"), + PARSE_OPT_NONEG), + + OPT_END() +}; + +/** + * Pushes "-q" or "-v" switches into arr to match the opt_verbosity level. + */ +static void argv_push_verbosity(struct argv_array *arr) +{ + int verbosity; + + for (verbosity = opt_verbosity; verbosity > 0; verbosity--) + argv_array_push(arr, "-v"); + + for (verbosity = opt_verbosity; verbosity < 0; verbosity++) + argv_array_push(arr, "-q"); +} + +/** + * Pushes "-f" switches into arr to match the opt_force level. + */ +static void argv_push_force(struct argv_array *arr) +{ + int force = opt_force; + while (force-- > 0) + argv_array_push(arr, "-f"); +} + +/** + * Sets the GIT_REFLOG_ACTION environment variable to the concatenation of argv + */ +static void set_reflog_message(int argc, const char **argv) +{ + int i; + struct strbuf msg = STRBUF_INIT; + + for (i = 0; i < argc; i++) { + if (i) + strbuf_addch(&msg, ' '); + strbuf_addstr(&msg, argv[i]); + } + + setenv("GIT_REFLOG_ACTION", msg.buf, 0); + + strbuf_release(&msg); +} + +/** + * If pull.ff is unset, returns NULL. If pull.ff is "true", returns "--ff". If + * pull.ff is "false", returns "--no-ff". If pull.ff is "only", returns + * "--ff-only". Otherwise, if pull.ff is set to an invalid value, die with an + * error. + */ +static const char *config_get_ff(void) +{ + const char *value; + + if (git_config_get_value("pull.ff", &value)) + return NULL; + + switch (git_config_maybe_bool("pull.ff", value)) { + case 0: + return "--no-ff"; + case 1: + return "--ff"; + } + + if (!strcmp(value, "only")) + return "--ff-only"; + + die(_("Invalid value for pull.ff: %s"), value); +} + +/** + * Returns the default configured value for --rebase. It first looks for the + * value of "branch.$curr_branch.rebase", where $curr_branch is the current + * branch, and if HEAD is detached or the configuration key does not exist, + * looks for the value of "pull.rebase". If both configuration keys do not + * exist, returns REBASE_FALSE. + */ +static enum rebase_type config_get_rebase(void) +{ + struct branch *curr_branch = branch_get("HEAD"); + const char *value; + + if (curr_branch) { + char *key = xstrfmt("branch.%s.rebase", curr_branch->name); + + if (!git_config_get_value(key, &value)) { + enum rebase_type ret = parse_config_rebase(key, value, 1); + free(key); + return ret; + } + + free(key); + } + + if (!git_config_get_value("pull.rebase", &value)) + return parse_config_rebase("pull.rebase", value, 1); + + return REBASE_FALSE; +} + +/** + * Returns 1 if there are unstaged changes, 0 otherwise. + */ +static int has_unstaged_changes(const char *prefix) +{ + struct rev_info rev_info; + int result; + + init_revisions(&rev_info, prefix); + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + DIFF_OPT_SET(&rev_info.diffopt, QUICK); + diff_setup_done(&rev_info.diffopt); + result = run_diff_files(&rev_info, 0); + return diff_result_code(&rev_info.diffopt, result); +} + +/** + * Returns 1 if there are uncommitted changes, 0 otherwise. + */ +static int has_uncommitted_changes(const char *prefix) +{ + struct rev_info rev_info; + int result; + + if (is_cache_unborn()) + return 0; + + init_revisions(&rev_info, prefix); + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + DIFF_OPT_SET(&rev_info.diffopt, QUICK); + add_head_to_pending(&rev_info); + diff_setup_done(&rev_info.diffopt); + result = run_diff_index(&rev_info, 1); + return diff_result_code(&rev_info.diffopt, result); +} + +/** + * If the work tree has unstaged or uncommitted changes, dies with the + * appropriate message. + */ +static void die_on_unclean_work_tree(const char *prefix) +{ + struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); + int do_die = 0; + + hold_locked_index(lock_file, 0); + refresh_cache(REFRESH_QUIET); + update_index_if_able(&the_index, lock_file); + rollback_lock_file(lock_file); + + if (has_unstaged_changes(prefix)) { + error(_("Cannot pull with rebase: You have unstaged changes.")); + do_die = 1; + } + + if (has_uncommitted_changes(prefix)) { + if (do_die) + error(_("Additionally, your index contains uncommitted changes.")); + else + error(_("Cannot pull with rebase: Your index contains uncommitted changes.")); + do_die = 1; + } + + if (do_die) + exit(1); +} + +/** + * Appends merge candidates from FETCH_HEAD that are not marked not-for-merge + * into merge_heads. + */ +static void get_merge_heads(struct sha1_array *merge_heads) +{ + const char *filename = git_path("FETCH_HEAD"); + FILE *fp; + struct strbuf sb = STRBUF_INIT; + unsigned char sha1[GIT_SHA1_RAWSZ]; + + if (!(fp = fopen(filename, "r"))) + die_errno(_("could not open '%s' for reading"), filename); + while (strbuf_getline(&sb, fp, '\n') != EOF) { + if (get_sha1_hex(sb.buf, sha1)) + continue; /* invalid line: does not start with SHA1 */ + if (starts_with(sb.buf + GIT_SHA1_HEXSZ, "\tnot-for-merge\t")) + continue; /* ref is not-for-merge */ + sha1_array_append(merge_heads, sha1); + } + fclose(fp); + strbuf_release(&sb); +} + +/** + * Used by die_no_merge_candidates() as a for_each_remote() callback to + * retrieve the name of the remote if the repository only has one remote. + */ +static int get_only_remote(struct remote *remote, void *cb_data) +{ + const char **remote_name = cb_data; + + if (*remote_name) + return -1; + + *remote_name = remote->name; + return 0; +} + +/** + * Dies with the appropriate reason for why there are no merge candidates: + * + * 1. We fetched from a specific remote, and a refspec was given, but it ended + * up not fetching anything. This is usually because the user provided a + * wildcard refspec which had no matches on the remote end. + * + * 2. We fetched from a non-default remote, but didn't specify a branch to + * merge. We can't use the configured one because it applies to the default + * remote, thus the user must specify the branches to merge. + * + * 3. We fetched from the branch's or repo's default remote, but: + * + * a. We are not on a branch, so there will never be a configured branch to + * merge with. + * + * b. We are on a branch, but there is no configured branch to merge with. + * + * 4. We fetched from the branch's or repo's default remote, but the configured + * branch to merge didn't get fetched. (Either it doesn't exist, or wasn't + * part of the configured fetch refspec.) + */ +static void NORETURN die_no_merge_candidates(const char *repo, const char **refspecs) +{ + struct branch *curr_branch = branch_get("HEAD"); + const char *remote = curr_branch ? curr_branch->remote_name : NULL; + + if (*refspecs) { + if (opt_rebase) + fprintf_ln(stderr, _("There is no candidate for rebasing against among the refs that you just fetched.")); + else + fprintf_ln(stderr, _("There are no candidates for merging among the refs that you just fetched.")); + fprintf_ln(stderr, _("Generally this means that you provided a wildcard refspec which had no\n" + "matches on the remote end.")); + } else if (repo && curr_branch && (!remote || strcmp(repo, remote))) { + fprintf_ln(stderr, _("You asked to pull from the remote '%s', but did not specify\n" + "a branch. Because this is not the default configured remote\n" + "for your current branch, you must specify a branch on the command line."), + repo); + } else if (!curr_branch) { + fprintf_ln(stderr, _("You are not currently on a branch.")); + if (opt_rebase) + fprintf_ln(stderr, _("Please specify which branch you want to rebase against.")); + else + fprintf_ln(stderr, _("Please specify which branch you want to merge with.")); + fprintf_ln(stderr, _("See git-pull(1) for details.")); + fprintf(stderr, "\n"); + fprintf_ln(stderr, " git pull <remote> <branch>"); + fprintf(stderr, "\n"); + } else if (!curr_branch->merge_nr) { + const char *remote_name = NULL; + + if (for_each_remote(get_only_remote, &remote_name) || !remote_name) + remote_name = "<remote>"; + + fprintf_ln(stderr, _("There is no tracking information for the current branch.")); + if (opt_rebase) + fprintf_ln(stderr, _("Please specify which branch you want to rebase against.")); + else + fprintf_ln(stderr, _("Please specify which branch you want to merge with.")); + fprintf_ln(stderr, _("See git-pull(1) for details.")); + fprintf(stderr, "\n"); + fprintf_ln(stderr, " git pull <remote> <branch>"); + fprintf(stderr, "\n"); + fprintf_ln(stderr, _("If you wish to set tracking information for this branch you can do so with:\n" + "\n" + " git branch --set-upstream-to=%s/<branch> %s\n"), + remote_name, curr_branch->name); + } else + fprintf_ln(stderr, _("Your configuration specifies to merge with the ref '%s'\n" + "from the remote, but no such ref was fetched."), + *curr_branch->merge_name); + exit(1); +} + +/** + * Parses argv into [<repo> [<refspecs>...]], returning their values in `repo` + * as a string and `refspecs` as a null-terminated array of strings. If `repo` + * is not provided in argv, it is set to NULL. + */ +static void parse_repo_refspecs(int argc, const char **argv, const char **repo, + const char ***refspecs) +{ + if (argc > 0) { + *repo = *argv++; + argc--; + } else + *repo = NULL; + *refspecs = argv; +} + +/** + * Runs git-fetch, returning its exit status. `repo` and `refspecs` are the + * repository and refspecs to fetch, or NULL if they are not provided. + */ +static int run_fetch(const char *repo, const char **refspecs) +{ + struct argv_array args = ARGV_ARRAY_INIT; + int ret; + + argv_array_pushl(&args, "fetch", "--update-head-ok", NULL); + + /* Shared options */ + argv_push_verbosity(&args); + if (opt_progress) + argv_array_push(&args, opt_progress); + + /* Options passed to git-fetch */ + if (opt_all) + argv_array_push(&args, opt_all); + if (opt_append) + argv_array_push(&args, opt_append); + if (opt_upload_pack) + argv_array_push(&args, opt_upload_pack); + argv_push_force(&args); + if (opt_tags) + argv_array_push(&args, opt_tags); + if (opt_prune) + argv_array_push(&args, opt_prune); + if (opt_recurse_submodules) + argv_array_push(&args, opt_recurse_submodules); + if (opt_dry_run) + argv_array_push(&args, "--dry-run"); + if (opt_keep) + argv_array_push(&args, opt_keep); + if (opt_depth) + argv_array_push(&args, opt_depth); + if (opt_unshallow) + argv_array_push(&args, opt_unshallow); + if (opt_update_shallow) + argv_array_push(&args, opt_update_shallow); + if (opt_refmap) + argv_array_push(&args, opt_refmap); + + if (repo) { + argv_array_push(&args, repo); + argv_array_pushv(&args, refspecs); + } else if (*refspecs) + die("BUG: refspecs without repo?"); + ret = run_command_v_opt(args.argv, RUN_GIT_CMD); + argv_array_clear(&args); + return ret; +} + +/** + * "Pulls into void" by branching off merge_head. + */ +static int pull_into_void(const unsigned char *merge_head, + const unsigned char *curr_head) +{ + /* + * Two-way merge: we treat the index as based on an empty tree, + * and try to fast-forward to HEAD. This ensures we will not lose + * index/worktree changes that the user already made on the unborn + * branch. + */ + if (checkout_fast_forward(EMPTY_TREE_SHA1_BIN, merge_head, 0)) + return 1; + + if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR)) + return 1; + + return 0; +} + +/** + * Runs git-merge, returning its exit status. + */ +static int run_merge(void) +{ + int ret; + struct argv_array args = ARGV_ARRAY_INIT; + + argv_array_pushl(&args, "merge", NULL); + + /* Shared options */ + argv_push_verbosity(&args); + if (opt_progress) + argv_array_push(&args, opt_progress); + + /* Options passed to git-merge */ + if (opt_diffstat) + argv_array_push(&args, opt_diffstat); + if (opt_log) + argv_array_push(&args, opt_log); + if (opt_squash) + argv_array_push(&args, opt_squash); + if (opt_commit) + argv_array_push(&args, opt_commit); + if (opt_edit) + argv_array_push(&args, opt_edit); + if (opt_ff) + argv_array_push(&args, opt_ff); + if (opt_verify_signatures) + argv_array_push(&args, opt_verify_signatures); + argv_array_pushv(&args, opt_strategies.argv); + argv_array_pushv(&args, opt_strategy_opts.argv); + if (opt_gpg_sign) + argv_array_push(&args, opt_gpg_sign); + + argv_array_push(&args, "FETCH_HEAD"); + ret = run_command_v_opt(args.argv, RUN_GIT_CMD); + argv_array_clear(&args); + return ret; +} + +/** + * Returns remote's upstream branch for the current branch. If remote is NULL, + * the current branch's configured default remote is used. Returns NULL if + * `remote` does not name a valid remote, HEAD does not point to a branch, + * remote is not the branch's configured remote or the branch does not have any + * configured upstream branch. + */ +static const char *get_upstream_branch(const char *remote) +{ + struct remote *rm; + struct branch *curr_branch; + const char *curr_branch_remote; + + rm = remote_get(remote); + if (!rm) + return NULL; + + curr_branch = branch_get("HEAD"); + if (!curr_branch) + return NULL; + + curr_branch_remote = remote_for_branch(curr_branch, NULL); + assert(curr_branch_remote); + + if (strcmp(curr_branch_remote, rm->name)) + return NULL; + + return branch_get_upstream(curr_branch, NULL); +} + +/** + * Derives the remote tracking branch from the remote and refspec. + * + * FIXME: The current implementation assumes the default mapping of + * refs/heads/<branch_name> to refs/remotes/<remote_name>/<branch_name>. + */ +static const char *get_tracking_branch(const char *remote, const char *refspec) +{ + struct refspec *spec; + const char *spec_src; + const char *merge_branch; + + spec = parse_fetch_refspec(1, &refspec); + spec_src = spec->src; + if (!*spec_src || !strcmp(spec_src, "HEAD")) + spec_src = "HEAD"; + else if (skip_prefix(spec_src, "heads/", &spec_src)) + ; + else if (skip_prefix(spec_src, "refs/heads/", &spec_src)) + ; + else if (starts_with(spec_src, "refs/") || + starts_with(spec_src, "tags/") || + starts_with(spec_src, "remotes/")) + spec_src = ""; + + if (*spec_src) { + if (!strcmp(remote, ".")) + merge_branch = mkpath("refs/heads/%s", spec_src); + else + merge_branch = mkpath("refs/remotes/%s/%s", remote, spec_src); + } else + merge_branch = NULL; + + free_refspec(1, spec); + return merge_branch; +} + +/** + * Given the repo and refspecs, sets fork_point to the point at which the + * current branch forked from its remote tracking branch. Returns 0 on success, + * -1 on failure. + */ +static int get_rebase_fork_point(unsigned char *fork_point, const char *repo, + const char *refspec) +{ + int ret; + struct branch *curr_branch; + const char *remote_branch; + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf sb = STRBUF_INIT; + + curr_branch = branch_get("HEAD"); + if (!curr_branch) + return -1; + + if (refspec) + remote_branch = get_tracking_branch(repo, refspec); + else + remote_branch = get_upstream_branch(repo); + + if (!remote_branch) + return -1; + + argv_array_pushl(&cp.args, "merge-base", "--fork-point", + remote_branch, curr_branch->name, NULL); + cp.no_stdin = 1; + cp.no_stderr = 1; + cp.git_cmd = 1; + + ret = capture_command(&cp, &sb, GIT_SHA1_HEXSZ); + if (ret) + goto cleanup; + + ret = get_sha1_hex(sb.buf, fork_point); + if (ret) + goto cleanup; + +cleanup: + strbuf_release(&sb); + return ret ? -1 : 0; +} + +/** + * Sets merge_base to the octopus merge base of curr_head, merge_head and + * fork_point. Returns 0 if a merge base is found, 1 otherwise. + */ +static int get_octopus_merge_base(unsigned char *merge_base, + const unsigned char *curr_head, + const unsigned char *merge_head, + const unsigned char *fork_point) +{ + struct commit_list *revs = NULL, *result; + + commit_list_insert(lookup_commit_reference(curr_head), &revs); + commit_list_insert(lookup_commit_reference(merge_head), &revs); + if (!is_null_sha1(fork_point)) + commit_list_insert(lookup_commit_reference(fork_point), &revs); + + result = reduce_heads(get_octopus_merge_bases(revs)); + free_commit_list(revs); + if (!result) + return 1; + + hashcpy(merge_base, result->item->object.sha1); + return 0; +} + +/** + * Given the current HEAD SHA1, the merge head returned from git-fetch and the + * fork point calculated by get_rebase_fork_point(), runs git-rebase with the + * appropriate arguments and returns its exit status. + */ +static int run_rebase(const unsigned char *curr_head, + const unsigned char *merge_head, + const unsigned char *fork_point) +{ + int ret; + unsigned char oct_merge_base[GIT_SHA1_RAWSZ]; + struct argv_array args = ARGV_ARRAY_INIT; + + if (!get_octopus_merge_base(oct_merge_base, curr_head, merge_head, fork_point)) + if (!is_null_sha1(fork_point) && !hashcmp(oct_merge_base, fork_point)) + fork_point = NULL; + + argv_array_push(&args, "rebase"); + + /* Shared options */ + argv_push_verbosity(&args); + + /* Options passed to git-rebase */ + if (opt_rebase == REBASE_PRESERVE) + argv_array_push(&args, "--preserve-merges"); + if (opt_diffstat) + argv_array_push(&args, opt_diffstat); + argv_array_pushv(&args, opt_strategies.argv); + argv_array_pushv(&args, opt_strategy_opts.argv); + if (opt_gpg_sign) + argv_array_push(&args, opt_gpg_sign); + + argv_array_push(&args, "--onto"); + argv_array_push(&args, sha1_to_hex(merge_head)); + + if (fork_point && !is_null_sha1(fork_point)) + argv_array_push(&args, sha1_to_hex(fork_point)); + else + argv_array_push(&args, sha1_to_hex(merge_head)); + + ret = run_command_v_opt(args.argv, RUN_GIT_CMD); + argv_array_clear(&args); + return ret; +} + +int cmd_pull(int argc, const char **argv, const char *prefix) +{ + const char *repo, **refspecs; + struct sha1_array merge_heads = SHA1_ARRAY_INIT; + unsigned char orig_head[GIT_SHA1_RAWSZ], curr_head[GIT_SHA1_RAWSZ]; + unsigned char rebase_fork_point[GIT_SHA1_RAWSZ]; + + if (!getenv("GIT_REFLOG_ACTION")) + set_reflog_message(argc, argv); + + argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0); + + parse_repo_refspecs(argc, argv, &repo, &refspecs); + + if (!opt_ff) + opt_ff = xstrdup_or_null(config_get_ff()); + + if (opt_rebase < 0) + opt_rebase = config_get_rebase(); + + git_config(git_default_config, NULL); + + if (read_cache_unmerged()) + die_resolve_conflict("Pull"); + + if (file_exists(git_path("MERGE_HEAD"))) + die_conclude_merge(); + + if (get_sha1("HEAD", orig_head)) + hashclr(orig_head); + + if (opt_rebase) { + if (is_null_sha1(orig_head) && !is_cache_unborn()) + die(_("Updating an unborn branch with changes added to the index.")); + + die_on_unclean_work_tree(prefix); + + if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs)) + hashclr(rebase_fork_point); + } + + if (run_fetch(repo, refspecs)) + return 1; + + if (opt_dry_run) + return 0; + + if (get_sha1("HEAD", curr_head)) + hashclr(curr_head); + + if (!is_null_sha1(orig_head) && !is_null_sha1(curr_head) && + hashcmp(orig_head, curr_head)) { + /* + * The fetch involved updating the current branch. + * + * The working tree and the index file are still based on + * orig_head commit, but we are merging into curr_head. + * Update the working tree to match curr_head. + */ + + warning(_("fetch updated the current branch head.\n" + "fast-forwarding your working tree from\n" + "commit %s."), sha1_to_hex(orig_head)); + + if (checkout_fast_forward(orig_head, curr_head, 0)) + die(_("Cannot fast-forward your working tree.\n" + "After making sure that you saved anything precious from\n" + "$ git diff %s\n" + "output, run\n" + "$ git reset --hard\n" + "to recover."), sha1_to_hex(orig_head)); + } + + get_merge_heads(&merge_heads); + + if (!merge_heads.nr) + die_no_merge_candidates(repo, refspecs); + + if (is_null_sha1(orig_head)) { + if (merge_heads.nr > 1) + die(_("Cannot merge multiple branches into empty head.")); + return pull_into_void(*merge_heads.sha1, curr_head); + } else if (opt_rebase) { + if (merge_heads.nr > 1) + die(_("Cannot rebase onto multiple branches.")); + return run_rebase(curr_head, *merge_heads.sha1, rebase_fork_point); + } else + return run_merge(); +} diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 94d0571776..e6b93d0264 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -19,6 +19,7 @@ #include "tag.h" #include "gpg-interface.h" #include "sigchain.h" +#include "fsck.h" static const char receive_pack_usage[] = "git receive-pack <git-dir>"; @@ -36,6 +37,7 @@ static enum deny_action deny_current_branch = DENY_UNCONFIGURED; static enum deny_action deny_delete_current = DENY_UNCONFIGURED; static int receive_fsck_objects = -1; static int transfer_fsck_objects = -1; +static struct strbuf fsck_msg_types = STRBUF_INIT; static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int advertise_atomic_push = 1; @@ -115,6 +117,26 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.fsck.skiplist") == 0) { + const char *path; + + if (git_config_pathname(&path, var, value)) + return 1; + strbuf_addf(&fsck_msg_types, "%cskiplist=%s", + fsck_msg_types.len ? ',' : '=', path); + free((char *)path); + return 0; + } + + if (skip_prefix(var, "receive.fsck.", &var)) { + if (is_valid_msg_type(var, value)) + strbuf_addf(&fsck_msg_types, "%c%s=%s", + fsck_msg_types.len ? ',' : '=', var, value); + else + warning("Skipping unknown msg id '%s'", var); + return 0; + } + if (strcmp(var, "receive.fsckobjects") == 0) { receive_fsck_objects = git_config_bool(var, value); return 0; @@ -911,7 +933,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) return "deletion prohibited"; } - if (!strcmp(namespaced_name, head_name)) { + if (head_name && !strcmp(namespaced_name, head_name)) { switch (deny_delete_current) { case DENY_IGNORE: break; @@ -1490,7 +1512,8 @@ static const char *unpack(int err_fd, struct shallow_info *si) if (quiet) argv_array_push(&child.args, "-q"); if (fsck_objects) - argv_array_push(&child.args, "--strict"); + argv_array_pushf(&child.args, "--strict%s", + fsck_msg_types.buf); child.no_stdout = 1; child.err = err_fd; child.git_cmd = 1; @@ -1508,7 +1531,8 @@ static const char *unpack(int err_fd, struct shallow_info *si) argv_array_pushl(&child.args, "index-pack", "--stdin", hdr_arg, keep_arg, NULL); if (fsck_objects) - argv_array_push(&child.args, "--strict"); + argv_array_pushf(&child.args, "--strict%s", + fsck_msg_types.buf); if (fix_thin) argv_array_push(&child.args, "--fix-thin"); child.out = -1; diff --git a/builtin/reflog.c b/builtin/reflog.c index c2eb8ff840..7ed0e8501c 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -13,6 +13,8 @@ static const char reflog_expire_usage[] = "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 [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>..."; +static const char reflog_exists_usage[] = +"git reflog exists <ref>"; static unsigned long default_reflog_expire; static unsigned long default_reflog_expire_unreachable; @@ -699,12 +701,38 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) return status; } +static int cmd_reflog_exists(int argc, const char **argv, const char *prefix) +{ + int i, start = 0; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(arg, "--")) { + i++; + break; + } + else if (arg[0] == '-') + usage(reflog_exists_usage); + else + break; + } + + start = i; + + if (argc - start != 1) + usage(reflog_exists_usage); + + if (check_refname_format(argv[start], REFNAME_ALLOW_ONELEVEL)) + die("invalid ref format: %s", argv[start]); + return !reflog_exists(argv[start]); +} + /* * main "reflog" */ static const char reflog_usage[] = -"git reflog [ show | expire | delete ]"; +"git reflog [ show | expire | delete | exists ]"; int cmd_reflog(int argc, const char **argv, const char *prefix) { @@ -724,5 +752,8 @@ int cmd_reflog(int argc, const char **argv, const char *prefix) if (!strcmp(argv[1], "delete")) return cmd_reflog_delete(argc - 1, argv + 1, prefix); + if (!strcmp(argv[1], "exists")) + return cmd_reflog_exists(argc - 1, argv + 1, prefix); + return cmd_log_reflog(argc, argv, prefix); } diff --git a/builtin/remote.c b/builtin/remote.c index f4a6ec9f13..cc3c7410f3 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -746,26 +746,6 @@ static int mv(int argc, const char **argv) return 0; } -static int remove_branches(struct string_list *branches) -{ - struct strbuf err = STRBUF_INIT; - int i, result = 0; - - 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; - const char *refname = item->string; - - if (delete_ref(refname, NULL, 0)) - result |= error(_("Could not remove branch %s"), refname); - } - - return result; -} - static int rm(int argc, const char **argv) { struct option options[] = { @@ -822,7 +802,7 @@ static int rm(int argc, const char **argv) strbuf_release(&buf); if (!result) - result = remove_branches(&branches); + result = delete_refs(&branches); string_list_clear(&branches, 0); if (skipped.nr) { @@ -1334,19 +1314,12 @@ static int prune_remote(const char *remote, int dry_run) 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); - } + if (!dry_run) + result |= delete_refs(&refs_to_prune); for_each_string_list_item(item, &states.stale) { const char *refname = item->util; - if (!dry_run) - result |= delete_ref(refname, NULL, 0); - if (dry_run) printf_ln(_(" * [would prune] %s"), abbrev_ref(refname, "refs/remotes/")); diff --git a/builtin/replace.c b/builtin/replace.c index 0d52e7fa1d..6b3c469a33 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -104,9 +104,9 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn) continue; } full_hex = sha1_to_hex(sha1); - snprintf(ref, sizeof(ref), "refs/replace/%s", full_hex); + snprintf(ref, sizeof(ref), "%s%s", git_replace_ref_base, full_hex); /* read_ref() may reuse the buffer */ - full_hex = ref + strlen("refs/replace/"); + full_hex = ref + strlen(git_replace_ref_base); if (read_ref(ref, sha1)) { error("replace ref '%s' not found.", full_hex); had_error = 1; @@ -134,7 +134,7 @@ static void check_ref_valid(unsigned char object[20], int force) { if (snprintf(ref, ref_size, - "refs/replace/%s", + "%s%s", git_replace_ref_base, sha1_to_hex(object)) > ref_size - 1) die("replace ref name too long: %.*s...", 50, ref); if (check_refname_format(ref, 0)) diff --git a/builtin/rev-list.c b/builtin/rev-list.c index ff84a825ff..c0b4b53652 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -42,6 +42,7 @@ static const char rev_list_usage[] = " --abbrev=<n> | --no-abbrev\n" " --abbrev-commit\n" " --left-right\n" +" --count\n" " special purpose:\n" " --bisect\n" " --bisect-vars\n" @@ -355,7 +356,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (bisect_list) revs.limited = 1; - if (use_bitmap_index) { + if (use_bitmap_index && !revs.prune) { if (revs.count && !revs.left_right && !revs.cherry_mark) { uint32_t commit_count; if (!prepare_bitmap_walk(&revs)) { diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index b6232390a6..02d747dcb1 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -371,6 +371,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) N_("output in stuck long form")), OPT_END(), }; + static const char * const flag_chars = "*=?!"; struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT; const char **usage = NULL; @@ -400,7 +401,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) /* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */ while (strbuf_getline(&sb, stdin, '\n') != EOF) { const char *s; - const char *end; + const char *help; struct option *o; if (!sb.len) @@ -410,54 +411,56 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) memset(opts + onb, 0, sizeof(opts[onb])); o = &opts[onb++]; - s = strchr(sb.buf, ' '); - if (!s || *sb.buf == ' ') { + help = strchr(sb.buf, ' '); + if (!help || *sb.buf == ' ') { o->type = OPTION_GROUP; o->help = xstrdup(skipspaces(sb.buf)); continue; } o->type = OPTION_CALLBACK; - o->help = xstrdup(skipspaces(s)); + o->help = xstrdup(skipspaces(help)); o->value = &parsed; o->flags = PARSE_OPT_NOARG; o->callback = &parseopt_dump; - /* Possible argument name hint */ - end = s; - while (s > sb.buf && strchr("*=?!", s[-1]) == NULL) - --s; - if (s != sb.buf && s != end) - o->argh = xmemdupz(s, end - s); - if (s == sb.buf) - s = end; - - while (s > sb.buf && strchr("*=?!", s[-1])) { - switch (*--s) { + /* name(s) */ + s = strpbrk(sb.buf, flag_chars); + if (s == NULL) + s = help; + + if (s - sb.buf == 1) /* short option only */ + o->short_name = *sb.buf; + else if (sb.buf[1] != ',') /* long option only */ + o->long_name = xmemdupz(sb.buf, s - sb.buf); + else { + o->short_name = *sb.buf; + o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2); + } + + /* flags */ + while (s < help) { + switch (*s++) { case '=': o->flags &= ~PARSE_OPT_NOARG; - break; + continue; case '?': o->flags &= ~PARSE_OPT_NOARG; o->flags |= PARSE_OPT_OPTARG; - break; + continue; case '!': o->flags |= PARSE_OPT_NONEG; - break; + continue; case '*': o->flags |= PARSE_OPT_HIDDEN; - break; + continue; } + s--; + break; } - if (s - sb.buf == 1) /* short option only */ - o->short_name = *sb.buf; - else if (sb.buf[1] != ',') /* long option only */ - o->long_name = xmemdupz(sb.buf, s - sb.buf); - else { - o->short_name = *sb.buf; - o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2); - } + if (s < help) + o->argh = xmemdupz(s, help - s); } strbuf_release(&sb); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index b961e5ae78..23b2962cb0 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -11,6 +11,7 @@ #include "transport.h" #include "version.h" #include "sha1-array.h" +#include "gpg-interface.h" static const char send_pack_usage[] = "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]\n" @@ -113,6 +114,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) int from_stdin = 0; struct push_cas_option cas = {0}; + git_config(git_gpg_config, NULL); + argv++; for (i = 1; i < argc; i++, argv++) { const char *arg = *argv; diff --git a/builtin/shortlog.c b/builtin/shortlog.c index c0bab6aaa9..007cc66a03 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -138,7 +138,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) ctx.abbrev = log->abbrev; ctx.subject = ""; ctx.after_subject = ""; - ctx.date_mode = DATE_NORMAL; + ctx.date_mode.type = DATE_NORMAL; ctx.output_encoding = get_log_output_encoding(); pretty_print_commit(&ctx, commit, &ufbuf); buffer = ufbuf.buf; diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 323f857463..c87c46eb38 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -784,7 +784,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) else msg++; reflog_msg[i] = xstrfmt("(%s) %s", - show_date(timestamp, tz, 1), + show_date(timestamp, tz, + DATE_MODE(RELATIVE)), msg); free(logmsg); sprintf(nth_desc, "%s@{%d}", *av, base+i); diff --git a/builtin/tag.c b/builtin/tag.c index 071d001655..471d6b1ab8 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -571,6 +571,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct create_tag_options opt; char *cleanup_arg = NULL; int annotate = 0, force = 0, lines = -1; + int create_reflog = 0; int cmdmode = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; @@ -597,6 +598,7 @@ 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_BOOL(0, "create-reflog", &create_reflog, N_("create_reflog")), OPT_GROUP(N_("Tag listing options")), OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), @@ -715,7 +717,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref.buf, object, prev, - 0, NULL, &err) || + create_reflog ? REF_FORCE_CREATE_REFLOG : 0, + NULL, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index ac6667242c..7cc086f5f2 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -20,6 +20,7 @@ static unsigned char buffer[4096]; static unsigned int offset, len; static off_t consumed_bytes; static git_SHA_CTX ctx; +static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; /* * When running under --strict mode, objects whose reachability are @@ -178,7 +179,7 @@ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf) * that have reachability requirements and calls this function. * Verify its reachability and validity recursively and write it out. */ -static int check_object(struct object *obj, int type, void *data) +static int check_object(struct object *obj, int type, void *data, struct fsck_options *options) { struct obj_buffer *obj_buf; @@ -203,10 +204,10 @@ static int check_object(struct object *obj, int type, void *data) 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)) + if (fsck_object(obj, obj_buf->buffer, obj_buf->size, &fsck_options)) die("Error in object"); - if (fsck_walk(obj, check_object, NULL)) + fsck_options.walk = check_object; + if (fsck_walk(obj, NULL, &fsck_options)) die("Error on reachable objects of %s", sha1_to_hex(obj->sha1)); write_cached_object(obj, obj_buf); return 0; @@ -217,7 +218,7 @@ static void write_rest(void) unsigned i; for (i = 0; i < nr_objects; i++) { if (obj_list[i].obj) - check_object(obj_list[i].obj, OBJ_ANY, NULL); + check_object(obj_list[i].obj, OBJ_ANY, NULL, NULL); } } @@ -529,6 +530,11 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) strict = 1; continue; } + if (skip_prefix(arg, "--strict=", &arg)) { + strict = 1; + fsck_set_msg_types(&fsck_options, arg); + continue; + } if (starts_with(arg, "--pack_header=")) { struct pack_header *hdr; char *c; diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 3d79a46b03..04dd00f734 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -14,6 +14,7 @@ static const char * const git_update_ref_usage[] = { static char line_termination = '\n'; static int update_flags; +static unsigned create_reflog_flag; static const char *msg; /* @@ -200,7 +201,8 @@ static const char *parse_cmd_update(struct ref_transaction *transaction, if (ref_transaction_update(transaction, refname, new_sha1, have_old ? old_sha1 : NULL, - update_flags, msg, &err)) + update_flags | create_reflog_flag, + msg, &err)) die("%s", err.buf); update_flags = 0; @@ -231,7 +233,8 @@ static const char *parse_cmd_create(struct ref_transaction *transaction, die("create %s: extra input: %s", refname, next); if (ref_transaction_create(transaction, refname, new_sha1, - update_flags, msg, &err)) + update_flags | create_reflog_flag, + msg, &err)) die("%s", err.buf); update_flags = 0; @@ -354,6 +357,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) unsigned char sha1[20], oldsha1[20]; int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0; unsigned int flags = 0; + int create_reflog = 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")), @@ -361,6 +365,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) N_("update <refname> not the one it points to")), OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")), OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")), + OPT_BOOL( 0 , "create-reflog", &create_reflog, N_("create_reflog")), OPT_END(), }; @@ -370,6 +375,8 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (msg && !*msg) die("Refusing to perform update with empty message."); + create_reflog_flag = create_reflog ? REF_FORCE_CREATE_REFLOG : 0; + if (read_stdin) { struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; @@ -408,15 +415,29 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) die("%s: not a valid SHA1", value); } - hashclr(oldsha1); /* all-zero hash in case oldval is the empty string */ - if (oldval && *oldval && get_sha1(oldval, oldsha1)) - die("%s: not a valid old SHA1", oldval); + if (oldval) { + if (!*oldval) + /* + * The empty string implies that the reference + * must not already exist: + */ + hashclr(oldsha1); + else if (get_sha1(oldval, oldsha1)) + die("%s: not a valid old SHA1", oldval); + } if (no_deref) flags = REF_NODEREF; if (delete) - return delete_ref(refname, oldval ? oldsha1 : NULL, flags); + /* + * For purposes of backwards compatibility, we treat + * NULL_SHA1 as "don't care" here: + */ + return delete_ref(refname, + (oldval && !is_null_sha1(oldsha1)) ? oldsha1 : NULL, + flags); else return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, - flags, UPDATE_REFS_DIE_ON_ERR); + flags | create_reflog_flag, + UPDATE_REFS_DIE_ON_ERR); } diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c index ec0c4e3d83..38bedf8f9f 100644 --- a/builtin/verify-commit.c +++ b/builtin/verify-commit.c @@ -18,25 +18,21 @@ static const char * const verify_commit_usage[] = { NULL }; -static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, int verbose) +static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, unsigned flags) { struct signature_check signature_check; + int ret; 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); + ret = check_commit_signature(lookup_commit(sha1), &signature_check); + print_signature_buffer(&signature_check, flags); signature_check_clear(&signature_check); - return signature_check.result != 'G'; + return ret; } -static int verify_commit(const char *name, int verbose) +static int verify_commit(const char *name, unsigned flags) { enum object_type type; unsigned char sha1[20]; @@ -54,7 +50,7 @@ static int verify_commit(const char *name, int verbose) return error("%s: cannot verify a non-commit object of type %s.", name, typename(type)); - ret = run_gpg_verify(sha1, buf, size, verbose); + ret = run_gpg_verify(sha1, buf, size, flags); free(buf); return ret; @@ -71,8 +67,10 @@ static int git_verify_commit_config(const char *var, const char *value, void *cb int cmd_verify_commit(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; + unsigned flags = 0; const struct option verify_commit_options[] = { OPT__VERBOSE(&verbose, N_("print commit contents")), + OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW), OPT_END() }; @@ -83,11 +81,14 @@ int cmd_verify_commit(int argc, const char **argv, const char *prefix) if (argc <= i) usage_with_options(verify_commit_usage, verify_commit_options); + if (verbose) + flags |= GPG_VERIFY_VERBOSE; + /* 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)) + if (verify_commit(argv[i++], flags)) had_error = 1; return had_error; } diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 53c68fce3a..00663f6a30 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -18,21 +18,30 @@ static const char * const verify_tag_usage[] = { NULL }; -static int run_gpg_verify(const char *buf, unsigned long size, int verbose) +static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags) { + struct signature_check sigc; int len; + int ret; + + memset(&sigc, 0, sizeof(sigc)); len = parse_signature(buf, size); - if (verbose) - write_in_full(1, buf, len); - if (size == len) + if (size == len) { + if (flags & GPG_VERIFY_VERBOSE) + write_in_full(1, buf, len); return error("no signature found"); + } + + ret = check_signature(buf, len, buf + len, size - len, &sigc); + print_signature_buffer(&sigc, flags); - return verify_signed_buffer(buf, len, buf + len, size - len, NULL, NULL); + signature_check_clear(&sigc); + return ret; } -static int verify_tag(const char *name, int verbose) +static int verify_tag(const char *name, unsigned flags) { enum object_type type; unsigned char sha1[20]; @@ -52,7 +61,7 @@ static int verify_tag(const char *name, int verbose) if (!buf) return error("%s: unable to read file.", name); - ret = run_gpg_verify(buf, size, verbose); + ret = run_gpg_verify(buf, size, flags); free(buf); return ret; @@ -69,8 +78,10 @@ static int git_verify_tag_config(const char *var, const char *value, void *cb) int cmd_verify_tag(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; + unsigned flags = 0; const struct option verify_tag_options[] = { OPT__VERBOSE(&verbose, N_("print tag contents")), + OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW), OPT_END() }; @@ -81,11 +92,14 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) if (argc <= i) usage_with_options(verify_tag_usage, verify_tag_options); + if (verbose) + flags |= GPG_VERIFY_VERBOSE; + /* 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_tag(argv[i++], verbose)) + if (verify_tag(argv[i++], flags)) had_error = 1; return had_error; } diff --git a/builtin/worktree.c b/builtin/worktree.c new file mode 100644 index 0000000000..6a264ee749 --- /dev/null +++ b/builtin/worktree.c @@ -0,0 +1,332 @@ +#include "cache.h" +#include "builtin.h" +#include "dir.h" +#include "parse-options.h" +#include "argv-array.h" +#include "run-command.h" +#include "sigchain.h" +#include "refs.h" + +static const char * const worktree_usage[] = { + N_("git worktree add [<options>] <path> <branch>"), + N_("git worktree prune [<options>]"), + NULL +}; + +static int show_only; +static int verbose; +static unsigned long expire; + +static int prune_worktree(const char *id, struct strbuf *reason) +{ + struct stat st; + char *path; + int fd, len; + + 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; + } + } + free(path); + return 0; +} + +static void prune_worktrees(void) +{ + 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); +} + +static int prune(int ac, const char **av, const char *prefix) +{ + struct option options[] = { + OPT__DRY_RUN(&show_only, N_("do not remove, show only")), + OPT__VERBOSE(&verbose, N_("report pruned objects")), + OPT_EXPIRY_DATE(0, "expire", &expire, + N_("expire objects older than <time>")), + OPT_END() + }; + + expire = ULONG_MAX; + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac) + usage_with_options(worktree_usage, options); + prune_worktrees(); + return 0; +} + +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 const char *worktree_basename(const char *path, int *olen) +{ + const char *name; + int len; + + 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; + } + + *olen = len; + return name; +} + +static int add_worktree(const char *path, const char **child_argv) +{ + struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + const char *name; + struct stat st; + struct child_process cp; + int counter = 0, len, ret; + unsigned char rev[20]; + + if (file_exists(path) && !is_empty_dir(path)) + die(_("'%s' already exists"), path); + + name = worktree_basename(path, &len); + 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. Moreover, HEAD + * in the new worktree must resolve to the same value as HEAD in + * the current tree since the command invoked to populate the new + * worktree will be handed the branch/ref specified by the user. + * For instance, if the user asks for the new worktree to be based + * at HEAD~5, then the resolved HEAD~5 in the new worktree must + * match the resolved HEAD~5 in the current tree in order to match + * the user's expectation. + */ + if (!resolve_ref_unsafe("HEAD", 0, rev, NULL)) + die(_("unable to resolve HEAD")); + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); + write_file(sb.buf, 1, "%s\n", sha1_to_hex(rev)); + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/commondir", sb_repo.buf); + write_file(sb.buf, 1, "../..\n"); + + 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 = child_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 add(int ac, const char **av, const char *prefix) +{ + int force = 0, detach = 0; + const char *new_branch = NULL, *new_branch_force = NULL; + const char *path, *branch; + struct argv_array cmd = ARGV_ARRAY_INIT; + struct option options[] = { + OPT__FORCE(&force, N_("checkout <branch> even if already checked out in other worktree")), + OPT_STRING('b', NULL, &new_branch, N_("branch"), + N_("create a new branch")), + OPT_STRING('B', NULL, &new_branch_force, N_("branch"), + N_("create or reset a branch")), + OPT_BOOL(0, "detach", &detach, N_("detach HEAD at named commit")), + OPT_END() + }; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (new_branch && new_branch_force) + die(_("-b and -B are mutually exclusive")); + if (ac < 1 || ac > 2) + usage_with_options(worktree_usage, options); + + path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0]; + branch = ac < 2 ? "HEAD" : av[1]; + + if (ac < 2 && !new_branch && !new_branch_force) { + int n; + const char *s = worktree_basename(path, &n); + new_branch = xstrndup(s, n); + } + + argv_array_push(&cmd, "checkout"); + if (force) + argv_array_push(&cmd, "--ignore-other-worktrees"); + if (new_branch) + argv_array_pushl(&cmd, "-b", new_branch, NULL); + if (new_branch_force) + argv_array_pushl(&cmd, "-B", new_branch_force, NULL); + if (detach) + argv_array_push(&cmd, "--detach"); + argv_array_push(&cmd, branch); + + return add_worktree(path, cmd.argv); +} + +int cmd_worktree(int ac, const char **av, const char *prefix) +{ + struct option options[] = { + OPT_END() + }; + + if (ac < 2) + usage_with_options(worktree_usage, options); + if (!strcmp(av[1], "add")) + return add(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "prune")) + return prune(ac - 1, av + 1, prefix); + usage_with_options(worktree_usage, options); +} |