diff options
Diffstat (limited to 'builtin')
-rw-r--r-- | builtin/add.c | 4 | ||||
-rw-r--r-- | builtin/apply.c | 14 | ||||
-rw-r--r-- | builtin/blame.c | 3 | ||||
-rw-r--r-- | builtin/branch.c | 10 | ||||
-rw-r--r-- | builtin/bundle.c | 8 | ||||
-rw-r--r-- | builtin/cat-file.c | 51 | ||||
-rw-r--r-- | builtin/checkout-index.c | 10 | ||||
-rw-r--r-- | builtin/checkout.c | 280 | ||||
-rw-r--r-- | builtin/clean.c | 6 | ||||
-rw-r--r-- | builtin/clone.c | 13 | ||||
-rw-r--r-- | builtin/commit.c | 15 | ||||
-rw-r--r-- | builtin/config.c | 10 | ||||
-rw-r--r-- | builtin/count-objects.c | 4 | ||||
-rw-r--r-- | builtin/fetch.c | 5 | ||||
-rw-r--r-- | builtin/fmt-merge-msg.c | 16 | ||||
-rw-r--r-- | builtin/fsck.c | 4 | ||||
-rw-r--r-- | builtin/gc.c | 34 | ||||
-rw-r--r-- | builtin/hash-object.c | 4 | ||||
-rw-r--r-- | builtin/index-pack.c | 290 | ||||
-rw-r--r-- | builtin/init-db.c | 7 | ||||
-rw-r--r-- | builtin/log.c | 2 | ||||
-rw-r--r-- | builtin/merge.c | 248 | ||||
-rw-r--r-- | builtin/pack-objects.c | 4 | ||||
-rw-r--r-- | builtin/patch-id.c | 34 | ||||
-rw-r--r-- | builtin/prune.c | 99 | ||||
-rw-r--r-- | builtin/receive-pack.c | 2 | ||||
-rw-r--r-- | builtin/remote.c | 2 | ||||
-rw-r--r-- | builtin/repack.c | 8 | ||||
-rw-r--r-- | builtin/rev-parse.c | 11 | ||||
-rw-r--r-- | builtin/show-branch.c | 6 | ||||
-rw-r--r-- | builtin/update-index.c | 201 |
31 files changed, 1063 insertions, 342 deletions
diff --git a/builtin/add.c b/builtin/add.c index 3390933d68..df5135bf62 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -63,6 +63,7 @@ static void update_callback(struct diff_queue_struct *q, switch (fix_unmerged_status(p, data)) { default: die(_("unexpected diff status %c"), p->status); + case DIFF_STATUS_ADDED: case DIFF_STATUS_MODIFIED: case DIFF_STATUS_TYPE_CHANGED: if (add_file_to_index(&the_index, path, data->flags)) { @@ -208,7 +209,8 @@ static int edit_patch(int argc, const char **argv, const char *prefix) if (run_diff_files(&rev, 0)) die(_("Could not write patch")); - launch_editor(file, NULL, NULL); + if (launch_editor(file, NULL, NULL)) + die(_("editing patch failed")); if (stat(file, &st)) die_errno(_("Could not stat '%s'"), file); diff --git a/builtin/apply.c b/builtin/apply.c index 0769b09287..146be97a1a 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -208,7 +208,7 @@ struct patch { struct patch *next; /* three-way fallback result */ - unsigned char threeway_stage[3][20]; + struct object_id threeway_stage[3]; }; static void free_fragment_list(struct fragment *list) @@ -3426,11 +3426,11 @@ static int try_threeway(struct image *image, struct patch *patch, if (status) { patch->conflicted_threeway = 1; if (patch->is_new) - hashclr(patch->threeway_stage[0]); + oidclr(&patch->threeway_stage[0]); else - hashcpy(patch->threeway_stage[0], pre_sha1); - hashcpy(patch->threeway_stage[1], our_sha1); - hashcpy(patch->threeway_stage[2], post_sha1); + hashcpy(patch->threeway_stage[0].hash, pre_sha1); + hashcpy(patch->threeway_stage[1].hash, our_sha1); + hashcpy(patch->threeway_stage[2].hash, post_sha1); fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name); } else { fprintf(stderr, "Applied patch to '%s' cleanly.\n", patch->new_name); @@ -4186,14 +4186,14 @@ static void add_conflicted_stages_file(struct patch *patch) remove_file_from_cache(patch->new_name); for (stage = 1; stage < 4; stage++) { - if (is_null_sha1(patch->threeway_stage[stage - 1])) + if (is_null_oid(&patch->threeway_stage[stage - 1])) continue; ce = xcalloc(1, ce_size); memcpy(ce->name, patch->new_name, namelen); ce->ce_mode = create_ce_mode(mode); ce->ce_flags = create_ce_flags(stage); ce->ce_namelen = namelen; - hashcpy(ce->sha1, patch->threeway_stage[stage - 1]); + hashcpy(ce->sha1, patch->threeway_stage[stage - 1].hash); if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) die(_("unable to add cache entry for %s"), patch->new_name); } diff --git a/builtin/blame.c b/builtin/blame.c index 91d4221f59..b3e948e757 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -28,7 +28,7 @@ #include "line-log.h" #include "dir.h" -static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] file"); +static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>"); static const char *blame_opt_usage[] = { blame_usage, @@ -2339,6 +2339,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, if (strbuf_read(&buf, 0, 0) < 0) die_errno("failed to read from stdin"); } + convert_to_git(path, buf.buf, buf.len, &buf, 0); origin->file.ptr = buf.buf; origin->file.size = buf.len; pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1); diff --git a/builtin/branch.c b/builtin/branch.c index 1d150378e9..9cbab189f5 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -242,7 +242,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, sha1, &flags); if (!target) { error(remote_branch - ? _("remote branch '%s' not found.") + ? _("remote-tracking branch '%s' not found.") : _("branch '%s' not found."), bname.buf); ret = 1; continue; @@ -257,7 +257,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, if (delete_ref(name, sha1, REF_NODEREF)) { error(remote_branch - ? _("Error deleting remote branch '%s'") + ? _("Error deleting remote-tracking branch '%s'") : _("Error deleting branch '%s'"), bname.buf); ret = 1; @@ -265,7 +265,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, } if (!quiet) { printf(remote_branch - ? _("Deleted remote branch %s (was %s).\n") + ? _("Deleted remote-tracking branch %s (was %s).\n") : _("Deleted branch %s (was %s).\n"), bname.buf, (flags & REF_ISBROKEN) ? "broken" @@ -771,7 +771,6 @@ static const char edit_description[] = "BRANCH_DESCRIPTION"; static int edit_branch_description(const char *branch_name) { - FILE *fp; int status; struct strbuf buf = STRBUF_INIT; struct strbuf name = STRBUF_INIT; @@ -784,8 +783,7 @@ static int edit_branch_description(const char *branch_name) " %s\n" "Lines starting with '%c' will be stripped.\n", branch_name, comment_line_char); - fp = fopen(git_path(edit_description), "w"); - if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) { + if (write_file(git_path(edit_description), 0, "%s", buf.buf)) { strbuf_release(&buf); return error(_("could not write branch description template: %s"), strerror(errno)); diff --git a/builtin/bundle.c b/builtin/bundle.c index 92a8a6026a..4883a435a9 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -42,6 +42,10 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) if (!strcmp(cmd, "verify")) { close(bundle_fd); + if (argc != 1) { + usage(builtin_bundle_usage); + return 1; + } if (verify_bundle(&header, 1)) return 1; fprintf(stderr, _("%s is okay\n"), bundle_file); @@ -52,6 +56,10 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) return !!list_bundle_refs(&header, argc, argv); } if (!strcmp(cmd, "create")) { + if (argc < 2) { + usage(builtin_bundle_usage); + return 1; + } if (!startup_info->have_repository) die(_("Need a repository to create a bundle.")); return !!create_bundle(&header, bundle_file, argc, argv); diff --git a/builtin/cat-file.c b/builtin/cat-file.c index df99df4db1..ecb488822f 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -9,13 +9,20 @@ #include "userdiff.h" #include "streaming.h" -static int cat_one_file(int opt, const char *exp_type, const char *obj_name) +static int cat_one_file(int opt, const char *exp_type, const char *obj_name, + int unknown_type) { unsigned char sha1[20]; enum object_type type; char *buf; unsigned long size; struct object_context obj_context; + struct object_info oi = {NULL}; + struct strbuf sb = STRBUF_INIT; + unsigned flags = LOOKUP_REPLACE_OBJECT; + + if (unknown_type) + flags |= LOOKUP_UNKNOWN_OBJECT; if (get_sha1_with_context(obj_name, 0, sha1, &obj_context)) die("Not a valid object name %s", obj_name); @@ -23,20 +30,22 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) buf = NULL; switch (opt) { case 't': - type = sha1_object_info(sha1, NULL); - if (type > 0) { - printf("%s\n", typename(type)); + oi.typename = &sb; + if (sha1_object_info_extended(sha1, &oi, flags) < 0) + die("git cat-file: could not get object info"); + if (sb.len) { + printf("%s\n", sb.buf); + strbuf_release(&sb); return 0; } break; case 's': - type = sha1_object_info(sha1, &size); - if (type > 0) { - printf("%lu\n", size); - return 0; - } - break; + oi.sizep = &size; + if (sha1_object_info_extended(sha1, &oi, flags) < 0) + die("git cat-file: could not get object info"); + printf("%lu\n", size); + return 0; case 'e': return !has_sha1_file(sha1); @@ -323,7 +332,7 @@ static int batch_objects(struct batch_options *opt) } static const char * const cat_file_usage[] = { - N_("git cat-file (-t | -s | -e | -p | <type> | --textconv) <object>"), + N_("git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<type>|--textconv) <object>"), N_("git cat-file (--batch | --batch-check) < <list-of-objects>"), NULL }; @@ -359,16 +368,19 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) int opt = 0; const char *exp_type = NULL, *obj_name = NULL; struct batch_options batch = {0}; + int unknown_type = 0; const struct option options[] = { OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")), - OPT_SET_INT('t', NULL, &opt, N_("show object type"), 't'), - OPT_SET_INT('s', NULL, &opt, N_("show object size"), 's'), - OPT_SET_INT('e', NULL, &opt, + OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'), + OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), + OPT_CMDMODE('e', NULL, &opt, N_("exit with zero when there's no error"), 'e'), - OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'), - OPT_SET_INT(0, "textconv", &opt, + OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'), + OPT_CMDMODE(0, "textconv", &opt, N_("for blob objects, run textconv on object's content"), 'c'), + OPT_BOOL( 0, "allow-unknown-type", &unknown_type, + N_("allow -s and -t to work with broken/corrupt objects")), { OPTION_CALLBACK, 0, "batch", &batch, "format", N_("show info and content of objects fed from the standard input"), PARSE_OPT_OPTARG, batch_option_callback }, @@ -380,9 +392,6 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) git_config(git_cat_file_config, NULL); - if (argc != 3 && argc != 2) - usage_with_options(cat_file_usage, options); - argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0); if (opt) { @@ -405,5 +414,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) if (batch.enabled) return batch_objects(&batch); - return cat_one_file(opt, exp_type, obj_name); + if (unknown_type && opt != 't' && opt != 's') + die("git cat-file --allow-unknown-type: use with -s or -t"); + return cat_one_file(opt, exp_type, obj_name, unknown_type); } diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 9ca2da1583..8028c3768f 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -241,7 +241,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) /* Check out named files first */ for (i = 0; i < argc; i++) { const char *arg = argv[i]; - const char *p; + char *p; if (all) die("git checkout-index: don't mix '--all' and explicit filenames"); @@ -249,8 +249,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) die("git checkout-index: don't mix '--stdin' and explicit filenames"); p = prefix_path(prefix, prefix_length, arg); checkout_file(p, prefix); - if (p < arg || p > arg + strlen(arg)) - free((char *)p); + free(p); } if (read_from_stdin) { @@ -260,7 +259,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) die("git checkout-index: don't mix '--all' and '--stdin'"); while (strbuf_getline(&buf, stdin, line_termination) != EOF) { - const char *p; + char *p; if (line_termination && buf.buf[0] == '"') { strbuf_reset(&nbuf); if (unquote_c_style(&nbuf, buf.buf, NULL)) @@ -269,8 +268,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) } p = prefix_path(prefix, prefix_length, buf.buf); checkout_file(p, prefix); - if (p < buf.buf || p > buf.buf + buf.len) - free((char *)p); + free(p); } strbuf_release(&nbuf); strbuf_release(&buf); diff --git a/builtin/checkout.c b/builtin/checkout.c index 3e141fc149..2f92328db4 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -20,6 +20,7 @@ #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>"), @@ -36,6 +37,7 @@ struct checkout_opts { int writeout_stage; int overwrite_ignore; int ignore_skipworktree; + int ignore_other_worktrees; const char *new_branch; const char *new_branch_force; @@ -48,6 +50,10 @@ struct checkout_opts { const char *prefix; struct pathspec pathspec; struct tree *source_tree; + + const char *new_worktree; + const char **saved_argv; + int new_worktree_mode; }; static int post_checkout_hook(struct commit *old, struct commit *new, @@ -267,6 +273,9 @@ 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); @@ -441,6 +450,11 @@ struct branch_info { const char *name; /* The short name used */ const char *path; /* The full name of a real branch */ struct commit *commit; /* The named commit */ + /* + * if not null the branch is detached because it's already + * checked out in this checkout + */ + char *checkout; }; static void setup_branch_path(struct branch_info *branch) @@ -502,7 +516,7 @@ static int merge_working_tree(const struct checkout_opts *opts, topts.dir->flags |= DIR_SHOW_IGNORED; setup_standard_excludes(topts.dir); } - tree = parse_tree_indirect(old->commit ? + tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ? old->commit->object.sha1 : EMPTY_TREE_SHA1_BIN); init_tree_desc(&trees[0], tree->buffer, tree->size); @@ -606,18 +620,21 @@ static void update_refs_for_switch(const struct checkout_opts *opts, if (opts->new_orphan_branch) { if (opts->new_branch_log && !log_all_ref_updates) { int temp; - char log_file[PATH_MAX]; - char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch); + struct strbuf log_file = STRBUF_INIT; + int ret; + const char *ref_name; + ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch); temp = log_all_ref_updates; log_all_ref_updates = 1; - if (log_ref_setup(ref_name, log_file, sizeof(log_file))) { + ret = log_ref_setup(ref_name, &log_file); + log_all_ref_updates = temp; + strbuf_release(&log_file); + if (ret) { fprintf(stderr, _("Can not do reflog for '%s'\n"), opts->new_orphan_branch); - log_all_ref_updates = temp; return; } - log_all_ref_updates = temp; } } else @@ -743,10 +760,17 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) if (advice_detached_head) fprintf(stderr, - _( + Q_( + /* The singular version */ + "If you want to keep it by creating a new branch, " + "this may be a good time\nto do so with:\n\n" + " git branch <new-branch-name> %s\n\n", + /* The plural version */ "If you want to keep them by creating a new branch, " "this may be a good time\nto do so with:\n\n" - " git branch <new-branch-name> %s\n\n"), + " git branch <new-branch-name> %s\n\n", + /* Give ngettext() the count */ + lost), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); } @@ -815,7 +839,8 @@ static int switch_branches(const struct checkout_opts *opts, return ret; } - if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) + if (!opts->quiet && !old.path && old.commit && + new->commit != old.commit && !opts->new_worktree_mode) orphaned_commit_warning(old.commit, new->commit); update_refs_for_switch(opts, &old, new); @@ -825,6 +850,138 @@ static int switch_branches(const struct checkout_opts *opts, return ret || writeout_error; } +static char *junk_work_tree; +static char *junk_git_dir; +static int is_junk; +static pid_t junk_pid; + +static void remove_junk(void) +{ + struct strbuf sb = STRBUF_INIT; + if (!is_junk || getpid() != junk_pid) + return; + if (junk_git_dir) { + strbuf_addstr(&sb, junk_git_dir); + remove_dir_recursively(&sb, 0); + strbuf_reset(&sb); + } + if (junk_work_tree) { + strbuf_addstr(&sb, junk_work_tree); + remove_dir_recursively(&sb, 0); + } + strbuf_release(&sb); +} + +static void remove_junk_on_signal(int signo) +{ + remove_junk(); + sigchain_pop(signo); + raise(signo); +} + +static int prepare_linked_checkout(const struct checkout_opts *opts, + struct branch_info *new) +{ + struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + const char *path = opts->new_worktree, *name; + struct stat st; + struct child_process cp; + int counter = 0, len, ret; + + if (!new->commit) + die(_("no branch specified")); + if (file_exists(path) && !is_empty_dir(path)) + die(_("'%s' already exists"), path); + + len = strlen(path); + while (len && is_dir_sep(path[len - 1])) + len--; + + for (name = path + len - 1; name > path; name--) + if (is_dir_sep(*name)) { + name++; + break; + } + strbuf_addstr(&sb_repo, + git_path("worktrees/%.*s", (int)(path + len - name), name)); + len = sb_repo.len; + if (safe_create_leading_directories_const(sb_repo.buf)) + die_errno(_("could not create leading directories of '%s'"), + sb_repo.buf); + while (!stat(sb_repo.buf, &st)) { + counter++; + strbuf_setlen(&sb_repo, len); + strbuf_addf(&sb_repo, "%d", counter); + } + name = strrchr(sb_repo.buf, '/') + 1; + + junk_pid = getpid(); + atexit(remove_junk); + sigchain_push_common(remove_junk_on_signal); + + if (mkdir(sb_repo.buf, 0777)) + die_errno(_("could not create directory of '%s'"), sb_repo.buf); + junk_git_dir = xstrdup(sb_repo.buf); + is_junk = 1; + + /* + * lock the incomplete repo so prune won't delete it, unlock + * after the preparation is over. + */ + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + write_file(sb.buf, 1, "initializing\n"); + + strbuf_addf(&sb_git, "%s/.git", path); + if (safe_create_leading_directories_const(sb_git.buf)) + die_errno(_("could not create leading directories of '%s'"), + sb_git.buf); + junk_work_tree = xstrdup(path); + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); + write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf)); + write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n", + real_path(get_git_common_dir()), name); + /* + * This is to keep resolve_ref() happy. We need a valid HEAD + * or is_git_directory() will reject the directory. Any valid + * value would do because this value will be ignored and + * replaced at the next (real) checkout. + */ + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); + write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1)); + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/commondir", sb_repo.buf); + write_file(sb.buf, 1, "../..\n"); + + if (!opts->quiet) + fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name); + + setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1); + setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1); + setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1); + memset(&cp, 0, sizeof(cp)); + cp.git_cmd = 1; + cp.argv = opts->saved_argv; + ret = run_command(&cp); + if (!ret) { + is_junk = 0; + free(junk_work_tree); + free(junk_git_dir); + junk_work_tree = NULL; + junk_git_dir = NULL; + } + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + unlink_or_warn(sb.buf); + strbuf_release(&sb); + strbuf_release(&sb_repo); + strbuf_release(&sb_git); + return ret; +} + static int git_checkout_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "diff.ignoresubmodules")) { @@ -880,13 +1037,80 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1) return NULL; } +static void check_linked_checkout(struct branch_info *new, const char *id) +{ + struct strbuf sb = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; + struct strbuf gitdir = STRBUF_INIT; + const char *start, *end; + + if (id) + strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id); + else + strbuf_addf(&path, "%s/HEAD", get_git_common_dir()); + + if (strbuf_read_file(&sb, path.buf, 0) < 0 || + !skip_prefix(sb.buf, "ref:", &start)) + goto done; + while (isspace(*start)) + start++; + end = start; + while (*end && !isspace(*end)) + end++; + if (strncmp(start, new->path, end - start) || new->path[end - start] != '\0') + goto done; + if (id) { + strbuf_reset(&path); + strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id); + if (strbuf_read_file(&gitdir, path.buf, 0) <= 0) + goto done; + strbuf_rtrim(&gitdir); + } else + strbuf_addstr(&gitdir, get_git_common_dir()); + die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf); +done: + strbuf_release(&path); + strbuf_release(&sb); + strbuf_release(&gitdir); +} + +static void check_linked_checkouts(struct branch_info *new) +{ + struct strbuf path = STRBUF_INIT; + DIR *dir; + struct dirent *d; + + strbuf_addf(&path, "%s/worktrees", get_git_common_dir()); + if ((dir = opendir(path.buf)) == NULL) { + strbuf_release(&path); + return; + } + + /* + * $GIT_COMMON_DIR/HEAD is practically outside + * $GIT_DIR so resolve_ref_unsafe() won't work (it + * uses git_path). Parse the ref ourselves. + */ + check_linked_checkout(new, NULL); + + while ((d = readdir(dir)) != NULL) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + check_linked_checkout(new, d->d_name); + } + strbuf_release(&path); + closedir(dir); +} + static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new, - struct tree **source_tree, - unsigned char rev[20], - const char **new_branch) + struct checkout_opts *opts, + unsigned char rev[20]) { + struct tree **source_tree = &opts->source_tree; + const char **new_branch = &opts->new_branch; + int force_detach = opts->force_detach; int argcount = 0; unsigned char branch_rev[20]; const char *arg; @@ -1007,6 +1231,17 @@ static int parse_branchname_arg(int argc, const char **argv, else new->path = NULL; /* not an existing branch */ + if (new->path && !force_detach && !*new_branch) { + unsigned char sha1[20]; + int flag; + char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag); + if (head_ref && + (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) && + !opts->ignore_other_worktrees) + check_linked_checkouts(new); + free(head_ref); + } + new->commit = lookup_commit_reference_gently(rev, 1); if (!new->commit) { /* not a commit */ @@ -1086,6 +1321,9 @@ static int checkout_branch(struct checkout_opts *opts, die(_("Cannot switch branch to a non-commit '%s'"), new->name); + if (opts->new_worktree) + return prepare_linked_checkout(opts, new); + if (!new->commit && opts->new_branch) { unsigned char rev[20]; int flag; @@ -1128,6 +1366,10 @@ 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(), }; @@ -1136,6 +1378,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.overwrite_ignore = 1; opts.prefix = prefix; + opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2)); + memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1)); + gitmodules_config(); git_config(git_checkout_config, &opts); @@ -1144,6 +1389,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); + /* recursive execution from checkout_new_worktree() */ + opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL; + if (opts.new_worktree_mode) + opts.new_worktree = NULL; + + if (!opts.new_worktree) + setup_work_tree(); + if (conflict_style) { opts.merge = 1; /* implied */ git_xmerge_config("merge.conflictstyle", conflict_style, NULL); @@ -1197,8 +1450,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.track == BRANCH_TRACK_UNSPECIFIED && !opts.new_branch; int n = parse_branchname_arg(argc, argv, dwim_ok, - &new, &opts.source_tree, - rev, &opts.new_branch); + &new, &opts, rev); argv += n; argc -= n; } diff --git a/builtin/clean.c b/builtin/clean.c index a9eb7723e9..6dcb72e644 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -939,15 +939,15 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (!cache_name_is_other(ent->name, ent->len)) continue; - if (lstat(ent->name, &st)) - die_errno("Cannot lstat '%s'", ent->name); - if (pathspec.nr) matches = dir_path_match(ent, &pathspec, 0, NULL); if (pathspec.nr && !matches) continue; + if (lstat(ent->name, &st)) + die_errno("Cannot lstat '%s'", ent->name); + if (S_ISDIR(st.st_mode) && !remove_directories && matches != MATCHED_EXACTLY) continue; diff --git a/builtin/clone.c b/builtin/clone.c index 53a2e5af35..b878252bc2 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -293,16 +293,17 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst, struct strbuf line = STRBUF_INIT; while (strbuf_getline(&line, in, '\n') != EOF) { - char *abs_path, abs_buf[PATH_MAX]; + char *abs_path; if (!line.len || line.buf[0] == '#') continue; if (is_absolute_path(line.buf)) { add_to_alternates_file(line.buf); continue; } - abs_path = mkpath("%s/objects/%s", src_repo, line.buf); - normalize_path_copy(abs_buf, abs_path); - add_to_alternates_file(abs_buf); + abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf); + normalize_path_copy(abs_path, abs_path); + add_to_alternates_file(abs_path); + free(abs_path); } strbuf_release(&line); fclose(in); @@ -906,6 +907,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote = remote_get(option_origin); transport = transport_get(remote, remote->url[0]); + transport_set_verbosity(transport, option_verbosity, option_progress); + path = get_repo_path(remote->url[0], &is_bundle); is_local = option_local != 0 && path && !is_bundle; if (is_local) { @@ -932,8 +935,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_single_branch) transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); - transport_set_verbosity(transport, option_verbosity, option_progress); - if (option_upload_pack) transport_set_option(transport, TRANS_OPT_UPLOADPACK, option_upload_pack); diff --git a/builtin/commit.c b/builtin/commit.c index da79ac4bc7..254477fd1d 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -170,7 +170,7 @@ static void determine_whence(struct wt_status *s) whence = FROM_MERGE; else if (file_exists(git_path("CHERRY_PICK_HEAD"))) { whence = FROM_CHERRY_PICK; - if (file_exists(git_path("sequencer"))) + if (file_exists(git_path(SEQ_DIR))) sequencer_in_use = 1; } else @@ -1366,13 +1366,14 @@ int cmd_status(int argc, const char **argv, const char *prefix) refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL); fd = hold_locked_index(&index_lock, 0); - if (0 <= fd) - update_index_if_able(&the_index, &index_lock); s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; s.ignore_submodule_arg = ignore_submodule_arg; wt_status_collect(&s); + if (0 <= fd) + update_index_if_able(&the_index, &index_lock); + if (s.relative_paths) s.prefix = prefix; @@ -1398,12 +1399,10 @@ int cmd_status(int argc, const char **argv, const char *prefix) static const char *implicit_ident_advice(void) { - char *user_config = NULL; - char *xdg_config = NULL; - int config_exists; + char *user_config = expand_user_path("~/.gitconfig"); + char *xdg_config = xdg_config_home("config"); + int config_exists = file_exists(user_config) || file_exists(xdg_config); - home_config_paths(&user_config, &xdg_config, "config"); - config_exists = file_exists(user_config) || file_exists(xdg_config); free(user_config); free(xdg_config); diff --git a/builtin/config.c b/builtin/config.c index bfd3016e83..7188405f7e 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -193,7 +193,7 @@ static int get_value(const char *key_, const char *regex_) key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(key_regexp, key, REG_EXTENDED)) { - fprintf(stderr, "Invalid key pattern: %s\n", key_); + error("invalid key pattern: %s", key_); free(key_regexp); key_regexp = NULL; ret = CONFIG_INVALID_PATTERN; @@ -214,7 +214,7 @@ static int get_value(const char *key_, const char *regex_) regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(regexp, regex_, REG_EXTENDED)) { - fprintf(stderr, "Invalid pattern: %s\n", regex_); + error("invalid pattern: %s", regex_); free(regexp); regexp = NULL; ret = CONFIG_INVALID_PATTERN; @@ -488,10 +488,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) } if (use_global_config) { - char *user_config = NULL; - char *xdg_config = NULL; - - home_config_paths(&user_config, &xdg_config, "config"); + char *user_config = expand_user_path("~/.gitconfig"); + char *xdg_config = xdg_config_home("config"); if (!user_config) /* diff --git a/builtin/count-objects.c b/builtin/count-objects.c index e47ef0b1af..ad0c79954a 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -70,8 +70,10 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) /* we do not take arguments other than flags for now */ if (argc) usage_with_options(count_objects_usage, opts); - if (verbose) + if (verbose) { report_garbage = real_report_garbage; + report_linked_checkout_garbage(); + } for_each_loose_file_in_objdir(get_object_directory(), count_loose, count_cruft, NULL, NULL); diff --git a/builtin/fetch.c b/builtin/fetch.c index f9512652cf..7910419c93 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -588,7 +588,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, struct strbuf note = STRBUF_INIT; const char *what, *kind; struct ref *rm; - char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); + char *url; + const char *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); int want_status; fp = fopen(filename, "a"); @@ -822,7 +823,7 @@ static void check_not_current_branch(struct ref *ref_map) static int truncate_fetch_head(void) { - char *filename = git_path("FETCH_HEAD"); + const char *filename = git_path("FETCH_HEAD"); FILE *fp = fopen(filename, "w"); if (!fp) diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 1d962dc569..05f4c26311 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -223,16 +223,14 @@ static void add_branch_desc(struct strbuf *out, const char *name) #define util_as_integral(elem) ((intptr_t)((elem)->util)) -static void record_person(int which, struct string_list *people, - struct commit *commit) +static void record_person_from_buf(int which, struct string_list *people, + const char *buffer) { - const char *buffer; char *name_buf, *name, *name_end; struct string_list_item *elem; const char *field; field = (which == 'a') ? "\nauthor " : "\ncommitter "; - buffer = get_commit_buffer(commit, NULL); name = strstr(buffer, field); if (!name) return; @@ -245,7 +243,6 @@ static void record_person(int which, struct string_list *people, if (name_end < name) return; name_buf = xmemdupz(name, name_end - name + 1); - unuse_commit_buffer(commit, buffer); elem = string_list_lookup(people, name_buf); if (!elem) { @@ -256,6 +253,15 @@ static void record_person(int which, struct string_list *people, free(name_buf); } + +static void record_person(int which, struct string_list *people, + struct commit *commit) +{ + const char *buffer = get_commit_buffer(commit, NULL); + record_person_from_buf(which, people, buffer); + unuse_commit_buffer(commit, buffer); +} + static int cmp_string_list_util_as_integral(const void *a_, const void *b_) { const struct string_list_item *a = a_, *b = b_; diff --git a/builtin/fsck.c b/builtin/fsck.c index 0c757862e8..4783896fd6 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -225,12 +225,12 @@ static void check_unreachable_object(struct object *obj) printf("dangling %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); if (write_lost_and_found) { - char *filename = git_path("lost-found/%s/%s", + const char *filename = git_path("lost-found/%s/%s", obj->type == OBJ_COMMIT ? "commit" : "other", sha1_to_hex(obj->sha1)); FILE *f; - if (safe_create_leading_directories(filename)) { + if (safe_create_leading_directories_const(filename)) { error("Could not create lost-found"); return; } diff --git a/builtin/gc.c b/builtin/gc.c index 5c634afc00..36fe33300f 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -33,11 +33,13 @@ static int gc_auto_threshold = 6700; static int gc_auto_pack_limit = 50; static int detach_auto = 1; static const char *prune_expire = "2.weeks.ago"; +static const char *prune_worktrees_expire = "3.months.ago"; static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT; static struct argv_array reflog = ARGV_ARRAY_INIT; static struct argv_array repack = ARGV_ARRAY_INIT; static struct argv_array prune = ARGV_ARRAY_INIT; +static struct argv_array prune_worktrees = ARGV_ARRAY_INIT; static struct argv_array rerere = ARGV_ARRAY_INIT; static char *pidfile; @@ -55,6 +57,17 @@ static void remove_pidfile_on_signal(int signo) raise(signo); } +static void git_config_date_string(const char *key, const char **output) +{ + if (git_config_get_string_const(key, output)) + return; + if (strcmp(*output, "now")) { + unsigned long now = approxidate("now"); + if (approxidate(*output) >= now) + git_die_config(key, _("Invalid %s: '%s'"), key, *output); + } +} + static void gc_config(void) { const char *value; @@ -71,16 +84,8 @@ static void gc_config(void) git_config_get_int("gc.auto", &gc_auto_threshold); git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit); git_config_get_bool("gc.autodetach", &detach_auto); - - if (!git_config_get_string_const("gc.pruneexpire", &prune_expire)) { - if (strcmp(prune_expire, "now")) { - unsigned long now = approxidate("now"); - if (approxidate(prune_expire) >= now) { - git_die_config("gc.pruneexpire", _("Invalid gc.pruneexpire: '%s'"), - prune_expire); - } - } - } + git_config_date_string("gc.pruneexpire", &prune_expire); + git_config_date_string("gc.pruneworktreesexpire", &prune_worktrees_expire); git_config(git_default_config, NULL); } @@ -287,7 +292,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL); argv_array_pushl(&repack, "repack", "-d", "-l", NULL); - argv_array_pushl(&prune, "prune", "--expire", NULL ); + argv_array_pushl(&prune, "prune", "--expire", NULL); + argv_array_pushl(&prune_worktrees, "prune", "--worktrees", "--expire", NULL); argv_array_pushl(&rerere, "rerere", "gc", NULL); gc_config(); @@ -357,6 +363,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix) return error(FAILED_RUN, prune.argv[0]); } + if (prune_worktrees_expire) { + argv_array_push(&prune_worktrees, prune_worktrees_expire); + if (run_command_v_opt(prune_worktrees.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, prune_worktrees.argv[0]); + } + if (run_command_v_opt(rerere.argv, RUN_GIT_CMD)) return error(FAILED_RUN, rerere.argv[0]); diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 207b90c7b1..07fef3cc6b 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -22,10 +22,8 @@ static int hash_literally(unsigned char *sha1, int fd, const char *type, unsigne if (strbuf_read(&buf, fd, 4096) < 0) ret = -1; - else if (flags & HASH_WRITE_OBJECT) - ret = write_sha1_file(buf.buf, buf.len, type, sha1); else - ret = hash_sha1_file(buf.buf, buf.len, type, sha1); + ret = hash_sha1_file_literally(buf.buf, buf.len, type, sha1, flags); strbuf_release(&buf); return ret; } diff --git a/builtin/index-pack.c b/builtin/index-pack.c index cf654df09b..7ea2020d82 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -18,16 +18,14 @@ static const char index_pack_usage[] = struct object_entry { struct pack_idx_entry idx; unsigned long size; - unsigned int hdr_size; - enum object_type type; - enum object_type real_type; - unsigned delta_depth; - int base_object_no; + unsigned char hdr_size; + signed char type; + signed char real_type; }; -union delta_base { - unsigned char sha1[20]; - off_t offset; +struct object_stat { + unsigned delta_depth; + int base_object_no; }; struct base_data { @@ -49,25 +47,28 @@ struct thread_local { int pack_fd; }; -/* - * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want - * to memcmp() only the first 20 bytes. - */ -#define UNION_BASE_SZ 20 - #define FLAG_LINK (1u<<20) #define FLAG_CHECKED (1u<<21) -struct delta_entry { - union delta_base base; +struct ofs_delta_entry { + off_t offset; + int obj_no; +}; + +struct ref_delta_entry { + unsigned char sha1[20]; int obj_no; }; static struct object_entry *objects; -static struct delta_entry *deltas; +static struct object_stat *obj_stat; +static struct ofs_delta_entry *ofs_deltas; +static struct ref_delta_entry *ref_deltas; static struct thread_local nothread_data; static int nr_objects; -static int nr_deltas; +static int nr_ofs_deltas; +static int nr_ref_deltas; +static int ref_deltas_alloc; static int nr_resolved_deltas; static int nr_threads; @@ -476,7 +477,8 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size, } static void *unpack_raw_entry(struct object_entry *obj, - union delta_base *delta_base, + off_t *ofs_offset, + unsigned char *ref_sha1, unsigned char *sha1) { unsigned char *p; @@ -505,11 +507,10 @@ static void *unpack_raw_entry(struct object_entry *obj, switch (obj->type) { case OBJ_REF_DELTA: - hashcpy(delta_base->sha1, fill(20)); + hashcpy(ref_sha1, fill(20)); use(20); break; case OBJ_OFS_DELTA: - memset(delta_base, 0, sizeof(*delta_base)); p = fill(1); c = *p; use(1); @@ -523,8 +524,8 @@ static void *unpack_raw_entry(struct object_entry *obj, use(1); base_offset = (base_offset << 7) + (c & 127); } - delta_base->offset = obj->idx.offset - base_offset; - if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset) + *ofs_offset = obj->idx.offset - base_offset; + if (*ofs_offset <= 0 || *ofs_offset >= obj->idx.offset) bad_object(obj->idx.offset, _("delta base offset is out of bound")); break; case OBJ_COMMIT: @@ -608,55 +609,108 @@ static void *get_data_from_pack(struct object_entry *obj) return unpack_data(obj, NULL, NULL); } -static int compare_delta_bases(const union delta_base *base1, - const union delta_base *base2, - enum object_type type1, - enum object_type type2) +static int compare_ofs_delta_bases(off_t offset1, off_t offset2, + enum object_type type1, + enum object_type type2) { int cmp = type1 - type2; if (cmp) return cmp; - return memcmp(base1, base2, UNION_BASE_SZ); + return offset1 - offset2; } -static int find_delta(const union delta_base *base, enum object_type type) +static int find_ofs_delta(const off_t offset, enum object_type type) { - int first = 0, last = nr_deltas; - - while (first < last) { - int next = (first + last) / 2; - struct delta_entry *delta = &deltas[next]; - int cmp; - - cmp = compare_delta_bases(base, &delta->base, - type, objects[delta->obj_no].type); - if (!cmp) - return next; - if (cmp < 0) { - last = next; - continue; - } - first = next+1; - } - return -first-1; + int first = 0, last = nr_ofs_deltas; + + while (first < last) { + int next = (first + last) / 2; + struct ofs_delta_entry *delta = &ofs_deltas[next]; + int cmp; + + cmp = compare_ofs_delta_bases(offset, delta->offset, + type, objects[delta->obj_no].type); + if (!cmp) + return next; + if (cmp < 0) { + last = next; + continue; + } + first = next+1; + } + return -first-1; } -static void find_delta_children(const union delta_base *base, - int *first_index, int *last_index, - enum object_type type) +static void find_ofs_delta_children(off_t offset, + int *first_index, int *last_index, + enum object_type type) { - int first = find_delta(base, type); + int first = find_ofs_delta(offset, type); int last = first; - int end = nr_deltas - 1; + int end = nr_ofs_deltas - 1; if (first < 0) { *first_index = 0; *last_index = -1; return; } - while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ)) + while (first > 0 && ofs_deltas[first - 1].offset == offset) --first; - while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ)) + while (last < end && ofs_deltas[last + 1].offset == offset) + ++last; + *first_index = first; + *last_index = last; +} + +static int compare_ref_delta_bases(const unsigned char *sha1, + const unsigned char *sha2, + enum object_type type1, + enum object_type type2) +{ + int cmp = type1 - type2; + if (cmp) + return cmp; + return hashcmp(sha1, sha2); +} + +static int find_ref_delta(const unsigned char *sha1, enum object_type type) +{ + int first = 0, last = nr_ref_deltas; + + while (first < last) { + int next = (first + last) / 2; + struct ref_delta_entry *delta = &ref_deltas[next]; + int cmp; + + cmp = compare_ref_delta_bases(sha1, delta->sha1, + type, objects[delta->obj_no].type); + if (!cmp) + return next; + if (cmp < 0) { + last = next; + continue; + } + first = next+1; + } + return -first-1; +} + +static void find_ref_delta_children(const unsigned char *sha1, + int *first_index, int *last_index, + enum object_type type) +{ + int first = find_ref_delta(sha1, type); + int last = first; + int end = nr_ref_deltas - 1; + + if (first < 0) { + *first_index = 0; + *last_index = -1; + return; + } + while (first > 0 && !hashcmp(ref_deltas[first - 1].sha1, sha1)) + --first; + while (last < end && !hashcmp(ref_deltas[last + 1].sha1, sha1)) ++last; *first_index = first; *last_index = last; @@ -873,13 +927,15 @@ static void resolve_delta(struct object_entry *delta_obj, void *base_data, *delta_data; if (show_stat) { - delta_obj->delta_depth = base->obj->delta_depth + 1; + int i = delta_obj - objects; + int j = base->obj - objects; + obj_stat[i].delta_depth = obj_stat[j].delta_depth + 1; deepest_delta_lock(); - if (deepest_delta < delta_obj->delta_depth) - deepest_delta = delta_obj->delta_depth; + if (deepest_delta < obj_stat[i].delta_depth) + deepest_delta = obj_stat[i].delta_depth; deepest_delta_unlock(); + obj_stat[i].base_object_no = j; } - delta_obj->base_object_no = base->obj - objects; delta_data = get_data_from_pack(delta_obj); base_data = get_base_data(base); result->obj = delta_obj; @@ -902,7 +958,7 @@ static void resolve_delta(struct object_entry *delta_obj, * "want"; if so, swap in "set" and return true. Otherwise, leave it untouched * and return false. */ -static int compare_and_swap_type(enum object_type *type, +static int compare_and_swap_type(signed char *type, enum object_type want, enum object_type set) { @@ -921,16 +977,13 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base, struct base_data *prev_base) { if (base->ref_last == -1 && base->ofs_last == -1) { - union delta_base base_spec; + find_ref_delta_children(base->obj->idx.sha1, + &base->ref_first, &base->ref_last, + OBJ_REF_DELTA); - hashcpy(base_spec.sha1, base->obj->idx.sha1); - find_delta_children(&base_spec, - &base->ref_first, &base->ref_last, OBJ_REF_DELTA); - - memset(&base_spec, 0, sizeof(base_spec)); - base_spec.offset = base->obj->idx.offset; - find_delta_children(&base_spec, - &base->ofs_first, &base->ofs_last, OBJ_OFS_DELTA); + find_ofs_delta_children(base->obj->idx.offset, + &base->ofs_first, &base->ofs_last, + OBJ_OFS_DELTA); if (base->ref_last == -1 && base->ofs_last == -1) { free(base->data); @@ -941,7 +994,7 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base, } if (base->ref_first <= base->ref_last) { - struct object_entry *child = objects + deltas[base->ref_first].obj_no; + struct object_entry *child = objects + ref_deltas[base->ref_first].obj_no; struct base_data *result = alloc_base_data(); if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA, @@ -957,7 +1010,7 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base, } if (base->ofs_first <= base->ofs_last) { - struct object_entry *child = objects + deltas[base->ofs_first].obj_no; + struct object_entry *child = objects + ofs_deltas[base->ofs_first].obj_no; struct base_data *result = alloc_base_data(); assert(child->real_type == OBJ_OFS_DELTA); @@ -993,15 +1046,20 @@ static void find_unresolved_deltas(struct base_data *base) } } -static int compare_delta_entry(const void *a, const void *b) +static int compare_ofs_delta_entry(const void *a, const void *b) { - const struct delta_entry *delta_a = a; - const struct delta_entry *delta_b = b; + const struct ofs_delta_entry *delta_a = a; + const struct ofs_delta_entry *delta_b = b; - /* group by type (ref vs ofs) and then by value (sha-1 or offset) */ - return compare_delta_bases(&delta_a->base, &delta_b->base, - objects[delta_a->obj_no].type, - objects[delta_b->obj_no].type); + return delta_a->offset - delta_b->offset; +} + +static int compare_ref_delta_entry(const void *a, const void *b) +{ + const struct ref_delta_entry *delta_a = a; + const struct ref_delta_entry *delta_b = b; + + return hashcmp(delta_a->sha1, delta_b->sha1); } static void resolve_base(struct object_entry *obj) @@ -1047,7 +1105,8 @@ static void *threaded_second_pass(void *data) static void parse_pack_objects(unsigned char *sha1) { int i, nr_delays = 0; - struct delta_entry *delta = deltas; + struct ofs_delta_entry *ofs_delta = ofs_deltas; + unsigned char ref_delta_sha1[20]; struct stat st; if (verbose) @@ -1056,12 +1115,18 @@ static void parse_pack_objects(unsigned char *sha1) nr_objects); for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; - void *data = unpack_raw_entry(obj, &delta->base, obj->idx.sha1); + void *data = unpack_raw_entry(obj, &ofs_delta->offset, + ref_delta_sha1, obj->idx.sha1); obj->real_type = obj->type; - if (is_delta_type(obj->type)) { - nr_deltas++; - delta->obj_no = i; - delta++; + if (obj->type == OBJ_OFS_DELTA) { + nr_ofs_deltas++; + ofs_delta->obj_no = i; + ofs_delta++; + } else if (obj->type == OBJ_REF_DELTA) { + ALLOC_GROW(ref_deltas, nr_ref_deltas + 1, ref_deltas_alloc); + hashcpy(ref_deltas[nr_ref_deltas].sha1, ref_delta_sha1); + ref_deltas[nr_ref_deltas].obj_no = i; + nr_ref_deltas++; } else if (!data) { /* large blobs, check later */ obj->real_type = OBJ_BAD; @@ -1112,15 +1177,18 @@ static void resolve_deltas(void) { int i; - if (!nr_deltas) + if (!nr_ofs_deltas && !nr_ref_deltas) return; /* Sort deltas by base SHA1/offset for fast searching */ - qsort(deltas, nr_deltas, sizeof(struct delta_entry), - compare_delta_entry); + qsort(ofs_deltas, nr_ofs_deltas, sizeof(struct ofs_delta_entry), + compare_ofs_delta_entry); + qsort(ref_deltas, nr_ref_deltas, sizeof(struct ref_delta_entry), + compare_ref_delta_entry); if (verbose) - progress = start_progress(_("Resolving deltas"), nr_deltas); + progress = start_progress(_("Resolving deltas"), + nr_ref_deltas + nr_ofs_deltas); #ifndef NO_PTHREADS nr_dispatched = 0; @@ -1158,7 +1226,7 @@ static void resolve_deltas(void) static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved); static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1) { - if (nr_deltas == nr_resolved_deltas) { + if (nr_ref_deltas + nr_ofs_deltas == nr_resolved_deltas) { stop_progress(&progress); /* Flush remaining pack final 20-byte SHA1. */ flush(); @@ -1169,7 +1237,7 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha struct sha1file *f; unsigned char read_sha1[20], tail_sha1[20]; struct strbuf msg = STRBUF_INIT; - int nr_unresolved = nr_deltas - nr_resolved_deltas; + int nr_unresolved = nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas; int nr_objects_initial = nr_objects; if (nr_unresolved <= 0) die(_("confusion beyond insanity")); @@ -1191,11 +1259,11 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha die(_("Unexpected tail checksum for %s " "(disk corruption?)"), curr_pack); } - if (nr_deltas != nr_resolved_deltas) + if (nr_ofs_deltas + nr_ref_deltas != nr_resolved_deltas) die(Q_("pack has %d unresolved delta", "pack has %d unresolved deltas", - nr_deltas - nr_resolved_deltas), - nr_deltas - nr_resolved_deltas); + nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas), + nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas); } static int write_compressed(struct sha1file *f, void *in, unsigned int size) @@ -1254,14 +1322,14 @@ static struct object_entry *append_obj_to_pack(struct sha1file *f, static int delta_pos_compare(const void *_a, const void *_b) { - struct delta_entry *a = *(struct delta_entry **)_a; - struct delta_entry *b = *(struct delta_entry **)_b; + struct ref_delta_entry *a = *(struct ref_delta_entry **)_a; + struct ref_delta_entry *b = *(struct ref_delta_entry **)_b; return a->obj_no - b->obj_no; } static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) { - struct delta_entry **sorted_by_pos; + struct ref_delta_entry **sorted_by_pos; int i, n = 0; /* @@ -1275,28 +1343,25 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) * resolving deltas in the same order as their position in the pack. */ sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos)); - for (i = 0; i < nr_deltas; i++) { - if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA) - continue; - sorted_by_pos[n++] = &deltas[i]; - } + for (i = 0; i < nr_ref_deltas; i++) + sorted_by_pos[n++] = &ref_deltas[i]; qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare); for (i = 0; i < n; i++) { - struct delta_entry *d = sorted_by_pos[i]; + struct ref_delta_entry *d = sorted_by_pos[i]; enum object_type type; struct base_data *base_obj = alloc_base_data(); if (objects[d->obj_no].real_type != OBJ_REF_DELTA) continue; - base_obj->data = read_sha1_file(d->base.sha1, &type, &base_obj->size); + base_obj->data = read_sha1_file(d->sha1, &type, &base_obj->size); if (!base_obj->data) continue; - if (check_sha1_signature(d->base.sha1, base_obj->data, + if (check_sha1_signature(d->sha1, base_obj->data, base_obj->size, typename(type))) - die(_("local object %s is corrupt"), sha1_to_hex(d->base.sha1)); - base_obj->obj = append_obj_to_pack(f, d->base.sha1, + die(_("local object %s is corrupt"), sha1_to_hex(d->sha1)); + base_obj->obj = append_obj_to_pack(f, d->sha1, base_obj->data, base_obj->size, type); find_unresolved_deltas(base_obj); display_progress(progress, nr_resolved_deltas); @@ -1488,7 +1553,7 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name) static void show_pack_info(int stat_only) { - int i, baseobjects = nr_objects - nr_deltas; + int i, baseobjects = nr_objects - nr_ref_deltas - nr_ofs_deltas; unsigned long *chain_histogram = NULL; if (deepest_delta) @@ -1498,7 +1563,7 @@ static void show_pack_info(int stat_only) struct object_entry *obj = &objects[i]; if (is_delta_type(obj->type)) - chain_histogram[obj->delta_depth - 1]++; + chain_histogram[obj_stat[i].delta_depth - 1]++; if (stat_only) continue; printf("%s %-6s %lu %lu %"PRIuMAX, @@ -1507,8 +1572,8 @@ static void show_pack_info(int stat_only) (unsigned long)(obj[1].idx.offset - obj->idx.offset), (uintmax_t)obj->idx.offset); if (is_delta_type(obj->type)) { - struct object_entry *bobj = &objects[obj->base_object_no]; - printf(" %u %s", obj->delta_depth, sha1_to_hex(bobj->idx.sha1)); + struct object_entry *bobj = &objects[obj_stat[i].base_object_no]; + printf(" %u %s", obj_stat[i].delta_depth, sha1_to_hex(bobj->idx.sha1)); } putchar('\n'); } @@ -1671,11 +1736,14 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) curr_pack = open_pack_file(pack_name); parse_pack_header(); objects = xcalloc(nr_objects + 1, sizeof(struct object_entry)); - deltas = xcalloc(nr_objects, sizeof(struct delta_entry)); + if (show_stat) + obj_stat = xcalloc(nr_objects + 1, sizeof(struct object_stat)); + ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry)); parse_pack_objects(pack_sha1); resolve_deltas(); conclude_pack(fix_thin_pack, curr_pack, pack_sha1); - free(deltas); + free(ofs_deltas); + free(ref_deltas); if (strict) foreign_nr = check_objects(); diff --git a/builtin/init-db.c b/builtin/init-db.c index ab9f86b889..4335738135 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -362,7 +362,6 @@ int set_git_dir_init(const char *git_dir, const char *real_git_dir, static void separate_git_dir(const char *git_dir) { struct stat st; - FILE *fp; if (!stat(git_link, &st)) { const char *src; @@ -378,11 +377,7 @@ static void separate_git_dir(const char *git_dir) die_errno(_("unable to move %s to %s"), src, git_dir); } - fp = fopen(git_link, "w"); - if (!fp) - die(_("Could not create git link %s"), git_link); - fprintf(fp, "gitdir: %s\n", git_dir); - fclose(fp); + write_file(git_link, 1, "gitdir: %s\n", git_dir); } int init_db(const char *template_dir, unsigned int flags) diff --git a/builtin/log.c b/builtin/log.c index dd8f3fcfc4..4c4e6be28c 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -38,7 +38,7 @@ static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; static const char * const builtin_log_usage[] = { - N_("git log [<options>] [<revision range>] [[--] <path>...]"), + N_("git log [<options>] [<revision-range>] [[--] <path>...]"), N_("git show [<options>] <object>..."), NULL }; diff --git a/builtin/merge.c b/builtin/merge.c index 3b0f8f96d4..f89f60e11a 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -492,8 +492,7 @@ static void merge_name(const char *remote, struct strbuf *msg) } if (len) { struct strbuf truname = STRBUF_INIT; - strbuf_addstr(&truname, "refs/heads/"); - strbuf_addstr(&truname, remote); + strbuf_addf(&truname, "refs/heads/%s", remote); strbuf_setlen(&truname, truname.len - len); if (ref_exists(truname.buf)) { strbuf_addf(msg, @@ -504,28 +503,7 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_release(&truname); goto cleanup; } - } - - if (!strcmp(remote, "FETCH_HEAD") && - !access(git_path("FETCH_HEAD"), R_OK)) { - const char *filename; - FILE *fp; - struct strbuf line = STRBUF_INIT; - char *ptr; - - filename = git_path("FETCH_HEAD"); - fp = fopen(filename, "r"); - if (!fp) - die_errno(_("could not open '%s' for reading"), - filename); - strbuf_getline(&line, fp, '\n'); - fclose(fp); - ptr = strstr(line.buf, "\tnot-for-merge\t"); - if (ptr) - strbuf_remove(&line, ptr-line.buf+1, 13); - strbuf_addbuf(msg, &line); - strbuf_release(&line); - goto cleanup; + strbuf_release(&truname); } if (remote_head->util) { @@ -1037,28 +1015,24 @@ static int default_edit_option(void) st_stdin.st_mode == st_stdout.st_mode); } -static struct commit_list *collect_parents(struct commit *head_commit, - int *head_subsumed, - int argc, const char **argv) +static struct commit_list *reduce_parents(struct commit *head_commit, + int *head_subsumed, + struct commit_list *remoteheads) { - int i; - struct commit_list *remoteheads = NULL, *parents, *next; - struct commit_list **remotes = &remoteheads; + struct commit_list *parents, *next, **remotes = &remoteheads; - if (head_commit) - remotes = &commit_list_insert(head_commit, remotes)->next; - for (i = 0; i < argc; i++) { - struct commit *commit = get_merge_parent(argv[i]); - if (!commit) - help_unknown_ref(argv[i], "merge", - "not something we can merge"); - remotes = &commit_list_insert(commit, remotes)->next; - } - *remotes = NULL; + /* + * Is the current HEAD reachable from another commit being + * merged? If so we do not want to record it as a parent of + * the resulting merge, unless --no-ff is given. We will flip + * this variable to 0 when we find HEAD among the independent + * tips being merged. + */ + *head_subsumed = 1; + /* Find what parents to record by checking independent ones. */ parents = reduce_heads(remoteheads); - *head_subsumed = 1; /* we will flip this to 0 when we find it */ for (remoteheads = NULL, remotes = &remoteheads; parents; parents = next) { @@ -1068,7 +1042,119 @@ static struct commit_list *collect_parents(struct commit *head_commit, *head_subsumed = 0; else remotes = &commit_list_insert(commit, remotes)->next; + free(parents); + } + return remoteheads; +} + +static void prepare_merge_message(struct strbuf *merge_names, struct strbuf *merge_msg) +{ + struct fmt_merge_msg_opts opts; + + memset(&opts, 0, sizeof(opts)); + opts.add_title = !have_message; + opts.shortlog_len = shortlog_len; + opts.credit_people = (0 < option_edit); + + fmt_merge_msg(merge_names, merge_msg, &opts); + if (merge_msg->len) + strbuf_setlen(merge_msg, merge_msg->len - 1); +} + +static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge_names) +{ + const char *filename; + int fd, pos, npos; + struct strbuf fetch_head_file = STRBUF_INIT; + + if (!merge_names) + merge_names = &fetch_head_file; + + filename = git_path("FETCH_HEAD"); + fd = open(filename, O_RDONLY); + if (fd < 0) + die_errno(_("could not open '%s' for reading"), filename); + + if (strbuf_read(merge_names, fd, 0) < 0) + die_errno(_("could not read '%s'"), filename); + if (close(fd) < 0) + die_errno(_("could not close '%s'"), filename); + + for (pos = 0; pos < merge_names->len; pos = npos) { + unsigned char sha1[20]; + char *ptr; + struct commit *commit; + + ptr = strchr(merge_names->buf + pos, '\n'); + if (ptr) + npos = ptr - merge_names->buf + 1; + else + npos = merge_names->len; + + if (npos - pos < 40 + 2 || + get_sha1_hex(merge_names->buf + pos, sha1)) + commit = NULL; /* bad */ + else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2)) + continue; /* not-for-merge */ + else { + char saved = merge_names->buf[pos + 40]; + merge_names->buf[pos + 40] = '\0'; + commit = get_merge_parent(merge_names->buf + pos); + merge_names->buf[pos + 40] = saved; + } + if (!commit) { + if (ptr) + *ptr = '\0'; + die("not something we can merge in %s: %s", + filename, merge_names->buf + pos); + } + remotes = &commit_list_insert(commit, remotes)->next; + } + + if (merge_names == &fetch_head_file) + strbuf_release(&fetch_head_file); +} + +static struct commit_list *collect_parents(struct commit *head_commit, + int *head_subsumed, + int argc, const char **argv, + struct strbuf *merge_msg) +{ + int i; + struct commit_list *remoteheads = NULL; + struct commit_list **remotes = &remoteheads; + struct strbuf merge_names = STRBUF_INIT, *autogen = NULL; + + if (merge_msg && (!have_message || shortlog_len)) + autogen = &merge_names; + + if (head_commit) + remotes = &commit_list_insert(head_commit, remotes)->next; + + if (argc == 1 && !strcmp(argv[0], "FETCH_HEAD")) { + handle_fetch_head(remotes, autogen); + remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads); + } else { + for (i = 0; i < argc; i++) { + struct commit *commit = get_merge_parent(argv[i]); + if (!commit) + help_unknown_ref(argv[i], "merge", + "not something we can merge"); + remotes = &commit_list_insert(commit, remotes)->next; + } + remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads); + if (autogen) { + struct commit_list *p; + for (p = remoteheads; p; p = p->next) + merge_name(merge_remote_util(p->item)->name, autogen); + } } + + if (autogen) { + prepare_merge_message(autogen, merge_msg); + strbuf_release(autogen); + } + return remoteheads; } @@ -1158,61 +1244,62 @@ int cmd_merge(int argc, const char **argv, const char *prefix) option_commit = 0; } - if (!abort_current_merge) { - if (!argc) { - if (default_to_upstream) - argc = setup_with_upstream(&argv); - else - die(_("No commit specified and merge.defaultToUpstream not set.")); - } else if (argc == 1 && !strcmp(argv[0], "-")) - argv[0] = "@{-1}"; + if (!argc) { + if (default_to_upstream) + argc = setup_with_upstream(&argv); + else + die(_("No commit specified and merge.defaultToUpstream not set.")); + } else if (argc == 1 && !strcmp(argv[0], "-")) { + argv[0] = "@{-1}"; } + if (!argc) usage_with_options(builtin_merge_usage, builtin_merge_options); - /* - * This could be traditional "merge <msg> HEAD <commit>..." and - * the way we can tell it is to see if the second token is HEAD, - * but some people might have misused the interface and used a - * commit-ish that is the same as HEAD there instead. - * Traditional format never would have "-m" so it is an - * additional safety measure to check for it. - */ - - if (!have_message && head_commit && - is_old_style_invocation(argc, argv, head_commit->object.sha1)) { - strbuf_addstr(&merge_msg, argv[0]); - head_arg = argv[1]; - argv += 2; - argc -= 2; - remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); - } else if (!head_commit) { + if (!head_commit) { struct commit *remote_head; /* * If the merged head is a valid one there is no reason * to forbid "git merge" into a branch yet to be born. * We do the same for "git pull". */ - if (argc != 1) - die(_("Can merge only exactly one commit into " - "empty head")); if (squash) die(_("Squash commit into empty head not supported yet")); if (fast_forward == FF_NO) die(_("Non-fast-forward commit does not make sense into " "an empty head")); - remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); + remoteheads = collect_parents(head_commit, &head_subsumed, + argc, argv, NULL); remote_head = remoteheads->item; if (!remote_head) die(_("%s - not something we can merge"), argv[0]); + if (remoteheads->next) + die(_("Can merge only exactly one commit into empty head")); read_empty(remote_head->object.sha1, 0); update_ref("initial pull", "HEAD", remote_head->object.sha1, NULL, 0, UPDATE_REFS_DIE_ON_ERR); goto done; - } else { - struct strbuf merge_names = STRBUF_INIT; + } + /* + * This could be traditional "merge <msg> HEAD <commit>..." and + * the way we can tell it is to see if the second token is HEAD, + * but some people might have misused the interface and used a + * commit-ish that is the same as HEAD there instead. + * Traditional format never would have "-m" so it is an + * additional safety measure to check for it. + */ + if (!have_message && + is_old_style_invocation(argc, argv, head_commit->object.sha1)) { + warning("old-style 'git merge <msg> HEAD <commit>' is deprecated."); + strbuf_addstr(&merge_msg, argv[0]); + head_arg = argv[1]; + argv += 2; + argc -= 2; + remoteheads = collect_parents(head_commit, &head_subsumed, + argc, argv, NULL); + } else { /* We are invoked directly as the first-class UI. */ head_arg = "HEAD"; @@ -1221,21 +1308,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * the standard merge summary message to be appended * to the given message. */ - remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); - for (p = remoteheads; p; p = p->next) - merge_name(merge_remote_util(p->item)->name, &merge_names); - - if (!have_message || shortlog_len) { - struct fmt_merge_msg_opts opts; - memset(&opts, 0, sizeof(opts)); - opts.add_title = !have_message; - opts.shortlog_len = shortlog_len; - opts.credit_people = (0 < option_edit); - - fmt_merge_msg(&merge_names, &merge_msg, &opts); - if (merge_msg.len) - strbuf_setlen(&merge_msg, merge_msg.len - 1); - } + remoteheads = collect_parents(head_commit, &head_subsumed, + argc, argv, &merge_msg); } if (!head_commit || !argc) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index c3a75166bd..c067107a6a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -961,10 +961,8 @@ static int want_object_in_pack(const unsigned char *sha1, off_t offset = find_pack_entry_one(sha1, p); if (offset) { if (!*found_pack) { - if (!is_pack_valid(p)) { - warning("packfile %s cannot be accessed", p->pack_name); + if (!is_pack_valid(p)) continue; - } *found_offset = offset; *found_pack = p; } diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 77db8739b5..ba34dac4d2 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -1,14 +1,14 @@ #include "builtin.h" -static void flush_current_id(int patchlen, unsigned char *id, unsigned char *result) +static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result) { char name[50]; if (!patchlen) return; - memcpy(name, sha1_to_hex(id), 41); - printf("%s %s\n", sha1_to_hex(result), name); + memcpy(name, oid_to_hex(id), GIT_SHA1_HEXSZ + 1); + printf("%s %s\n", oid_to_hex(result), name); } static int remove_space(char *line) @@ -53,23 +53,23 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) return 1; } -static void flush_one_hunk(unsigned char *result, git_SHA_CTX *ctx) +static void flush_one_hunk(struct object_id *result, git_SHA_CTX *ctx) { - unsigned char hash[20]; + unsigned char hash[GIT_SHA1_RAWSZ]; unsigned short carry = 0; int i; git_SHA1_Final(hash, ctx); git_SHA1_Init(ctx); /* 20-byte sum, with carry */ - for (i = 0; i < 20; ++i) { - carry += result[i] + hash[i]; - result[i] = carry; + for (i = 0; i < GIT_SHA1_RAWSZ; ++i) { + carry += result->hash[i] + hash[i]; + result->hash[i] = carry; carry >>= 8; } } -static int get_one_patchid(unsigned char *next_sha1, unsigned char *result, +static int get_one_patchid(struct object_id *next_oid, struct object_id *result, struct strbuf *line_buf, int stable) { int patchlen = 0, found_next = 0; @@ -77,7 +77,7 @@ static int get_one_patchid(unsigned char *next_sha1, unsigned char *result, git_SHA_CTX ctx; git_SHA1_Init(&ctx); - hashclr(result); + oidclr(result); while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) { char *line = line_buf->buf; @@ -93,7 +93,7 @@ static int get_one_patchid(unsigned char *next_sha1, unsigned char *result, else if (!memcmp(line, "\\ ", 2) && 12 < strlen(line)) continue; - if (!get_sha1_hex(p, next_sha1)) { + if (!get_oid_hex(p, next_oid)) { found_next = 1; break; } @@ -143,7 +143,7 @@ static int get_one_patchid(unsigned char *next_sha1, unsigned char *result, } if (!found_next) - hashclr(next_sha1); + oidclr(next_oid); flush_one_hunk(result, &ctx); @@ -152,15 +152,15 @@ static int get_one_patchid(unsigned char *next_sha1, unsigned char *result, static void generate_id_list(int stable) { - unsigned char sha1[20], n[20], result[20]; + struct object_id oid, n, result; int patchlen; struct strbuf line_buf = STRBUF_INIT; - hashclr(sha1); + oidclr(&oid); while (!feof(stdin)) { - patchlen = get_one_patchid(n, result, &line_buf, stable); - flush_current_id(patchlen, sha1, result); - hashcpy(sha1, n); + patchlen = get_one_patchid(&n, &result, &line_buf, stable); + flush_current_id(patchlen, &oid, &result); + oidcpy(&oid, &n); } strbuf_release(&line_buf); } diff --git a/builtin/prune.c b/builtin/prune.c index 17094ad954..0c73246c72 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -76,6 +76,95 @@ 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 @@ -102,10 +191,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct progress *progress = NULL; + int do_prune_worktrees = 0; const struct option options[] = { OPT__DRY_RUN(&show_only, N_("do not remove, show only")), OPT__VERBOSE(&verbose, N_("report pruned objects")), OPT_BOOL(0, "progress", &show_progress, N_("show progress")), + OPT_BOOL(0, "worktrees", &do_prune_worktrees, N_("prune .git/worktrees")), OPT_EXPIRY_DATE(0, "expire", &expire, N_("expire objects older than <time>")), OPT_END() @@ -119,6 +210,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix) init_revisions(&revs, prefix); argc = parse_options(argc, argv, prefix, options, prune_usage, 0); + + if (do_prune_worktrees) { + if (argc) + die(_("--worktrees does not take extra arguments")); + prune_worktrees(); + return 0; + } + while (argc--) { unsigned char sha1[20]; const char *name = *argv++; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 5292bb5a50..d2ec52bca9 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1008,7 +1008,7 @@ static void run_update_post_hook(struct command *commands) int argc; const char **argv; struct child_process proc = CHILD_PROCESS_INIT; - char *hook; + const char *hook; hook = find_hook("post-update"); for (argc = 0, cmd = commands; cmd; cmd = cmd->next) { diff --git a/builtin/remote.c b/builtin/remote.c index 5d3ab906bc..ad57fc984e 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -584,7 +584,7 @@ static int migrate_file(struct remote *remote) { struct strbuf buf = STRBUF_INIT; int i; - char *path = NULL; + const char *path = NULL; strbuf_addf(&buf, "remote.%s.url", remote->name); for (i = 0; i < remote->url_nr; i++) diff --git a/builtin/repack.c b/builtin/repack.c index f2edeb0f4c..af7340c7ba 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -285,7 +285,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) failed = 0; for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - char *fname, *fname_old; + const char *fname_old; + char *fname; fname = mkpathdup("%s/pack-%s%s", packdir, item->string, exts[ext].name); if (!file_exists(fname)) { @@ -313,7 +314,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (failed) { struct string_list rollback_failure = STRING_LIST_INIT_DUP; for_each_string_list_item(item, &rollback) { - char *fname, *fname_old; + const char *fname_old; + char *fname; fname = mkpathdup("%s/%s", packdir, item->string); fname_old = mkpath("%s/old-%s", packdir, item->string); if (rename(fname_old, fname)) @@ -366,7 +368,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) /* Remove the "old-" files */ for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - char *fname; + const char *fname; fname = mkpath("%s/old-%s%s", packdir, item->string, diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 3626c61da6..4d10dd9545 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -533,6 +533,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) { const char *arg = argv[i]; + if (!strcmp(arg, "--git-path")) { + if (!argv[i + 1]) + die("--git-path requires an argument"); + puts(git_path("%s", argv[i + 1])); + i++; + continue; + } if (as_is) { if (show_file(arg, output_prefix) && as_is < 2) verify_filename(prefix, arg, 0); @@ -755,6 +762,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) free(cwd); continue; } + if (!strcmp(arg, "--git-common-dir")) { + puts(get_git_common_dir()); + continue; + } if (!strcmp(arg, "--resolve-git-dir")) { const char *gitdir = argv[++i]; if (!gitdir) diff --git a/builtin/show-branch.c b/builtin/show-branch.c index f3fb5fb2bf..e69fb7c489 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -718,7 +718,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) } /* If nothing is specified, show all branches by default */ - if (ac + all_heads + all_remotes == 0) + if (ac <= topics && all_heads + all_remotes == 0) all_heads = 1; if (reflog) { @@ -785,13 +785,13 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) } free(ref); } - else if (all_heads + all_remotes) - snarf_refs(all_heads, all_remotes); else { while (0 < ac) { append_one_rev(*av); ac--; av++; } + if (all_heads + all_remotes) + snarf_refs(all_heads, all_remotes); } head_p = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, diff --git a/builtin/update-index.c b/builtin/update-index.c index 6271b54adc..7431938fa6 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -33,6 +33,7 @@ static int mark_valid_only; static int mark_skip_worktree_only; #define MARK_FLAG 1 #define UNMARK_FLAG 2 +static struct strbuf mtime_dir = STRBUF_INIT; __attribute__((format (printf, 1, 2))) static void report(const char *fmt, ...) @@ -48,6 +49,166 @@ static void report(const char *fmt, ...) va_end(vp); } +static void remove_test_directory(void) +{ + if (mtime_dir.len) + remove_dir_recursively(&mtime_dir, 0); +} + +static const char *get_mtime_path(const char *path) +{ + static struct strbuf sb = STRBUF_INIT; + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/%s", mtime_dir.buf, path); + return sb.buf; +} + +static void xmkdir(const char *path) +{ + path = get_mtime_path(path); + if (mkdir(path, 0700)) + die_errno(_("failed to create directory %s"), path); +} + +static int xstat_mtime_dir(struct stat *st) +{ + if (stat(mtime_dir.buf, st)) + die_errno(_("failed to stat %s"), mtime_dir.buf); + return 0; +} + +static int create_file(const char *path) +{ + int fd; + path = get_mtime_path(path); + fd = open(path, O_CREAT | O_RDWR, 0644); + if (fd < 0) + die_errno(_("failed to create file %s"), path); + return fd; +} + +static void xunlink(const char *path) +{ + path = get_mtime_path(path); + if (unlink(path)) + die_errno(_("failed to delete file %s"), path); +} + +static void xrmdir(const char *path) +{ + path = get_mtime_path(path); + if (rmdir(path)) + die_errno(_("failed to delete directory %s"), path); +} + +static void avoid_racy(void) +{ + /* + * not use if we could usleep(10) if USE_NSEC is defined. The + * field nsec could be there, but the OS could choose to + * ignore it? + */ + sleep(1); +} + +static int test_if_untracked_cache_is_supported(void) +{ + struct stat st; + struct stat_data base; + int fd, ret = 0; + + strbuf_addstr(&mtime_dir, "mtime-test-XXXXXX"); + if (!mkdtemp(mtime_dir.buf)) + die_errno("Could not make temporary directory"); + + fprintf(stderr, _("Testing ")); + atexit(remove_test_directory); + xstat_mtime_dir(&st); + fill_stat_data(&base, &st); + fputc('.', stderr); + + avoid_racy(); + fd = create_file("newfile"); + xstat_mtime_dir(&st); + if (!match_stat_data(&base, &st)) { + close(fd); + fputc('\n', stderr); + fprintf_ln(stderr,_("directory stat info does not " + "change after adding a new file")); + goto done; + } + fill_stat_data(&base, &st); + fputc('.', stderr); + + avoid_racy(); + xmkdir("new-dir"); + xstat_mtime_dir(&st); + if (!match_stat_data(&base, &st)) { + close(fd); + fputc('\n', stderr); + fprintf_ln(stderr, _("directory stat info does not change " + "after adding a new directory")); + goto done; + } + fill_stat_data(&base, &st); + fputc('.', stderr); + + avoid_racy(); + write_or_die(fd, "data", 4); + close(fd); + xstat_mtime_dir(&st); + if (match_stat_data(&base, &st)) { + fputc('\n', stderr); + fprintf_ln(stderr, _("directory stat info changes " + "after updating a file")); + goto done; + } + fputc('.', stderr); + + avoid_racy(); + close(create_file("new-dir/new")); + xstat_mtime_dir(&st); + if (match_stat_data(&base, &st)) { + fputc('\n', stderr); + fprintf_ln(stderr, _("directory stat info changes after " + "adding a file inside subdirectory")); + goto done; + } + fputc('.', stderr); + + avoid_racy(); + xunlink("newfile"); + xstat_mtime_dir(&st); + if (!match_stat_data(&base, &st)) { + fputc('\n', stderr); + fprintf_ln(stderr, _("directory stat info does not " + "change after deleting a file")); + goto done; + } + fill_stat_data(&base, &st); + fputc('.', stderr); + + avoid_racy(); + xunlink("new-dir/new"); + xrmdir("new-dir"); + xstat_mtime_dir(&st); + if (!match_stat_data(&base, &st)) { + fputc('\n', stderr); + fprintf_ln(stderr, _("directory stat info does not " + "change after deleting a directory")); + goto done; + } + + if (rmdir(mtime_dir.buf)) + die_errno(_("failed to delete directory %s"), mtime_dir.buf); + fprintf_ln(stderr, _(" OK")); + ret = 1; + +done: + strbuf_release(&mtime_dir); + return ret; +} + static int mark_ce_flags(const char *path, int flag, int mark) { int namelen = strlen(path); @@ -532,10 +693,9 @@ static int do_unresolve(int ac, const char **av, for (i = 1; i < ac; i++) { const char *arg = av[i]; - const char *p = prefix_path(prefix, prefix_length, arg); + char *p = prefix_path(prefix, prefix_length, arg); err |= unresolve_one(p); - if (p < arg || p > arg + strlen(arg)) - free((char *)p); + free(p); } return err; } @@ -742,6 +902,7 @@ static int reupdate_callback(struct parse_opt_ctx_t *ctx, int cmd_update_index(int argc, const char **argv, const char *prefix) { int newfd, entries, has_errors = 0, line_termination = '\n'; + int untracked_cache = -1; int read_from_stdin = 0; int prefix_length = prefix ? strlen(prefix) : 0; int preferred_index_format = 0; @@ -833,6 +994,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) N_("write index in this format")), OPT_BOOL(0, "split-index", &split_index, N_("enable or disable split index")), + OPT_BOOL(0, "untracked-cache", &untracked_cache, + N_("enable/disable untracked cache")), + OPT_SET_INT(0, "force-untracked-cache", &untracked_cache, + N_("enable untracked cache without testing the filesystem"), 2), OPT_END() }; @@ -871,14 +1036,14 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) case PARSE_OPT_DONE: { const char *path = ctx.argv[0]; - const char *p; + char *p; setup_work_tree(); p = prefix_path(prefix, prefix_length, path); update_one(p); if (set_executable_bit) chmod_path(set_executable_bit, p); - free((char *)p); + free(p); ctx.argc--; ctx.argv++; break; @@ -909,7 +1074,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) setup_work_tree(); while (strbuf_getline(&buf, stdin, line_termination) != EOF) { - const char *p; + char *p; if (line_termination && buf.buf[0] == '"') { strbuf_reset(&nbuf); if (unquote_c_style(&nbuf, buf.buf, NULL)) @@ -920,7 +1085,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) update_one(p); if (set_executable_bit) chmod_path(set_executable_bit, p); - free((char *)p); + free(p); } strbuf_release(&nbuf); strbuf_release(&buf); @@ -939,6 +1104,28 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) the_index.split_index = NULL; the_index.cache_changed |= SOMETHING_CHANGED; } + if (untracked_cache > 0) { + struct untracked_cache *uc; + + if (untracked_cache < 2) { + setup_work_tree(); + if (!test_if_untracked_cache_is_supported()) + return 1; + } + if (!the_index.untracked) { + uc = xcalloc(1, sizeof(*uc)); + strbuf_init(&uc->ident, 100); + uc->exclude_per_dir = ".gitignore"; + /* should be the same flags used by git-status */ + uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; + the_index.untracked = uc; + } + add_untracked_ident(the_index.untracked); + the_index.cache_changed |= UNTRACKED_CHANGED; + } else if (!untracked_cache && the_index.untracked) { + the_index.untracked = NULL; + the_index.cache_changed |= UNTRACKED_CHANGED; + } if (active_cache_changed) { if (newfd < 0) { |